Move static CCtx construction sequencing out of the monolithic C leaf. Rust now validates the existing public workspace contract, sequences workspace creation, CCtx and auxiliary reservations, publication, and BMI2 setup, and short-circuits callback-reported failures. C callbacks retain the private cwksp and CCtx layouts plus the native workspace-size and CPU-feature rules. Add matching ABI layout assertions and focused callback-order, reservation- failure, workspace-capacity, and NULL-path unit coverage. Static-CDict and heap-CCtx paths remain unchanged. Test Plan: - `rustfmt --edition 2021 --check rust/src/zstd_compress.rs` -- passed - GCC and Clang `-fsyntax-only` checks on `zstd_compress.c` -- passed - GCC syntax checks with `ZSTD_DISABLE_ASM=1` and `ZSTD_ADDRESS_SANITIZER=1` -- passed - `git diff --check` -- passed - Cargo, make, native suites, and fuzzers not run per request
20403 lines
704 KiB
Rust
20403 lines
704 KiB
Rust
#![allow(non_camel_case_types)]
|
|
#![allow(non_snake_case)]
|
|
#![allow(clippy::missing_safety_doc)]
|
|
#![allow(clippy::too_many_arguments)]
|
|
|
|
//! First high-level compression slice.
|
|
//!
|
|
//! The layout-independent one-shot entry point in this module drives the
|
|
//! already migrated compression leaves. `ZSTD_compressCCtx` uses the same
|
|
//! path after a narrow C-owned context reset; public context allocation now
|
|
//! runs through a Rust-owned boundary while C retains configuration-dependent
|
|
//! private `ZSTD_CCtx_s` initialization. `ZSTD_compress2` and the complete-input simple
|
|
//! `ZSTD_compressStream2(..., ZSTD_e_end)` path dispatch through Rust while
|
|
//! retaining the C implementation for advanced and partial-stream cases.
|
|
|
|
use crate::common::MINMATCH;
|
|
use crate::errors::{ERR_isError, ZstdErrorCode, ERROR};
|
|
use crate::mem::MEM_writeLE32;
|
|
#[cfg(test)]
|
|
use crate::xxhash::XXH64_reset;
|
|
use crate::xxhash::{XXH64_digest, XXH64_state_t, XXH64_update};
|
|
#[cfg(not(test))]
|
|
use crate::zstd_compress_api::ZSTD_compressBound;
|
|
use crate::zstd_compress_block_split::ZSTD_rust_deriveBlockSplits;
|
|
use crate::zstd_compress_frame::{
|
|
write_raw_block, ZSTD_rust_noCompressBlock, ZSTD_rust_rleCompressBlock,
|
|
ZSTD_rust_writeBlockHeader, ZSTD_rust_writeEpilogue, ZSTD_rust_writeFrameHeader,
|
|
ZSTD_writeLastEmptyBlock,
|
|
};
|
|
use crate::zstd_compress_literals::min_gain;
|
|
use crate::zstd_compress_params::{
|
|
ZSTD_compressionParameters, ZSTD_frameParameters, ZSTD_parameters, ZSTD_resolveMaxBlockSize,
|
|
ZSTD_rustMatchStateSizing, ZSTD_rust_params_adjustCParams, ZSTD_rust_params_allocateChainTable,
|
|
ZSTD_rust_params_checkCParams, ZSTD_rust_params_defaultCLevel,
|
|
ZSTD_rust_params_estimateMatchStateSize, ZSTD_rust_params_getParamsInternal,
|
|
ZSTD_rust_params_maxNbSeq, ZSTD_rust_params_rowMatchFinderUsed,
|
|
ZSTD_rust_params_selectBlockCompressor, ZSTD_rust_params_selectCParams,
|
|
ZSTD_RUST_CPM_NO_ATTACH_DICT, ZSTD_RUST_PS_AUTO, ZSTD_RUST_PS_DISABLE, ZSTD_RUST_PS_ENABLE,
|
|
};
|
|
use crate::zstd_compress_params_api::{
|
|
ZSTD_CCtxParams_setParameter, ZSTD_CCtx_params, ZSTD_customMem, ZSTD_rust_isUpdateAuthorized,
|
|
};
|
|
use crate::zstd_compress_sequences::SeqDef;
|
|
use crate::zstd_compress_stats::{
|
|
update_rep, SeqCollector, SeqStore_t, ZSTD_Sequence, ZSTD_SequencePosition,
|
|
ZSTD_compressedBlockState_t, ZSTD_entropyCTablesMetadata_t, ZSTD_entropyCTables_t,
|
|
ZSTD_rust_confirmRepcodesAndEntropyTables, ZSTD_rust_convertSequencesNoRepcodes,
|
|
ZSTD_rust_copyBlockSequences, ZSTD_rust_countSeqStoreLiteralsBytes,
|
|
ZSTD_rust_countSeqStoreMatchBytes, ZSTD_rust_deriveSeqStoreChunk, ZSTD_rust_determineBlockSize,
|
|
ZSTD_rust_entropyCompressSeqStore, ZSTD_rust_entropyCompressSeqStore_internal,
|
|
ZSTD_rust_fastSequenceLengthSum, ZSTD_rust_finalizeOffBase, ZSTD_rust_get1BlockSummary,
|
|
ZSTD_rust_isRLE, ZSTD_rust_maybeRLE, ZSTD_rust_postProcessSequenceProducerResult,
|
|
ZSTD_rust_resetCompressedBlockState, ZSTD_rust_resetSeqStore,
|
|
ZSTD_rust_seqStore_resolveOffCodes, ZSTD_rust_storeLastLiterals,
|
|
ZSTD_rust_transferSequencesNoDelim, ZSTD_rust_transferSequencesWBlockDelim,
|
|
ZSTD_rust_validateSeqStore, ZSTD_LLT_LITERAL_LENGTH, ZSTD_LLT_MATCH_LENGTH,
|
|
};
|
|
use crate::zstd_compress_superblock::ZSTD_rust_compressSuperBlock;
|
|
use std::ffi::c_void;
|
|
use std::mem::{offset_of, size_of, MaybeUninit};
|
|
use std::os::raw::{c_int, c_longlong, c_uint};
|
|
use std::ptr;
|
|
|
|
#[cfg(not(test))]
|
|
unsafe extern "C" {
|
|
fn ZSTD_createCCtx() -> *mut c_void;
|
|
fn ZSTD_freeCCtx(cctx: *mut c_void) -> usize;
|
|
fn ZSTD_rust_resetCCtxForSimpleCompression(cctx: *mut c_void) -> usize;
|
|
fn ZSTD_rust_prepareCCtxForSimpleCompression(
|
|
cctx: *mut c_void,
|
|
src_size: usize,
|
|
compression_level: c_int,
|
|
) -> usize;
|
|
fn ZSTD_rust_resetCCtxForSimpleCompressionSession(cctx: *mut c_void) -> usize;
|
|
fn ZSTD_rust_markSimpleCompression2Complete(cctx: *mut c_void);
|
|
fn ZSTD_rust_simpleCompress2Level(cctx: *const c_void, src_size: usize) -> c_int;
|
|
fn ZSTD_compress_usingDict(
|
|
cctx: *mut c_void,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
compression_level: c_int,
|
|
) -> usize;
|
|
fn ZSTD_rust_simpleCompressStream2Level(cctx: *const c_void, src_size: usize) -> c_int;
|
|
fn ZSTD_compress2_c(
|
|
cctx: *mut c_void,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
) -> usize;
|
|
fn ZSTD_compressStream2_c(
|
|
cctx: *mut c_void,
|
|
output: *mut ZSTD_outBuffer,
|
|
input: *mut ZSTD_inBuffer,
|
|
end_op: c_int,
|
|
) -> usize;
|
|
}
|
|
|
|
const ZSTD_FAST: c_int = 1;
|
|
const ZSTD_DFAST: c_int = 2;
|
|
const ZSTD_BTLAZY2: c_int = 6;
|
|
const ZSTD_REP_NUM: usize = 3;
|
|
const ZSTD_BM_BUFFERED: c_int = 0;
|
|
const ZSTD_BM_STABLE: c_int = 1;
|
|
const ZSTD_SF_NO_BLOCK_DELIMITERS: c_int = 0;
|
|
const ZSTD_SF_EXPLICIT_BLOCK_DELIMITERS: c_int = 1;
|
|
const ZSTD_BLOCKSIZE_MAX: usize = 1 << 17;
|
|
const ZSTD_CONTENTSIZE_UNKNOWN: u64 = u64::MAX;
|
|
const ZSTD_TARGET_CBLOCK_BSS_COMPRESS: c_int = 0;
|
|
const ZSTD_BLOCK_HEADER_SIZE: usize = 3;
|
|
const MIN_CBLOCK_SIZE: usize = 2;
|
|
const MIN_COMPRESSIBLE_BLOCK_SIZE: usize = MIN_CBLOCK_SIZE + ZSTD_BLOCK_HEADER_SIZE + 1 + 1;
|
|
const ZSTD_ROWSIZE: usize = 16;
|
|
const ZSTD_CSTREAM_STAGE_INIT: c_int = 0;
|
|
|
|
/// Scalar stream state used to select the complete-input fast path. The C
|
|
/// context remains opaque; its private fields are projected before Rust
|
|
/// evaluates the eligibility predicate.
|
|
#[repr(C)]
|
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
pub struct ZSTD_rust_simpleCompressStream2Projection {
|
|
stream_stage: c_int,
|
|
pledged_src_size_plus_one: u64,
|
|
rust_simple_compress2_completed: c_uint,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_simpleCompressStream2Projection, stream_stage) == 0);
|
|
assert!(
|
|
offset_of!(
|
|
ZSTD_rust_simpleCompressStream2Projection,
|
|
pledged_src_size_plus_one
|
|
) == if size_of::<usize>() == 8 { 8 } else { 4 }
|
|
);
|
|
assert!(
|
|
offset_of!(
|
|
ZSTD_rust_simpleCompressStream2Projection,
|
|
rust_simple_compress2_completed
|
|
) == if size_of::<usize>() == 8 { 16 } else { 12 }
|
|
);
|
|
assert!(
|
|
size_of::<ZSTD_rust_simpleCompressStream2Projection>()
|
|
== if size_of::<usize>() == 8 { 24 } else { 16 }
|
|
);
|
|
};
|
|
|
|
pub type ZSTD_rust_simpleCompress2LevelFn = unsafe extern "C" fn(*const c_void, usize) -> c_int;
|
|
|
|
const ZSTD_WINDOW_START_INDEX: u32 = 2;
|
|
const ZSTD_DUBT_UNSORTED_MARK: u32 = 1;
|
|
const ZSTD_INDEXOVERFLOW_MARGIN: usize = 16usize << 20;
|
|
const ZSTD_SHORT_CACHE_TAG_BITS: u32 = 8;
|
|
const ZSTD_CURRENT_MAX: usize = if size_of::<usize>() == 8 {
|
|
3500usize << 20
|
|
} else {
|
|
2000usize << 20
|
|
};
|
|
const ZSTD_CHUNKSIZE_MAX: usize = u32::MAX as usize - ZSTD_CURRENT_MAX;
|
|
const ZSTD_E_END: c_int = 2;
|
|
const ZSTD_E_CONTINUE: c_int = 0;
|
|
const ZSTD_E_FLUSH: c_int = 1;
|
|
const ZSTD_C_WINDOW_LOG: c_int = 101;
|
|
const ZSTD_C_HASH_LOG: c_int = 102;
|
|
const ZSTD_C_CHAIN_LOG: c_int = 103;
|
|
const ZSTD_C_SEARCH_LOG: c_int = 104;
|
|
const ZSTD_C_MIN_MATCH: c_int = 105;
|
|
const ZSTD_C_TARGET_LENGTH: c_int = 106;
|
|
const ZSTD_C_STRATEGY: c_int = 107;
|
|
const ZSTD_C_FORMAT: c_int = 10;
|
|
const ZSTD_C_COMPRESSION_LEVEL: c_int = 100;
|
|
const ZSTD_C_TARGET_C_BLOCK_SIZE: c_int = 130;
|
|
const ZSTD_C_ENABLE_LDM: c_int = 160;
|
|
const ZSTD_C_LDM_HASH_LOG: c_int = 161;
|
|
const ZSTD_C_LDM_MIN_MATCH: c_int = 162;
|
|
const ZSTD_C_LDM_BUCKET_SIZE_LOG: c_int = 163;
|
|
const ZSTD_C_LDM_HASH_RATE_LOG: c_int = 164;
|
|
const ZSTD_C_CONTENT_SIZE_FLAG: c_int = 200;
|
|
const ZSTD_C_CHECKSUM_FLAG: c_int = 201;
|
|
const ZSTD_C_DICT_ID_FLAG: c_int = 202;
|
|
const ZSTD_C_NB_WORKERS: c_int = 400;
|
|
const ZSTD_C_JOB_SIZE: c_int = 401;
|
|
const ZSTD_C_OVERLAP_LOG: c_int = 402;
|
|
const ZSTD_C_RSYNCABLE: c_int = 500;
|
|
const ZSTD_C_FORCE_MAX_WINDOW: c_int = 1000;
|
|
const ZSTD_C_FORCE_ATTACH_DICT: c_int = 1001;
|
|
const ZSTD_C_LITERAL_COMPRESSION_MODE: c_int = 1002;
|
|
const ZSTD_C_SRC_SIZE_HINT: c_int = 1004;
|
|
const ZSTD_C_ENABLE_DEDICATED_DICT_SEARCH: c_int = 1005;
|
|
const ZSTD_C_STABLE_IN_BUFFER: c_int = 1006;
|
|
const ZSTD_C_STABLE_OUT_BUFFER: c_int = 1007;
|
|
const ZSTD_C_BLOCK_DELIMITERS: c_int = 1008;
|
|
const ZSTD_C_VALIDATE_SEQUENCES: c_int = 1009;
|
|
const ZSTD_C_SPLIT_AFTER_SEQUENCES: c_int = 1010;
|
|
const ZSTD_C_USE_ROW_MATCH_FINDER: c_int = 1011;
|
|
const ZSTD_C_DETERMINISTIC_REF_PREFIX: c_int = 1012;
|
|
const ZSTD_C_PREFETCH_CDICT_TABLES: c_int = 1013;
|
|
const ZSTD_C_ENABLE_SEQ_PRODUCER_FALLBACK: c_int = 1014;
|
|
const ZSTD_C_MAX_BLOCK_SIZE: c_int = 1015;
|
|
const ZSTD_C_REPCODE_RESOLUTION: c_int = 1016;
|
|
const ZSTD_C_BLOCK_SPLITTER_LEVEL: c_int = 1017;
|
|
const ZSTD_RESET_SESSION_ONLY: c_int = 1;
|
|
const ZSTD_RESET_PARAMETERS: c_int = 2;
|
|
const ZSTD_RESET_SESSION_AND_PARAMETERS: c_int = 3;
|
|
const ZSTD_CSTREAM_STAGE_LOAD: c_int = 1;
|
|
const ZSTD_CSTREAM_STAGE_FLUSH: c_int = 2;
|
|
const ZSTD_BSS_COMPRESS: c_int = 0;
|
|
const ZSTD_BSS_NO_COMPRESS: c_int = 1;
|
|
const FSE_REPEAT_CHECK: c_int = 1;
|
|
const FSE_REPEAT_VALID: c_int = 2;
|
|
|
|
type FrameChunkPrepareOverflowFn = unsafe extern "C" fn(*mut c_void, *const c_void, usize);
|
|
type FrameChunkPrepareWindowFn = unsafe extern "C" fn(*mut c_void, *const c_void, usize, c_uint);
|
|
type FrameChunkCompressFn =
|
|
unsafe extern "C" fn(*mut c_void, *mut c_void, usize, *const c_void, usize, c_uint) -> usize;
|
|
|
|
type BuildSeqStoreSkipFn = unsafe extern "C" fn(*mut c_void, usize);
|
|
type BuildSeqStorePrepareFn = unsafe extern "C" fn(*mut c_void, *const c_void, usize);
|
|
type BuildSeqStoreCompressFn =
|
|
unsafe extern "C" fn(*mut c_void, *mut SeqStore_t, *mut u32, *const c_void, usize) -> usize;
|
|
type BuildSeqStoreTryExternalProducerFn =
|
|
unsafe extern "C" fn(*mut c_void, *const c_void, usize, *mut c_int, *mut c_int) -> usize;
|
|
type BuildSeqStoreClearLdmFn = unsafe extern "C" fn(*mut c_void);
|
|
|
|
type BlockCompressorFn =
|
|
unsafe extern "C" fn(*mut c_void, *mut SeqStore_t, *mut u32, *const c_void, usize) -> usize;
|
|
|
|
const ZSTD_RUST_BLOCK_COMPRESSOR_TABLE_WIDTH: usize = 10;
|
|
const ZSTD_RUST_ROW_BLOCK_COMPRESSOR_TABLE_WIDTH: usize = 3;
|
|
const ZSTD_RUST_DICT_MODE_COUNT: c_int = 4;
|
|
const ZSTD_RUST_BLOCK_COMPRESSOR_MAX_STRATEGY: c_int =
|
|
ZSTD_RUST_BLOCK_COMPRESSOR_TABLE_WIDTH as c_int - 1;
|
|
|
|
/// C supplies the configuration-dependent compressor tables and their private
|
|
/// match-state leaves. Rust owns the strategy/row-mode index and dictionary
|
|
/// mode dispatch into those tables.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_selectBlockCompressorState {
|
|
block_compressors: *const Option<BlockCompressorFn>,
|
|
row_block_compressors: *const Option<BlockCompressorFn>,
|
|
selected_compressor: *mut Option<BlockCompressorFn>,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(size_of::<BlockCompressorFn>() == size_of::<usize>());
|
|
assert!(size_of::<Option<BlockCompressorFn>>() == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_selectBlockCompressorState, block_compressors) == 0);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_selectBlockCompressorState, row_block_compressors)
|
|
== size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_selectBlockCompressorState, selected_compressor)
|
|
== 2 * size_of::<usize>()
|
|
);
|
|
assert!(size_of::<ZSTD_rust_selectBlockCompressorState>() == 3 * size_of::<usize>());
|
|
};
|
|
|
|
/// Select one C-owned match-state compressor while keeping the table policy
|
|
/// and dispatch-index arithmetic in Rust.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_selectBlockCompressor(
|
|
state: *const ZSTD_rust_selectBlockCompressorState,
|
|
strategy: c_int,
|
|
use_row_match_finder: c_int,
|
|
dict_mode: c_int,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
if state.block_compressors.is_null()
|
|
|| state.row_block_compressors.is_null()
|
|
|| state.selected_compressor.is_null()
|
|
|| !(ZSTD_FAST..=ZSTD_RUST_BLOCK_COMPRESSOR_MAX_STRATEGY).contains(&strategy)
|
|
|| !(0..ZSTD_RUST_DICT_MODE_COUNT).contains(&dict_mode)
|
|
{
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
unsafe { *state.selected_compressor = None };
|
|
let selected_index = ZSTD_rust_params_selectBlockCompressor(strategy, use_row_match_finder);
|
|
let (table, table_width, table_index) = if selected_index < 3 {
|
|
(
|
|
state.row_block_compressors,
|
|
ZSTD_RUST_ROW_BLOCK_COMPRESSOR_TABLE_WIDTH,
|
|
selected_index,
|
|
)
|
|
} else {
|
|
(
|
|
state.block_compressors,
|
|
ZSTD_RUST_BLOCK_COMPRESSOR_TABLE_WIDTH,
|
|
selected_index - 3,
|
|
)
|
|
};
|
|
if table_index < 0 {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
let callback = unsafe { *table.add(dict_mode as usize * table_width + table_index as usize) };
|
|
let Some(callback) = callback else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
unsafe { *state.selected_compressor = Some(callback) };
|
|
0
|
|
}
|
|
|
|
/// Explicit projection for the sequence-store builder.
|
|
///
|
|
/// Rust owns the threshold/reset/repcode/literal-store orchestration. The
|
|
/// callbacks retain the private matchfinder, LDM, and external-producer
|
|
/// fallback operations in C without passing `ZSTD_CCtx` across the ABI.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_buildSeqStoreState {
|
|
seq_store: *mut SeqStore_t,
|
|
prev_c_block: *mut *mut ZSTD_compressedBlockState_t,
|
|
next_c_block: *mut *mut ZSTD_compressedBlockState_t,
|
|
callback_context: *mut c_void,
|
|
min_match: c_uint,
|
|
validate_seq_store: c_int,
|
|
skip_small_block: BuildSeqStoreSkipFn,
|
|
prepare_match_state: BuildSeqStorePrepareFn,
|
|
has_external_sequences: c_int,
|
|
ldm_enabled: c_int,
|
|
has_external_sequence_producer: c_int,
|
|
enable_match_finder_fallback: c_int,
|
|
compress_external_sequences: BuildSeqStoreCompressFn,
|
|
compress_ldm: BuildSeqStoreCompressFn,
|
|
try_external_sequence_producer: BuildSeqStoreTryExternalProducerFn,
|
|
compress_internal: BuildSeqStoreCompressFn,
|
|
clear_ldm_seq_store: BuildSeqStoreClearLdmFn,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_buildSeqStoreState, seq_store) == 0);
|
|
assert!(offset_of!(ZSTD_rust_buildSeqStoreState, prev_c_block) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_buildSeqStoreState, next_c_block) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_buildSeqStoreState, callback_context) == 3 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_buildSeqStoreState, min_match) == 4 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_buildSeqStoreState, validate_seq_store)
|
|
== 4 * size_of::<usize>() + size_of::<c_uint>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_buildSeqStoreState, skip_small_block)
|
|
== 4 * size_of::<usize>() + size_of::<c_uint>() + size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_buildSeqStoreState, prepare_match_state)
|
|
== 5 * size_of::<usize>() + size_of::<c_uint>() + size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_buildSeqStoreState, has_external_sequences)
|
|
== 6 * size_of::<usize>() + size_of::<c_uint>() + size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_buildSeqStoreState, ldm_enabled)
|
|
== 6 * size_of::<usize>() + size_of::<c_uint>() + 2 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_buildSeqStoreState, has_external_sequence_producer)
|
|
== 6 * size_of::<usize>() + size_of::<c_uint>() + 3 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_buildSeqStoreState, enable_match_finder_fallback)
|
|
== 6 * size_of::<usize>() + size_of::<c_uint>() + 4 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_buildSeqStoreState, compress_external_sequences)
|
|
== 6 * size_of::<usize>() + size_of::<c_uint>() + 5 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_buildSeqStoreState, compress_ldm)
|
|
== 7 * size_of::<usize>() + size_of::<c_uint>() + 5 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_buildSeqStoreState, try_external_sequence_producer)
|
|
== size_of::<[usize; 8]>() + size_of::<c_uint>() + 5 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_buildSeqStoreState, compress_internal)
|
|
== 9 * size_of::<usize>() + size_of::<c_uint>() + 5 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_buildSeqStoreState, clear_ldm_seq_store)
|
|
== 10 * size_of::<usize>() + size_of::<c_uint>() + 5 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
size_of::<ZSTD_rust_buildSeqStoreState>()
|
|
== 11 * size_of::<usize>() + size_of::<c_uint>() + 5 * size_of::<c_int>()
|
|
);
|
|
};
|
|
|
|
type ExternalSequenceProducerFn = unsafe extern "C" fn(
|
|
*mut c_void,
|
|
*mut ZSTD_Sequence,
|
|
usize,
|
|
*const c_void,
|
|
usize,
|
|
*const c_void,
|
|
usize,
|
|
c_int,
|
|
usize,
|
|
) -> usize;
|
|
type ExternalSequenceTransferFn = unsafe extern "C" fn(
|
|
*mut c_void,
|
|
*mut ZSTD_SequencePosition,
|
|
*const ZSTD_Sequence,
|
|
usize,
|
|
*const c_void,
|
|
usize,
|
|
) -> usize;
|
|
|
|
/// Projection for the successful block-level external sequence-producer path.
|
|
///
|
|
/// Rust owns producer invocation, result post-processing, length validation,
|
|
/// and transfer ordering. The transfer callback retains the private CCtx
|
|
/// match-state projection; C owns fallback selection after this leaf returns.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_externalSequenceProducerState {
|
|
callback_context: *mut c_void,
|
|
producer_state: *mut c_void,
|
|
producer: Option<ExternalSequenceProducerFn>,
|
|
ext_seq_buf: *mut ZSTD_Sequence,
|
|
ext_seq_buf_capacity: *const usize,
|
|
src: *const c_void,
|
|
src_size: *const usize,
|
|
compression_level: *const c_int,
|
|
window_size: *const usize,
|
|
transfer: Option<ExternalSequenceTransferFn>,
|
|
external_seq_count: *mut usize,
|
|
seq_store_complete: *mut c_int,
|
|
allow_fallback: *mut c_int,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_externalSequenceProducerState, callback_context) == 0);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_externalSequenceProducerState, producer_state) == size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_externalSequenceProducerState, producer) == 2 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_externalSequenceProducerState, ext_seq_buf) == 3 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(
|
|
ZSTD_rust_externalSequenceProducerState,
|
|
ext_seq_buf_capacity
|
|
) == 4 * size_of::<usize>()
|
|
);
|
|
assert!(offset_of!(ZSTD_rust_externalSequenceProducerState, src) == 5 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_externalSequenceProducerState, src_size) == 6 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_externalSequenceProducerState, compression_level)
|
|
== 7 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_externalSequenceProducerState, window_size) == size_of::<[usize; 8]>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_externalSequenceProducerState, transfer) == 9 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_externalSequenceProducerState, external_seq_count)
|
|
== 10 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_externalSequenceProducerState, seq_store_complete)
|
|
== 11 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_externalSequenceProducerState, allow_fallback)
|
|
== 12 * size_of::<usize>()
|
|
);
|
|
assert!(size_of::<ZSTD_rust_externalSequenceProducerState>() == 13 * size_of::<usize>());
|
|
};
|
|
|
|
/// Try an external sequence producer; C retains only the fallback decision.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_tryExternalSequenceProducer(
|
|
state: *const ZSTD_rust_externalSequenceProducerState,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
let Some(producer) = state.producer else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(transfer) = state.transfer else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
if state.ext_seq_buf.is_null()
|
|
|| state.ext_seq_buf_capacity.is_null()
|
|
|| state.src.is_null()
|
|
|| state.src_size.is_null()
|
|
|| state.compression_level.is_null()
|
|
|| state.window_size.is_null()
|
|
|| state.external_seq_count.is_null()
|
|
|| state.seq_store_complete.is_null()
|
|
|| state.allow_fallback.is_null()
|
|
{
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
unsafe {
|
|
*state.seq_store_complete = 0;
|
|
*state.allow_fallback = 0;
|
|
}
|
|
let ext_seq_buf_capacity = unsafe { *state.ext_seq_buf_capacity };
|
|
let src_size = unsafe { *state.src_size };
|
|
let nb_external_seqs = unsafe {
|
|
producer(
|
|
state.producer_state,
|
|
state.ext_seq_buf,
|
|
ext_seq_buf_capacity,
|
|
state.src,
|
|
src_size,
|
|
ptr::null(),
|
|
0,
|
|
*state.compression_level,
|
|
*state.window_size,
|
|
)
|
|
};
|
|
unsafe { *state.external_seq_count = nb_external_seqs };
|
|
|
|
let nb_post_processed_seqs = unsafe {
|
|
ZSTD_rust_postProcessSequenceProducerResult(
|
|
state.ext_seq_buf,
|
|
nb_external_seqs,
|
|
ext_seq_buf_capacity,
|
|
src_size,
|
|
)
|
|
};
|
|
if ERR_isError(nb_post_processed_seqs) {
|
|
unsafe { *state.allow_fallback = 1 };
|
|
return nb_post_processed_seqs;
|
|
}
|
|
|
|
let seq_len_sum =
|
|
unsafe { ZSTD_rust_fastSequenceLengthSum(state.ext_seq_buf, nb_post_processed_seqs) };
|
|
if seq_len_sum > src_size {
|
|
return ERROR(ZstdErrorCode::ExternalSequencesInvalid);
|
|
}
|
|
|
|
let mut seq_pos = ZSTD_SequencePosition::default();
|
|
let transfer_result = unsafe {
|
|
transfer(
|
|
state.callback_context,
|
|
&mut seq_pos,
|
|
state.ext_seq_buf,
|
|
nb_post_processed_seqs,
|
|
state.src,
|
|
src_size,
|
|
)
|
|
};
|
|
if ERR_isError(transfer_result) {
|
|
return transfer_result;
|
|
}
|
|
|
|
unsafe { *state.seq_store_complete = 1 };
|
|
0
|
|
}
|
|
|
|
/// Reset the externally referenced raw-sequence store without exposing its C
|
|
/// representation to Rust.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_externalSequenceStoreState {
|
|
seq: *mut *mut c_void,
|
|
pos: *mut usize,
|
|
pos_in_sequence: *mut usize,
|
|
size: *mut usize,
|
|
capacity: *mut usize,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_externalSequenceStoreState, seq) == 0);
|
|
assert!(offset_of!(ZSTD_rust_externalSequenceStoreState, pos) == size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_externalSequenceStoreState, pos_in_sequence) == 2 * size_of::<usize>()
|
|
);
|
|
assert!(offset_of!(ZSTD_rust_externalSequenceStoreState, size) == 3 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_externalSequenceStoreState, capacity) == 4 * size_of::<usize>());
|
|
assert!(size_of::<ZSTD_rust_externalSequenceStoreState>() == 5 * size_of::<usize>());
|
|
};
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_referenceExternalSequences(
|
|
state: *const ZSTD_rust_externalSequenceStoreState,
|
|
seq: *mut c_void,
|
|
nb_seq: usize,
|
|
) {
|
|
let Some(state) = state.as_ref() else {
|
|
return;
|
|
};
|
|
if state.seq.is_null()
|
|
|| state.pos.is_null()
|
|
|| state.pos_in_sequence.is_null()
|
|
|| state.size.is_null()
|
|
|| state.capacity.is_null()
|
|
{
|
|
return;
|
|
}
|
|
|
|
unsafe {
|
|
*state.seq = seq;
|
|
*state.size = nb_seq;
|
|
*state.capacity = nb_seq;
|
|
*state.pos = 0;
|
|
*state.pos_in_sequence = 0;
|
|
}
|
|
}
|
|
|
|
/// C-owned operations used by Rust's frame-chunk block-preparation policy.
|
|
///
|
|
/// The callback context and private window/workspace layout remain in C; Rust
|
|
/// controls the order in which these operations run.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_frameChunkPrepareState {
|
|
callback_context: *mut c_void,
|
|
max_dist: c_uint,
|
|
correct_overflow: FrameChunkPrepareOverflowFn,
|
|
check_dict_validity: FrameChunkPrepareWindowFn,
|
|
enforce_max_dist: FrameChunkPrepareWindowFn,
|
|
clamp_state: *const ZSTD_rust_frameChunkClampState,
|
|
}
|
|
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_frameChunkClampState {
|
|
next_to_update: *mut c_uint,
|
|
low_limit: *const c_uint,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_frameChunkClampState, next_to_update) == 0);
|
|
assert!(offset_of!(ZSTD_rust_frameChunkClampState, low_limit) == size_of::<usize>());
|
|
assert!(size_of::<ZSTD_rust_frameChunkClampState>() == 2 * size_of::<usize>());
|
|
};
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_frameChunkPrepareState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_frameChunkPrepareState, max_dist) == size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_frameChunkPrepareState, correct_overflow) == 2 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_frameChunkPrepareState, check_dict_validity) == 3 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_frameChunkPrepareState, enforce_max_dist) == 4 * size_of::<usize>()
|
|
);
|
|
assert!(offset_of!(ZSTD_rust_frameChunkPrepareState, clamp_state) == 5 * size_of::<usize>());
|
|
assert!(size_of::<ZSTD_rust_frameChunkPrepareState>() == 6 * size_of::<usize>());
|
|
};
|
|
|
|
/// Projected inputs shared by the frame-chunk internal block path and the
|
|
/// deprecated block adapter. Sequence-store construction and the private
|
|
/// compressed-block storage remain behind these two C-owned projections.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_compressContinueBlockState {
|
|
build_seq_store_state: *const ZSTD_rust_buildSeqStoreState,
|
|
block_internal_state: *const ZSTD_rust_blockInternalState,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_compressContinueBlockState, build_seq_store_state) == 0);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressContinueBlockState, block_internal_state)
|
|
== size_of::<usize>()
|
|
);
|
|
assert!(size_of::<ZSTD_rust_compressContinueBlockState>() == 2 * size_of::<usize>());
|
|
};
|
|
|
|
/// Explicit projection of the state used by the block-split estimator.
|
|
///
|
|
/// C retains the private block-split context and owns projection assembly;
|
|
/// Rust owns the estimator implementation and receives only its required
|
|
/// sequence stores, entropy tables, parameters, and workspace.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_deriveBlockSplitsState {
|
|
original_seq_store: *const SeqStore_t,
|
|
full_seq_store_chunk: *mut SeqStore_t,
|
|
first_half_seq_store: *mut SeqStore_t,
|
|
second_half_seq_store: *mut SeqStore_t,
|
|
prev_c_block: *mut *mut ZSTD_compressedBlockState_t,
|
|
next_c_block: *mut *mut ZSTD_compressedBlockState_t,
|
|
entropy_metadata: *mut ZSTD_entropyCTablesMetadata_t,
|
|
workspace: *mut c_void,
|
|
workspace_size: usize,
|
|
strategy: c_int,
|
|
disable_literal_compression: c_int,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_deriveBlockSplitsState, original_seq_store) == 0);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_deriveBlockSplitsState, full_seq_store_chunk) == size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_deriveBlockSplitsState, first_half_seq_store)
|
|
== 2 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_deriveBlockSplitsState, second_half_seq_store)
|
|
== 3 * size_of::<usize>()
|
|
);
|
|
assert!(offset_of!(ZSTD_rust_deriveBlockSplitsState, prev_c_block) == 4 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_deriveBlockSplitsState, next_c_block) == 5 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_deriveBlockSplitsState, entropy_metadata) == 6 * size_of::<usize>()
|
|
);
|
|
assert!(offset_of!(ZSTD_rust_deriveBlockSplitsState, workspace) == 7 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_deriveBlockSplitsState, workspace_size) == usize::BITS as usize);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_deriveBlockSplitsState, strategy)
|
|
== usize::BITS as usize + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(
|
|
ZSTD_rust_deriveBlockSplitsState,
|
|
disable_literal_compression
|
|
) == usize::BITS as usize + size_of::<usize>() + size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
size_of::<ZSTD_rust_deriveBlockSplitsState>()
|
|
== usize::BITS as usize + size_of::<usize>() + 2 * size_of::<c_int>()
|
|
);
|
|
};
|
|
|
|
/// Explicit projection of the state used by `ZSTD_compress_frameChunk`.
|
|
///
|
|
/// The Rust side owns the per-frame block loop and its savings/dispatch
|
|
/// policy. The callback context is opaque to Rust and is only returned to
|
|
/// C-owned callbacks, which retain private window, workspace, and CCtx
|
|
/// layout-sensitive operations.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_frameChunkState {
|
|
callback_context: *mut c_void,
|
|
tmp_workspace: *mut c_void,
|
|
checksum_state: *mut XXH64_state_t,
|
|
is_first_block: *mut c_int,
|
|
stage: *mut c_int,
|
|
tmp_wksp_size: usize,
|
|
block_size_max: usize,
|
|
savings: c_longlong,
|
|
pre_block_splitter_level: c_int,
|
|
strategy: c_int,
|
|
use_target_c_block_size: c_int,
|
|
block_splitter_enabled: c_int,
|
|
checksum_flag: c_int,
|
|
ending_stage: c_int,
|
|
prepare_state: *const ZSTD_rust_frameChunkPrepareState,
|
|
compress_target: Option<FrameChunkCompressFn>,
|
|
compress_split: Option<FrameChunkCompressFn>,
|
|
compress_internal: Option<FrameChunkCompressFn>,
|
|
compress_internal_state: *const ZSTD_rust_compressContinueBlockState,
|
|
compress_target_state: *const ZSTD_rust_targetCBlockSizeState,
|
|
compress_split_state: *const ZSTD_rust_splitBlockState,
|
|
derive_block_splits_state: *const ZSTD_rust_deriveBlockSplitsState,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_frameChunkState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_frameChunkState, tmp_workspace) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_frameChunkState, checksum_state) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_frameChunkState, is_first_block) == 3 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_frameChunkState, stage) == 4 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_frameChunkState, tmp_wksp_size) == 5 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_frameChunkState, block_size_max) == 6 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_frameChunkState, savings) == 7 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_frameChunkState, pre_block_splitter_level)
|
|
== 7 * size_of::<usize>() + size_of::<c_longlong>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_frameChunkState, strategy)
|
|
== 7 * size_of::<usize>() + size_of::<c_longlong>() + size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_frameChunkState, use_target_c_block_size)
|
|
== 7 * size_of::<usize>() + size_of::<c_longlong>() + 2 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_frameChunkState, block_splitter_enabled)
|
|
== 7 * size_of::<usize>() + size_of::<c_longlong>() + 3 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_frameChunkState, checksum_flag)
|
|
== 7 * size_of::<usize>() + size_of::<c_longlong>() + 4 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_frameChunkState, ending_stage)
|
|
== 7 * size_of::<usize>() + size_of::<c_longlong>() + 5 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_frameChunkState, prepare_state)
|
|
== 7 * size_of::<usize>() + size_of::<c_longlong>() + 6 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_frameChunkState, compress_internal_state)
|
|
== 11 * size_of::<usize>() + size_of::<c_longlong>() + 6 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_frameChunkState, compress_target_state)
|
|
== 12 * size_of::<usize>() + size_of::<c_longlong>() + 6 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_frameChunkState, compress_split_state)
|
|
== 13 * size_of::<usize>() + size_of::<c_longlong>() + 6 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_frameChunkState, derive_block_splits_state)
|
|
== 14 * size_of::<usize>() + size_of::<c_longlong>() + 6 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
size_of::<ZSTD_rust_frameChunkState>()
|
|
== 15 * size_of::<usize>() + size_of::<c_longlong>() + 6 * size_of::<c_int>()
|
|
);
|
|
};
|
|
|
|
/// Rust implementation of `ZSTD_compress_frameChunk`.
|
|
///
|
|
/// C still owns the private match-state/window operations and invokes the
|
|
/// selected block body through callbacks. Rust owns preparation ordering, the
|
|
/// block-size heuristic call, output framing, savings accounting, checksum
|
|
/// sequencing, and frame-state update.
|
|
#[inline]
|
|
unsafe fn prepare_frame_chunk_block(
|
|
state: &ZSTD_rust_frameChunkPrepareState,
|
|
src: *const c_void,
|
|
block_size: usize,
|
|
) {
|
|
unsafe { (state.correct_overflow)(state.callback_context, src, block_size) };
|
|
unsafe { (state.check_dict_validity)(state.callback_context, src, block_size, state.max_dist) };
|
|
unsafe { (state.enforce_max_dist)(state.callback_context, src, block_size, state.max_dist) };
|
|
if state.clamp_state.is_null() {
|
|
return;
|
|
}
|
|
let clamp_state = unsafe { &*state.clamp_state };
|
|
if clamp_state.next_to_update.is_null() || clamp_state.low_limit.is_null() {
|
|
return;
|
|
}
|
|
unsafe {
|
|
/* Ensure hash/chain table insertion resumes no sooner than lowlimit. */
|
|
if *clamp_state.next_to_update < *clamp_state.low_limit {
|
|
*clamp_state.next_to_update = *clamp_state.low_limit;
|
|
}
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
unsafe fn compress_frame_chunk_body_with(
|
|
state: &ZSTD_rust_frameChunkState,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
last_frame_chunk: c_uint,
|
|
) -> usize {
|
|
if state.is_first_block.is_null() || state.stage.is_null() || state.prepare_state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
if state.checksum_flag != 0 && src_size != 0 {
|
|
if state.checksum_state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
unsafe {
|
|
let _ = XXH64_update(state.checksum_state, src, src_size);
|
|
}
|
|
}
|
|
|
|
let mut remaining = src_size;
|
|
let mut ip = src.cast::<u8>();
|
|
let mut op = dst.cast::<u8>();
|
|
let mut remaining_capacity = dst_capacity;
|
|
let mut savings = state.savings;
|
|
let mut compressed_size_total = 0usize;
|
|
|
|
while remaining != 0 {
|
|
let block_size = unsafe {
|
|
crate::zstd_compress_frame::ZSTD_rust_optimalBlockSize(
|
|
ip.cast(),
|
|
remaining,
|
|
state.block_size_max,
|
|
state.pre_block_splitter_level,
|
|
state.strategy,
|
|
savings,
|
|
state.tmp_workspace,
|
|
state.tmp_wksp_size,
|
|
)
|
|
};
|
|
if block_size == 0 || block_size > remaining {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
let last_block = last_frame_chunk & u32::from(block_size == remaining);
|
|
|
|
/* Keep the original early capacity guard: even a raw block needs the
|
|
* minimum block header plus the minimum compressible payload budget. */
|
|
if remaining_capacity < ZSTD_BLOCK_HEADER_SIZE + MIN_CBLOCK_SIZE + 1 {
|
|
return ERROR(ZstdErrorCode::DstSizeTooSmall);
|
|
}
|
|
|
|
unsafe { prepare_frame_chunk_block(&*state.prepare_state, ip.cast(), block_size) };
|
|
|
|
let c_size = if state.use_target_c_block_size != 0 {
|
|
if !state.compress_target_state.is_null() {
|
|
let block_state = if state.compress_internal_state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
} else {
|
|
unsafe { &*state.compress_internal_state }
|
|
};
|
|
if block_state.build_seq_store_state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let bss = unsafe {
|
|
ZSTD_rust_buildSeqStore(
|
|
block_state.build_seq_store_state,
|
|
ip.cast(),
|
|
block_size,
|
|
)
|
|
};
|
|
if ERR_isError(bss) {
|
|
return bss;
|
|
}
|
|
let mut target_state = unsafe { ptr::read(state.compress_target_state) };
|
|
target_state.is_first_block = unsafe { *state.is_first_block };
|
|
unsafe {
|
|
ZSTD_rust_compressBlockTargetCBlockSizeAfterBuild(
|
|
&target_state,
|
|
op.cast(),
|
|
remaining_capacity,
|
|
ip.cast(),
|
|
block_size,
|
|
bss as c_int,
|
|
last_block,
|
|
)
|
|
}
|
|
} else {
|
|
let Some(compress_target) = state.compress_target else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
unsafe {
|
|
compress_target(
|
|
state.callback_context,
|
|
op.cast(),
|
|
remaining_capacity,
|
|
ip.cast(),
|
|
block_size,
|
|
last_block,
|
|
)
|
|
}
|
|
}
|
|
} else if state.block_splitter_enabled != 0 {
|
|
if !state.compress_split_state.is_null() {
|
|
let block_state = if state.compress_internal_state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
} else {
|
|
unsafe { &*state.compress_internal_state }
|
|
};
|
|
let split_state = unsafe { &*state.compress_split_state };
|
|
if block_state.build_seq_store_state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let bss = unsafe {
|
|
ZSTD_rust_buildSeqStore(
|
|
block_state.build_seq_store_state,
|
|
ip.cast(),
|
|
block_size,
|
|
)
|
|
};
|
|
if ERR_isError(bss) {
|
|
return bss;
|
|
}
|
|
let num_splits = if bss == ZSTD_BSS_COMPRESS as usize {
|
|
if split_state.seq_store.is_null() || split_state.partitions.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let sequence_store = unsafe { &*split_state.seq_store };
|
|
if sequence_store.sequencesStart.is_null() || sequence_store.sequences.is_null()
|
|
{
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let sequence_count = unsafe {
|
|
sequence_store
|
|
.sequences
|
|
.offset_from(sequence_store.sequencesStart)
|
|
};
|
|
if sequence_count < 0 {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
if state.derive_block_splits_state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let derive_state = unsafe { &*state.derive_block_splits_state };
|
|
if derive_state.original_seq_store.is_null()
|
|
|| derive_state.full_seq_store_chunk.is_null()
|
|
|| derive_state.first_half_seq_store.is_null()
|
|
|| derive_state.second_half_seq_store.is_null()
|
|
|| derive_state.prev_c_block.is_null()
|
|
|| derive_state.next_c_block.is_null()
|
|
|| derive_state.entropy_metadata.is_null()
|
|
{
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let prev_c_block = unsafe { *derive_state.prev_c_block };
|
|
let next_c_block = unsafe { *derive_state.next_c_block };
|
|
if prev_c_block.is_null() || next_c_block.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let result = unsafe {
|
|
ZSTD_rust_deriveBlockSplits(
|
|
split_state.partitions.cast_mut(),
|
|
sequence_count as u32,
|
|
derive_state.original_seq_store,
|
|
derive_state.full_seq_store_chunk,
|
|
derive_state.first_half_seq_store,
|
|
derive_state.second_half_seq_store,
|
|
ptr::addr_of!((*prev_c_block).entropy),
|
|
ptr::addr_of_mut!((*next_c_block).entropy),
|
|
derive_state.strategy,
|
|
derive_state.disable_literal_compression,
|
|
derive_state.entropy_metadata,
|
|
derive_state.workspace,
|
|
derive_state.workspace_size,
|
|
)
|
|
};
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
result
|
|
} else {
|
|
0
|
|
};
|
|
let mut split_state = unsafe { ptr::read(state.compress_split_state) };
|
|
split_state.is_first_block = unsafe { *state.is_first_block };
|
|
unsafe {
|
|
ZSTD_rust_compressBlockSplitAfterBuild(
|
|
&split_state,
|
|
op.cast(),
|
|
remaining_capacity,
|
|
ip.cast(),
|
|
block_size,
|
|
last_block,
|
|
num_splits,
|
|
bss as c_int,
|
|
)
|
|
}
|
|
} else {
|
|
let Some(compress_split) = state.compress_split else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
unsafe {
|
|
compress_split(
|
|
state.callback_context,
|
|
op.cast(),
|
|
remaining_capacity,
|
|
ip.cast(),
|
|
block_size,
|
|
last_block,
|
|
)
|
|
}
|
|
}
|
|
} else {
|
|
let compressed_size = if !state.compress_internal_state.is_null() {
|
|
unsafe {
|
|
ZSTD_rust_compressFrameChunkInternal(
|
|
state.compress_internal_state,
|
|
state.is_first_block,
|
|
op.add(ZSTD_BLOCK_HEADER_SIZE).cast(),
|
|
remaining_capacity - ZSTD_BLOCK_HEADER_SIZE,
|
|
ip.cast(),
|
|
block_size,
|
|
last_block,
|
|
)
|
|
}
|
|
} else {
|
|
let Some(compress_internal) = state.compress_internal else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
unsafe {
|
|
compress_internal(
|
|
state.callback_context,
|
|
op.add(ZSTD_BLOCK_HEADER_SIZE).cast(),
|
|
remaining_capacity - ZSTD_BLOCK_HEADER_SIZE,
|
|
ip.cast(),
|
|
block_size,
|
|
last_block,
|
|
)
|
|
}
|
|
};
|
|
if ERR_isError(compressed_size) {
|
|
return compressed_size;
|
|
}
|
|
|
|
if compressed_size == 0 {
|
|
unsafe {
|
|
ZSTD_rust_noCompressBlock(
|
|
op.cast(),
|
|
remaining_capacity,
|
|
ip.cast(),
|
|
block_size,
|
|
last_block,
|
|
)
|
|
}
|
|
} else {
|
|
unsafe {
|
|
ZSTD_rust_writeBlockHeader(op.cast(), compressed_size, block_size, last_block)
|
|
};
|
|
compressed_size + ZSTD_BLOCK_HEADER_SIZE
|
|
}
|
|
};
|
|
|
|
if ERR_isError(c_size) {
|
|
return c_size;
|
|
}
|
|
|
|
savings =
|
|
savings.wrapping_add((block_size as c_longlong).wrapping_sub(c_size as c_longlong));
|
|
unsafe {
|
|
ip = ip.add(block_size);
|
|
op = op.add(c_size);
|
|
}
|
|
remaining -= block_size;
|
|
debug_assert!(c_size <= remaining_capacity);
|
|
remaining_capacity = remaining_capacity.wrapping_sub(c_size);
|
|
compressed_size_total = compressed_size_total.wrapping_add(c_size);
|
|
unsafe { *state.is_first_block = 0 };
|
|
}
|
|
|
|
if last_frame_chunk != 0 && compressed_size_total != 0 {
|
|
unsafe { *state.stage = state.ending_stage };
|
|
}
|
|
compressed_size_total
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_compressFrameChunk(
|
|
state: *const ZSTD_rust_frameChunkState,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
last_frame_chunk: c_uint,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
unsafe {
|
|
compress_frame_chunk_body_with(&*state, dst, dst_capacity, src, src_size, last_frame_chunk)
|
|
}
|
|
}
|
|
|
|
type CompressContinueBlockFn =
|
|
unsafe extern "C" fn(*mut c_void, *mut c_void, usize, *const c_void, usize, c_uint) -> usize;
|
|
|
|
/// Pointer projection for one C-owned `ZSTD_window_t` and the match-state
|
|
/// fields which follow its update. Each field points directly at the C
|
|
/// storage so later callbacks observe the updated window immediately.
|
|
#[repr(C)]
|
|
#[derive(Default)]
|
|
struct ZSTD_rust_compressContinueWindowProjection {
|
|
next_src: *mut *const c_void,
|
|
base: *mut *const c_void,
|
|
dict_base: *mut *const c_void,
|
|
dict_limit: *mut c_uint,
|
|
low_limit: *mut c_uint,
|
|
force_non_contiguous: *mut c_int,
|
|
next_to_update: *mut c_uint,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_compressContinueWindowProjection, next_src) == 0);
|
|
assert!(offset_of!(ZSTD_rust_compressContinueWindowProjection, base) == size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressContinueWindowProjection, dict_base) == 2 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressContinueWindowProjection, dict_limit)
|
|
== 3 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressContinueWindowProjection, low_limit) == 4 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(
|
|
ZSTD_rust_compressContinueWindowProjection,
|
|
force_non_contiguous
|
|
) == 5 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressContinueWindowProjection, next_to_update)
|
|
== 6 * size_of::<usize>()
|
|
);
|
|
assert!(size_of::<ZSTD_rust_compressContinueWindowProjection>() == 7 * size_of::<usize>());
|
|
};
|
|
|
|
/// Explicit projection for the high-level continue and deprecated block APIs.
|
|
///
|
|
/// Rust owns stage transitions, frame-header sequencing, input progression,
|
|
/// and dispatch between the already-migrated frame-chunk/block bodies. The
|
|
/// frame-header parameters and frame-chunk state are projected explicitly.
|
|
/// The opaque callback context remains in C for overflow-correction callbacks
|
|
/// and context-sensitive block operations; window advancement is projected
|
|
/// directly into Rust.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_compressContinueState {
|
|
callback_context: *mut c_void,
|
|
window_state: *const ZSTD_rust_compressContinueWindowProjection,
|
|
ldm_window_state: *const ZSTD_rust_compressContinueWindowProjection,
|
|
overflow_state: *const ZSTD_rust_overflowCorrectState,
|
|
frame_chunk_state: *const ZSTD_rust_frameChunkState,
|
|
compress_block: CompressContinueBlockFn,
|
|
stage: *mut c_int,
|
|
consumed_src_size: *mut u64,
|
|
produced_c_size: *mut u64,
|
|
pledged_src_size_plus_one: u64,
|
|
block_size_max: usize,
|
|
check_block_size: c_int,
|
|
no_dict_id_flag: c_int,
|
|
checksum_flag: c_int,
|
|
content_size_flag: c_int,
|
|
format: c_int,
|
|
window_log: c_uint,
|
|
dict_id: c_uint,
|
|
ldm_enabled: c_int,
|
|
}
|
|
|
|
const COMPRESS_CONTINUE_SCALARS_OFFSET: usize =
|
|
size_of::<[usize; 9]>() + size_of::<u64>() + size_of::<usize>();
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_compressContinueState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_compressContinueState, window_state) == size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressContinueState, ldm_window_state) == 2 * size_of::<usize>()
|
|
);
|
|
assert!(offset_of!(ZSTD_rust_compressContinueState, overflow_state) == 3 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressContinueState, frame_chunk_state) == 4 * size_of::<usize>()
|
|
);
|
|
assert!(offset_of!(ZSTD_rust_compressContinueState, compress_block) == 5 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_compressContinueState, stage) == 6 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressContinueState, consumed_src_size) == 7 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressContinueState, produced_c_size)
|
|
== 8 * (usize::BITS as usize / 8)
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressContinueState, pledged_src_size_plus_one)
|
|
== size_of::<[usize; 9]>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressContinueState, block_size_max)
|
|
== size_of::<[usize; 9]>() + size_of::<u64>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressContinueState, check_block_size)
|
|
== COMPRESS_CONTINUE_SCALARS_OFFSET
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressContinueState, no_dict_id_flag)
|
|
== COMPRESS_CONTINUE_SCALARS_OFFSET + size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressContinueState, checksum_flag)
|
|
== COMPRESS_CONTINUE_SCALARS_OFFSET + size_of::<[c_int; 2]>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressContinueState, content_size_flag)
|
|
== COMPRESS_CONTINUE_SCALARS_OFFSET + size_of::<[c_int; 3]>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressContinueState, format)
|
|
== COMPRESS_CONTINUE_SCALARS_OFFSET + size_of::<[c_int; 4]>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressContinueState, window_log)
|
|
== COMPRESS_CONTINUE_SCALARS_OFFSET + size_of::<[c_int; 5]>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressContinueState, dict_id)
|
|
== COMPRESS_CONTINUE_SCALARS_OFFSET + size_of::<[c_int; 5]>() + size_of::<c_uint>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressContinueState, ldm_enabled)
|
|
== COMPRESS_CONTINUE_SCALARS_OFFSET + size_of::<[c_int; 5]>() + 2 * size_of::<c_uint>()
|
|
);
|
|
assert!(
|
|
size_of::<ZSTD_rust_compressContinueState>()
|
|
== if size_of::<usize>() == 8 { 120 } else { 80 }
|
|
);
|
|
};
|
|
|
|
const ZSTD_COMPRESSION_STAGE_CREATED: c_int = 0;
|
|
const ZSTD_COMPRESSION_STAGE_INIT: c_int = 1;
|
|
const ZSTD_COMPRESSION_STAGE_ONGOING: c_int = 2;
|
|
#[cfg(test)]
|
|
const ZSTD_COMPRESSION_STAGE_ENDING: c_int = 3;
|
|
|
|
type GenerateSequencesGetParameterFn =
|
|
unsafe extern "C" fn(*mut c_void, c_int, *mut c_int) -> usize;
|
|
type GenerateSequencesAllocateFn = unsafe extern "C" fn(*mut c_void, usize) -> *mut c_void;
|
|
type GenerateSequencesFreeFn = unsafe extern "C" fn(*mut c_void, *mut c_void);
|
|
type GenerateSequencesSetCollectorFn =
|
|
unsafe extern "C" fn(*mut c_void, *mut ZSTD_Sequence, usize) -> usize;
|
|
type GenerateSequencesCompress2Fn =
|
|
unsafe extern "C" fn(*mut c_void, *mut c_void, usize, *const c_void, usize) -> usize;
|
|
type GenerateSequencesGetCountFn = unsafe extern "C" fn(*mut c_void) -> usize;
|
|
|
|
/// C-owned context projection for `ZSTD_generateSequences()`.
|
|
///
|
|
/// Rust owns the public policy and temporary-buffer lifetime. C callbacks keep
|
|
/// the private `ZSTD_CCtx` and `SeqCollector` layouts out of this module.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_generateSequencesState {
|
|
callback_context: *mut c_void,
|
|
get_parameter: GenerateSequencesGetParameterFn,
|
|
allocate: GenerateSequencesAllocateFn,
|
|
free: GenerateSequencesFreeFn,
|
|
set_collector: GenerateSequencesSetCollectorFn,
|
|
compress2: GenerateSequencesCompress2Fn,
|
|
get_count: GenerateSequencesGetCountFn,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_generateSequencesState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_generateSequencesState, get_parameter) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_generateSequencesState, allocate) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_generateSequencesState, free) == 3 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_generateSequencesState, set_collector) == 4 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_generateSequencesState, compress2) == 5 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_generateSequencesState, get_count) == 6 * size_of::<usize>());
|
|
assert!(size_of::<ZSTD_rust_generateSequencesState>() == 7 * size_of::<usize>());
|
|
};
|
|
|
|
/// Own the validation, allocation, collection setup, compression call, and
|
|
/// cleanup ordering for the public sequence-generation API.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_generateSequences(
|
|
state: *const ZSTD_rust_generateSequencesState,
|
|
out_seqs: *mut ZSTD_Sequence,
|
|
out_seqs_size: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
let dst_capacity = crate::zstd_compress_api::ZSTD_compressBound(src_size);
|
|
let mut parameter = 0;
|
|
|
|
let result = unsafe {
|
|
(state.get_parameter)(
|
|
state.callback_context,
|
|
ZSTD_C_TARGET_C_BLOCK_SIZE,
|
|
&mut parameter,
|
|
)
|
|
};
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
if parameter != 0 {
|
|
return ERROR(ZstdErrorCode::ParameterUnsupported);
|
|
}
|
|
|
|
let result =
|
|
unsafe { (state.get_parameter)(state.callback_context, ZSTD_C_NB_WORKERS, &mut parameter) };
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
if parameter != 0 {
|
|
return ERROR(ZstdErrorCode::ParameterUnsupported);
|
|
}
|
|
|
|
let dst = unsafe { (state.allocate)(state.callback_context, dst_capacity) };
|
|
if dst.is_null() {
|
|
return ERROR(ZstdErrorCode::MemoryAllocation);
|
|
}
|
|
|
|
let result = unsafe { (state.set_collector)(state.callback_context, out_seqs, out_seqs_size) };
|
|
if ERR_isError(result) {
|
|
unsafe { (state.free)(state.callback_context, dst) };
|
|
return result;
|
|
}
|
|
|
|
let result =
|
|
unsafe { (state.compress2)(state.callback_context, dst, dst_capacity, src, src_size) };
|
|
unsafe { (state.free)(state.callback_context, dst) };
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
|
|
let sequence_count = unsafe { (state.get_count)(state.callback_context) };
|
|
debug_assert!(sequence_count <= crate::zstd_compress_api::ZSTD_sequenceBound(src_size));
|
|
sequence_count
|
|
}
|
|
|
|
#[inline]
|
|
unsafe fn compress_continue_block_body_with(
|
|
state: &ZSTD_rust_compressContinueBlockState,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
frame: c_uint,
|
|
is_first_block: *const c_int,
|
|
) -> usize {
|
|
if state.build_seq_store_state.is_null() || state.block_internal_state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let bss = unsafe { ZSTD_rust_buildSeqStore(state.build_seq_store_state, src, src_size) };
|
|
if ERR_isError(bss) {
|
|
return bss;
|
|
}
|
|
if !is_first_block.is_null() {
|
|
let mut block_state = unsafe { ptr::read(state.block_internal_state) };
|
|
block_state.is_first_block = unsafe { *is_first_block };
|
|
return unsafe {
|
|
ZSTD_rust_compressBlockInternalAfterBuild(
|
|
&block_state,
|
|
dst,
|
|
dst_capacity,
|
|
src,
|
|
src_size,
|
|
frame,
|
|
bss as c_int,
|
|
)
|
|
};
|
|
}
|
|
unsafe {
|
|
ZSTD_rust_compressBlockInternalAfterBuild(
|
|
state.block_internal_state,
|
|
dst,
|
|
dst_capacity,
|
|
src,
|
|
src_size,
|
|
frame,
|
|
bss as c_int,
|
|
)
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_compressContinueBlock(
|
|
context: *mut c_void,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
_last_frame_chunk: c_uint,
|
|
) -> usize {
|
|
if context.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*context.cast::<ZSTD_rust_compressContinueBlockState>() };
|
|
unsafe {
|
|
compress_continue_block_body_with(state, dst, dst_capacity, src, src_size, 0, ptr::null())
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_compressFrameChunkInternal(
|
|
state: *const ZSTD_rust_compressContinueBlockState,
|
|
is_first_block: *const c_int,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
_last_block: c_uint,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
unsafe {
|
|
compress_continue_block_body_with(
|
|
&*state,
|
|
dst,
|
|
dst_capacity,
|
|
src,
|
|
src_size,
|
|
1,
|
|
is_first_block,
|
|
)
|
|
}
|
|
}
|
|
|
|
unsafe fn compress_continue_update_window(
|
|
projection: *const ZSTD_rust_compressContinueWindowProjection,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
force_non_contiguous: c_int,
|
|
update_match_state: bool,
|
|
) -> Result<(), usize> {
|
|
if projection.is_null() {
|
|
return Err(ERROR(ZstdErrorCode::Generic));
|
|
}
|
|
let projection = unsafe { &*projection };
|
|
if projection.next_src.is_null()
|
|
|| projection.base.is_null()
|
|
|| projection.dict_base.is_null()
|
|
|| projection.dict_limit.is_null()
|
|
|| projection.low_limit.is_null()
|
|
{
|
|
return Err(ERROR(ZstdErrorCode::Generic));
|
|
}
|
|
if update_match_state
|
|
&& (projection.force_non_contiguous.is_null() || projection.next_to_update.is_null())
|
|
{
|
|
return Err(ERROR(ZstdErrorCode::Generic));
|
|
}
|
|
|
|
let mut window_state = ZSTD_rust_windowUpdateState {
|
|
nextSrc: unsafe { *projection.next_src },
|
|
base: unsafe { *projection.base },
|
|
dictBase: unsafe { *projection.dict_base },
|
|
dictLimit: unsafe { *projection.dict_limit },
|
|
lowLimit: unsafe { *projection.low_limit },
|
|
};
|
|
let contiguous =
|
|
unsafe { ZSTD_rust_windowUpdate(&mut window_state, src, src_size, force_non_contiguous) };
|
|
unsafe {
|
|
*projection.next_src = window_state.nextSrc;
|
|
*projection.base = window_state.base;
|
|
*projection.dict_base = window_state.dictBase;
|
|
*projection.dict_limit = window_state.dictLimit;
|
|
*projection.low_limit = window_state.lowLimit;
|
|
}
|
|
if update_match_state && contiguous == 0 {
|
|
unsafe {
|
|
*projection.force_non_contiguous = 0;
|
|
*projection.next_to_update = window_state.dictLimit;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
unsafe fn compress_continue_body_with(
|
|
state: &ZSTD_rust_compressContinueState,
|
|
mut dst: *mut c_void,
|
|
mut dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
frame: c_uint,
|
|
last_frame_chunk: c_uint,
|
|
) -> usize {
|
|
if state.stage.is_null() || state.consumed_src_size.is_null() || state.produced_c_size.is_null()
|
|
{
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
/* The deprecated block entry point performed this check before entering
|
|
* the old internal routine. Keep that ordering in the Rust orchestration. */
|
|
if state.check_block_size != 0 && src_size > state.block_size_max {
|
|
return ERROR(ZstdErrorCode::SrcSizeWrong);
|
|
}
|
|
|
|
if unsafe { *state.stage } == ZSTD_COMPRESSION_STAGE_CREATED {
|
|
return ERROR(ZstdErrorCode::StageWrong);
|
|
}
|
|
|
|
let mut frame_header_size = 0usize;
|
|
if frame != 0 && unsafe { *state.stage } == ZSTD_COMPRESSION_STAGE_INIT {
|
|
frame_header_size = unsafe {
|
|
ZSTD_rust_writeFrameHeader(
|
|
dst,
|
|
dst_capacity,
|
|
state.no_dict_id_flag,
|
|
state.checksum_flag,
|
|
state.content_size_flag,
|
|
state.format,
|
|
state.window_log,
|
|
state.pledged_src_size_plus_one.wrapping_sub(1),
|
|
state.dict_id,
|
|
)
|
|
};
|
|
if ERR_isError(frame_header_size) {
|
|
return frame_header_size;
|
|
}
|
|
if frame_header_size > dst_capacity {
|
|
return ERROR(ZstdErrorCode::DstSizeTooSmall);
|
|
}
|
|
unsafe {
|
|
*state.stage = ZSTD_COMPRESSION_STAGE_ONGOING;
|
|
}
|
|
dst = unsafe { dst.cast::<u8>().add(frame_header_size).cast() };
|
|
dst_capacity -= frame_header_size;
|
|
}
|
|
|
|
if src_size == 0 {
|
|
return frame_header_size;
|
|
}
|
|
|
|
if state.window_state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let match_window = unsafe { &*state.window_state };
|
|
if match_window.force_non_contiguous.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let force_non_contiguous = unsafe { *match_window.force_non_contiguous };
|
|
if let Err(result) = unsafe {
|
|
compress_continue_update_window(
|
|
state.window_state,
|
|
src,
|
|
src_size,
|
|
force_non_contiguous,
|
|
true,
|
|
)
|
|
} {
|
|
return result;
|
|
}
|
|
if state.ldm_enabled != 0 {
|
|
if let Err(result) = unsafe {
|
|
compress_continue_update_window(state.ldm_window_state, src, src_size, 0, false)
|
|
} {
|
|
return result;
|
|
}
|
|
}
|
|
if frame == 0 {
|
|
if state.overflow_state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let src_end = src.cast::<u8>().wrapping_add(src_size).cast();
|
|
unsafe { ZSTD_rust_overflowCorrectIfNeeded(state.overflow_state, src, src_end) };
|
|
}
|
|
|
|
let compressed_size = if frame != 0 {
|
|
if state.frame_chunk_state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
unsafe {
|
|
ZSTD_rust_compressFrameChunk(
|
|
state.frame_chunk_state,
|
|
dst,
|
|
dst_capacity,
|
|
src,
|
|
src_size,
|
|
last_frame_chunk,
|
|
)
|
|
}
|
|
} else {
|
|
unsafe {
|
|
(state.compress_block)(state.callback_context, dst, dst_capacity, src, src_size, 0)
|
|
}
|
|
};
|
|
if ERR_isError(compressed_size) {
|
|
return compressed_size;
|
|
}
|
|
|
|
let source_size_wrong = update_frame_progression(
|
|
unsafe { &mut *state.consumed_src_size },
|
|
unsafe { &mut *state.produced_c_size },
|
|
state.pledged_src_size_plus_one,
|
|
src_size,
|
|
compressed_size,
|
|
frame_header_size,
|
|
);
|
|
if state.pledged_src_size_plus_one != 0 && source_size_wrong {
|
|
return ERROR(ZstdErrorCode::SrcSizeWrong);
|
|
}
|
|
compressed_size.wrapping_add(frame_header_size)
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_compressContinue(
|
|
state: *const ZSTD_rust_compressContinueState,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
frame: c_uint,
|
|
last_frame_chunk: c_uint,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
unsafe {
|
|
compress_continue_body_with(
|
|
&*state,
|
|
dst,
|
|
dst_capacity,
|
|
src,
|
|
src_size,
|
|
frame,
|
|
last_frame_chunk,
|
|
)
|
|
}
|
|
}
|
|
|
|
type CompressEndTraceFn = unsafe extern "C" fn(*mut c_void, usize);
|
|
|
|
/// Explicit projection for the public end-of-frame orchestration.
|
|
///
|
|
/// Rust owns callback ordering, output offset/capacity accounting, and
|
|
/// pledged-size validation. The continue state is projected explicitly and
|
|
/// the opaque callback context retains only the private trace operation in C;
|
|
/// Rust serializes the epilogue from explicit frame and checksum projections.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_compressEndState {
|
|
callback_context: *mut c_void,
|
|
compress_continue_state: *const ZSTD_rust_compressContinueState,
|
|
trace: CompressEndTraceFn,
|
|
consumed_src_size: *const u64,
|
|
pledged_src_size_plus_one: u64,
|
|
content_size_flag: c_int,
|
|
stage: *mut c_int,
|
|
no_dict_id_flag: c_int,
|
|
checksum_flag: c_int,
|
|
format: c_int,
|
|
window_log: c_uint,
|
|
checksum_state: *const XXH64_state_t,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_compressEndState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_compressEndState, compress_continue_state) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_compressEndState, trace) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_compressEndState, consumed_src_size) == 3 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressEndState, pledged_src_size_plus_one) == 4 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressEndState, content_size_flag)
|
|
== 4 * size_of::<usize>() + size_of::<u64>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressEndState, stage) == 5 * size_of::<usize>() + size_of::<u64>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressEndState, no_dict_id_flag)
|
|
== 6 * size_of::<usize>() + size_of::<u64>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressEndState, checksum_flag)
|
|
== 6 * size_of::<usize>() + size_of::<u64>() + size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressEndState, format)
|
|
== 6 * size_of::<usize>() + size_of::<u64>() + 2 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressEndState, window_log)
|
|
== 6 * size_of::<usize>() + size_of::<u64>() + 3 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressEndState, checksum_state)
|
|
== 6 * size_of::<usize>()
|
|
+ size_of::<u64>()
|
|
+ 3 * size_of::<c_int>()
|
|
+ size_of::<c_uint>()
|
|
);
|
|
assert!(
|
|
size_of::<ZSTD_rust_compressEndState>() == if size_of::<usize>() == 8 { 80 } else { 48 }
|
|
);
|
|
};
|
|
|
|
unsafe fn compress_end_body_with(
|
|
state: &ZSTD_rust_compressEndState,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
) -> usize {
|
|
if state.compress_continue_state.is_null()
|
|
|| state.consumed_src_size.is_null()
|
|
|| state.stage.is_null()
|
|
|| state.checksum_state.is_null()
|
|
{
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
let c_size = unsafe {
|
|
ZSTD_rust_compressContinue(
|
|
state.compress_continue_state,
|
|
dst,
|
|
dst_capacity,
|
|
src,
|
|
src_size,
|
|
1,
|
|
1,
|
|
)
|
|
};
|
|
if ERR_isError(c_size) {
|
|
return c_size;
|
|
}
|
|
|
|
let checksum = if state.checksum_flag != 0 {
|
|
unsafe { XXH64_digest(state.checksum_state) as u32 }
|
|
} else {
|
|
0
|
|
};
|
|
let end_result = unsafe {
|
|
ZSTD_rust_writeEpilogue(
|
|
dst.cast::<u8>().add(c_size).cast(),
|
|
dst_capacity.wrapping_sub(c_size),
|
|
state.stage,
|
|
state.no_dict_id_flag,
|
|
state.checksum_flag,
|
|
state.content_size_flag,
|
|
state.format,
|
|
state.window_log,
|
|
checksum,
|
|
)
|
|
};
|
|
if ERR_isError(end_result) {
|
|
return end_result;
|
|
}
|
|
|
|
debug_assert!(!(state.content_size_flag != 0 && state.pledged_src_size_plus_one == 0));
|
|
if state.pledged_src_size_plus_one != 0
|
|
&& state.pledged_src_size_plus_one != unsafe { (*state.consumed_src_size).wrapping_add(1) }
|
|
{
|
|
return ERROR(ZstdErrorCode::SrcSizeWrong);
|
|
}
|
|
|
|
unsafe { (state.trace)(state.callback_context, end_result) };
|
|
c_size.wrapping_add(end_result)
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_compressEnd(
|
|
state: *const ZSTD_rust_compressEndState,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
unsafe { compress_end_body_with(&*state, dst, dst_capacity, src, src_size) }
|
|
}
|
|
|
|
type Compress2ResetFn = unsafe extern "C" fn(*mut c_void) -> usize;
|
|
type Compress2SetBufferModesFn = unsafe extern "C" fn(*mut c_void, c_int, c_int);
|
|
type Compress2StreamEndFn = unsafe extern "C" fn(
|
|
*mut c_void,
|
|
*mut c_void,
|
|
usize,
|
|
*mut usize,
|
|
*const c_void,
|
|
usize,
|
|
*mut usize,
|
|
) -> usize;
|
|
|
|
/// Explicit projection for the C fallback behind `ZSTD_compress2`.
|
|
///
|
|
/// Rust owns the reset/mode-switch/stream-call ordering and result policy.
|
|
/// The opaque callback context remains in C, where callbacks retain access to
|
|
/// the private `ZSTD_CCtx` layout and the simple-arguments stream adapter.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_compress2State {
|
|
callback_context: *mut c_void,
|
|
reset_session: Compress2ResetFn,
|
|
set_buffer_modes: Compress2SetBufferModesFn,
|
|
compress_stream_end: Compress2StreamEndFn,
|
|
original_in_buffer_mode: c_int,
|
|
original_out_buffer_mode: c_int,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_compress2State, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_compress2State, reset_session) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_compress2State, set_buffer_modes) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_compress2State, compress_stream_end) == 3 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compress2State, original_in_buffer_mode) == 4 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compress2State, original_out_buffer_mode)
|
|
== 4 * size_of::<usize>() + size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
size_of::<ZSTD_rust_compress2State>() == 4 * size_of::<usize>() + 2 * size_of::<c_int>()
|
|
);
|
|
};
|
|
|
|
unsafe fn compress2_body_with(
|
|
state: &ZSTD_rust_compress2State,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
) -> usize {
|
|
let reset_result = unsafe { (state.reset_session)(state.callback_context) };
|
|
unsafe {
|
|
(state.set_buffer_modes)(state.callback_context, ZSTD_BM_STABLE, ZSTD_BM_STABLE);
|
|
}
|
|
|
|
let mut output_pos = 0;
|
|
let mut input_pos = 0;
|
|
let result = if ERR_isError(reset_result) {
|
|
reset_result
|
|
} else {
|
|
unsafe {
|
|
(state.compress_stream_end)(
|
|
state.callback_context,
|
|
dst,
|
|
dst_capacity,
|
|
&mut output_pos,
|
|
src,
|
|
src_size,
|
|
&mut input_pos,
|
|
)
|
|
}
|
|
};
|
|
|
|
unsafe {
|
|
(state.set_buffer_modes)(
|
|
state.callback_context,
|
|
state.original_in_buffer_mode,
|
|
state.original_out_buffer_mode,
|
|
);
|
|
}
|
|
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
if result != 0 {
|
|
debug_assert_eq!(output_pos, dst_capacity);
|
|
return ERROR(ZstdErrorCode::DstSizeTooSmall);
|
|
}
|
|
debug_assert_eq!(input_pos, src_size);
|
|
output_pos
|
|
}
|
|
|
|
/// Drive the `ZSTD_compress2_c()` fallback without crossing the C context
|
|
/// layout.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_compress2(
|
|
state: *const ZSTD_rust_compress2State,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
unsafe { compress2_body_with(&*state, dst, dst_capacity, src, src_size) }
|
|
}
|
|
|
|
/// Explicit projection for `ZSTD_CCtx_setParametersUsingCCtxParams`.
|
|
///
|
|
/// Rust owns the stage and dictionary policy while the C-owned parameter
|
|
/// objects cross the ABI as pointers to the shared layout mirror.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_setParametersUsingCCtxParamsState {
|
|
requested_params: *mut ZSTD_CCtx_params,
|
|
source_params: *const ZSTD_CCtx_params,
|
|
stream_stage: c_int,
|
|
cdict: *const c_void,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(
|
|
offset_of!(
|
|
ZSTD_rust_setParametersUsingCCtxParamsState,
|
|
requested_params
|
|
) == 0
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_setParametersUsingCCtxParamsState, source_params)
|
|
== size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_setParametersUsingCCtxParamsState, stream_stage)
|
|
== 2 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_setParametersUsingCCtxParamsState, cdict) == 3 * size_of::<usize>()
|
|
);
|
|
assert!(size_of::<ZSTD_rust_setParametersUsingCCtxParamsState>() == 4 * size_of::<usize>());
|
|
};
|
|
|
|
/// Apply stored parameters through the C-owned context projection.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_setParametersUsingCCtxParams(
|
|
state: *const ZSTD_rust_setParametersUsingCCtxParamsState,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
if state.stream_stage != ZSTD_CSTREAM_STAGE_INIT {
|
|
return ERROR(ZstdErrorCode::StageWrong);
|
|
}
|
|
if !state.cdict.is_null() {
|
|
return ERROR(ZstdErrorCode::StageWrong);
|
|
}
|
|
unsafe {
|
|
*state.requested_params = *state.source_params;
|
|
}
|
|
0
|
|
}
|
|
|
|
/// Explicit projection for `ZSTD_CCtx_setParameter`.
|
|
///
|
|
/// Rust owns the stage authorization and accepted-parameter policy while the
|
|
/// C-owned context exposes only the fields whose side effects remain private.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_setParameterState {
|
|
requested_params: *mut ZSTD_CCtx_params,
|
|
stream_stage: c_int,
|
|
c_params_changed: *mut c_int,
|
|
static_size: usize,
|
|
rust_simple_compress2_max_block_size_set: *mut c_uint,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_setParameterState, requested_params) == 0);
|
|
assert!(offset_of!(ZSTD_rust_setParameterState, stream_stage) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_setParameterState, c_params_changed) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_setParameterState, static_size) == 3 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(
|
|
ZSTD_rust_setParameterState,
|
|
rust_simple_compress2_max_block_size_set
|
|
) == 4 * size_of::<usize>()
|
|
);
|
|
assert!(size_of::<ZSTD_rust_setParameterState>() == 5 * size_of::<usize>());
|
|
};
|
|
|
|
#[inline]
|
|
fn set_parameter_is_supported(param: c_int) -> bool {
|
|
matches!(
|
|
param,
|
|
ZSTD_C_COMPRESSION_LEVEL
|
|
| ZSTD_C_WINDOW_LOG
|
|
| ZSTD_C_HASH_LOG
|
|
| ZSTD_C_CHAIN_LOG
|
|
| ZSTD_C_SEARCH_LOG
|
|
| ZSTD_C_MIN_MATCH
|
|
| ZSTD_C_TARGET_LENGTH
|
|
| ZSTD_C_STRATEGY
|
|
| ZSTD_C_LDM_HASH_RATE_LOG
|
|
| ZSTD_C_FORMAT
|
|
| ZSTD_C_CONTENT_SIZE_FLAG
|
|
| ZSTD_C_CHECKSUM_FLAG
|
|
| ZSTD_C_DICT_ID_FLAG
|
|
| ZSTD_C_FORCE_MAX_WINDOW
|
|
| ZSTD_C_FORCE_ATTACH_DICT
|
|
| ZSTD_C_LITERAL_COMPRESSION_MODE
|
|
| ZSTD_C_JOB_SIZE
|
|
| ZSTD_C_OVERLAP_LOG
|
|
| ZSTD_C_RSYNCABLE
|
|
| ZSTD_C_ENABLE_DEDICATED_DICT_SEARCH
|
|
| ZSTD_C_ENABLE_LDM
|
|
| ZSTD_C_LDM_HASH_LOG
|
|
| ZSTD_C_LDM_MIN_MATCH
|
|
| ZSTD_C_LDM_BUCKET_SIZE_LOG
|
|
| ZSTD_C_TARGET_C_BLOCK_SIZE
|
|
| ZSTD_C_SRC_SIZE_HINT
|
|
| ZSTD_C_STABLE_IN_BUFFER
|
|
| ZSTD_C_STABLE_OUT_BUFFER
|
|
| ZSTD_C_BLOCK_DELIMITERS
|
|
| ZSTD_C_VALIDATE_SEQUENCES
|
|
| ZSTD_C_SPLIT_AFTER_SEQUENCES
|
|
| ZSTD_C_BLOCK_SPLITTER_LEVEL
|
|
| ZSTD_C_USE_ROW_MATCH_FINDER
|
|
| ZSTD_C_DETERMINISTIC_REF_PREFIX
|
|
| ZSTD_C_PREFETCH_CDICT_TABLES
|
|
| ZSTD_C_ENABLE_SEQ_PRODUCER_FALLBACK
|
|
| ZSTD_C_MAX_BLOCK_SIZE
|
|
| ZSTD_C_REPCODE_RESOLUTION
|
|
)
|
|
}
|
|
|
|
/// Apply one context parameter through the C-owned parameter object.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_setParameter(
|
|
state: *const ZSTD_rust_setParameterState,
|
|
param: c_int,
|
|
value: c_int,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
|
|
if state.stream_stage != ZSTD_CSTREAM_STAGE_INIT {
|
|
if ZSTD_rust_isUpdateAuthorized(param) == 0 {
|
|
return ERROR(ZstdErrorCode::StageWrong);
|
|
}
|
|
unsafe {
|
|
*state.c_params_changed = 1;
|
|
}
|
|
}
|
|
if param == ZSTD_C_NB_WORKERS {
|
|
if value != 0 && state.static_size != 0 {
|
|
return ERROR(ZstdErrorCode::ParameterUnsupported);
|
|
}
|
|
} else if !set_parameter_is_supported(param) {
|
|
return ERROR(ZstdErrorCode::ParameterUnsupported);
|
|
}
|
|
|
|
let result = unsafe { ZSTD_CCtxParams_setParameter(state.requested_params, param, value) };
|
|
if !ERR_isError(result) && param == ZSTD_C_MAX_BLOCK_SIZE {
|
|
unsafe {
|
|
*state.rust_simple_compress2_max_block_size_set = 1;
|
|
}
|
|
}
|
|
result
|
|
}
|
|
|
|
/// Explicit projection for `ZSTD_CCtx_refThreadPool`.
|
|
///
|
|
/// Rust owns the init-stage check and assignment policy while the pool slot
|
|
/// itself remains storage owned by the C context. The pool value is opaque to
|
|
/// Rust because its private `POOL_ctx_s` layout is not part of this bridge.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_refThreadPoolState {
|
|
pool: *mut *mut c_void,
|
|
requested_pool: *mut c_void,
|
|
stream_stage: c_int,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_refThreadPoolState, pool) == 0);
|
|
assert!(offset_of!(ZSTD_rust_refThreadPoolState, requested_pool) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_refThreadPoolState, stream_stage) == 2 * size_of::<usize>());
|
|
assert!(size_of::<ZSTD_rust_refThreadPoolState>() == 3 * size_of::<usize>());
|
|
};
|
|
|
|
/// Attach an opaque thread pool through the C-owned context slot.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_refThreadPool(
|
|
state: *const ZSTD_rust_refThreadPoolState,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
if state.stream_stage != ZSTD_CSTREAM_STAGE_INIT {
|
|
return ERROR(ZstdErrorCode::StageWrong);
|
|
}
|
|
unsafe {
|
|
*state.pool = state.requested_pool;
|
|
}
|
|
0
|
|
}
|
|
|
|
type InitCCtxCallbackFn = unsafe extern "C" fn(*mut c_void);
|
|
type InitCCtxSetCustomMemFn = unsafe extern "C" fn(*mut c_void, *const c_void);
|
|
type InitCCtxResetFn = unsafe extern "C" fn(*mut c_void) -> usize;
|
|
|
|
/// Explicit projection for private heap-CCtx initialization.
|
|
///
|
|
/// Rust owns the initialization ordering while C retains the private context
|
|
/// layout, allocator publication, CPU-feature query, and reset operation
|
|
/// behind callbacks.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_initCCtxState {
|
|
callback_context: *mut c_void,
|
|
custom_mem: *const c_void,
|
|
zero: InitCCtxCallbackFn,
|
|
set_custom_mem: InitCCtxSetCustomMemFn,
|
|
set_bmi2: InitCCtxCallbackFn,
|
|
reset: InitCCtxResetFn,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(size_of::<InitCCtxCallbackFn>() == size_of::<usize>());
|
|
assert!(size_of::<InitCCtxSetCustomMemFn>() == size_of::<usize>());
|
|
assert!(size_of::<InitCCtxResetFn>() == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initCCtxState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_initCCtxState, custom_mem) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initCCtxState, zero) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initCCtxState, set_custom_mem) == 3 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initCCtxState, set_bmi2) == 4 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initCCtxState, reset) == 5 * size_of::<usize>());
|
|
assert!(size_of::<ZSTD_rust_initCCtxState>() == 6 * size_of::<usize>());
|
|
};
|
|
|
|
/// Initialize a heap-owned C compression context in the original order.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_initCCtx(state: *const ZSTD_rust_initCCtxState) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
if state.callback_context.is_null() || state.custom_mem.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
unsafe {
|
|
(state.zero)(state.callback_context);
|
|
(state.set_custom_mem)(state.callback_context, state.custom_mem);
|
|
(state.set_bmi2)(state.callback_context);
|
|
let result = (state.reset)(state.callback_context);
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
}
|
|
0
|
|
}
|
|
|
|
type CreateCCtxValidateCustomMemFn = unsafe extern "C" fn(*mut c_void) -> c_int;
|
|
type CreateCCtxAllocateFn = unsafe extern "C" fn(*mut c_void, usize) -> *mut c_void;
|
|
type CreateCCtxInitFn = unsafe extern "C" fn(*mut c_void, *mut c_void);
|
|
|
|
/// Explicit projection for the public `ZSTD_createCCtx_advanced` wrapper.
|
|
///
|
|
/// Rust owns custom-memory validation, allocation failure, and
|
|
/// allocate-before-init ordering. C retains private `ZSTD_CCtx` initialization
|
|
/// behind the final callback.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_createCCtxState {
|
|
callback_context: *mut c_void,
|
|
cctx_size: usize,
|
|
validate_custom_mem: CreateCCtxValidateCustomMemFn,
|
|
allocate: CreateCCtxAllocateFn,
|
|
init: CreateCCtxInitFn,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_createCCtxState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_createCCtxState, cctx_size) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_createCCtxState, validate_custom_mem) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_createCCtxState, allocate) == 3 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_createCCtxState, init) == 4 * size_of::<usize>());
|
|
assert!(size_of::<ZSTD_rust_createCCtxState>() == size_of::<[usize; 5]>());
|
|
};
|
|
|
|
/// Allocate and initialize a C-owned compression context.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_createCCtx(
|
|
state: *const ZSTD_rust_createCCtxState,
|
|
) -> *mut c_void {
|
|
if state.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
let state = unsafe { &*state };
|
|
if state.callback_context.is_null()
|
|
|| unsafe { (state.validate_custom_mem)(state.callback_context) == 0 }
|
|
{
|
|
return ptr::null_mut();
|
|
}
|
|
let cctx = unsafe { (state.allocate)(state.callback_context, state.cctx_size) };
|
|
if cctx.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
unsafe { (state.init)(state.callback_context, cctx) };
|
|
cctx
|
|
}
|
|
|
|
type CreateCDictAdvancedWrapperInitParamsFn = unsafe extern "C" fn(
|
|
*mut c_void,
|
|
*mut c_void,
|
|
*const ZSTD_compressionParameters,
|
|
*const c_void,
|
|
usize,
|
|
c_int,
|
|
);
|
|
type CreateCDictAdvancedWrapperCreateFn = unsafe extern "C" fn(
|
|
*mut c_void,
|
|
*const c_void,
|
|
usize,
|
|
c_int,
|
|
c_int,
|
|
*const c_void,
|
|
*const c_void,
|
|
) -> *mut c_void;
|
|
|
|
/// Explicit projection for the public heap-CDict wrapper.
|
|
///
|
|
/// Rust owns the wrapper's preparation-to-construction ordering and forwards
|
|
/// the dictionary scalars. C retains the private `ZSTD_CCtx_params` setup and
|
|
/// the advanced-CDict implementation behind callbacks; allocation and
|
|
/// workspace ownership stay in the existing advanced2 path.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_createCDictAdvancedWrapperState {
|
|
callback_context: *mut c_void,
|
|
cctx_params: *mut c_void,
|
|
c_params: *const ZSTD_compressionParameters,
|
|
custom_mem: *const c_void,
|
|
init_params: CreateCDictAdvancedWrapperInitParamsFn,
|
|
create: CreateCDictAdvancedWrapperCreateFn,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_createCDictAdvancedWrapperState, callback_context) == 0);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_createCDictAdvancedWrapperState, cctx_params) == size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_createCDictAdvancedWrapperState, c_params) == 2 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_createCDictAdvancedWrapperState, custom_mem) == 3 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_createCDictAdvancedWrapperState, init_params)
|
|
== 4 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_createCDictAdvancedWrapperState, create) == 5 * size_of::<usize>()
|
|
);
|
|
assert!(size_of::<ZSTD_rust_createCDictAdvancedWrapperState>() == size_of::<[usize; 6]>());
|
|
};
|
|
|
|
/// Prepare a heap-CDict parameter object, then enter the existing advanced
|
|
/// construction path in the original callback order.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_createCDictAdvancedWrapper(
|
|
state: *const ZSTD_rust_createCDictAdvancedWrapperState,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
dict_load_method: c_int,
|
|
dict_content_type: c_int,
|
|
) -> *mut c_void {
|
|
if state.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
let state = unsafe { &*state };
|
|
if state.cctx_params.is_null() || state.c_params.is_null() || state.custom_mem.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
|
|
unsafe {
|
|
(state.init_params)(
|
|
state.callback_context,
|
|
state.cctx_params,
|
|
state.c_params,
|
|
state.custom_mem,
|
|
dict_size,
|
|
dict_content_type,
|
|
);
|
|
(state.create)(
|
|
state.callback_context,
|
|
dict,
|
|
dict_size,
|
|
dict_load_method,
|
|
dict_content_type,
|
|
state.cctx_params,
|
|
state.custom_mem,
|
|
)
|
|
}
|
|
}
|
|
|
|
type InitStaticCCtxCreateWorkspaceFn = unsafe extern "C" fn(*mut c_void);
|
|
type InitStaticCCtxReserveObjectFn = unsafe extern "C" fn(*mut c_void) -> *mut c_void;
|
|
type InitStaticCCtxZeroFn = unsafe extern "C" fn(*mut c_void, *mut c_void);
|
|
type InitStaticCCtxMoveWorkspaceFn = unsafe extern "C" fn(*mut c_void, *mut c_void);
|
|
type InitStaticCCtxCheckAvailableFn = unsafe extern "C" fn(*mut c_void) -> c_int;
|
|
type InitStaticCCtxReserveBlockStateFn = unsafe extern "C" fn(*mut c_void, c_int) -> *mut c_void;
|
|
type InitStaticCCtxReserveTmpWorkspaceFn = unsafe extern "C" fn(*mut c_void) -> *mut c_void;
|
|
type InitStaticCCtxSetBmi2Fn = unsafe extern "C" fn(*mut c_void);
|
|
const ZSTD_STATIC_WORKSPACE_ALIGNMENT: usize = 8;
|
|
const INIT_STATIC_CCTX_PREV_CBLOCK: c_int = 0;
|
|
const INIT_STATIC_CCTX_NEXT_CBLOCK: c_int = 1;
|
|
|
|
/// Explicit projection for the public `ZSTD_initStaticCCtx` wrapper.
|
|
///
|
|
/// Rust owns workspace pointer/alignment validation, minimum-size validation,
|
|
/// construction ordering, and callback failure policy. C retains the private
|
|
/// static-workspace and CCtx layout operations behind narrow callbacks.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_initStaticCCtxState {
|
|
callback_context: *mut c_void,
|
|
workspace: *mut c_void,
|
|
workspace_size: usize,
|
|
cctx_size: usize,
|
|
create_workspace: InitStaticCCtxCreateWorkspaceFn,
|
|
reserve_object: InitStaticCCtxReserveObjectFn,
|
|
zero: InitStaticCCtxZeroFn,
|
|
move_workspace: InitStaticCCtxMoveWorkspaceFn,
|
|
check_available: InitStaticCCtxCheckAvailableFn,
|
|
reserve_block_state: InitStaticCCtxReserveBlockStateFn,
|
|
reserve_tmp_workspace: InitStaticCCtxReserveTmpWorkspaceFn,
|
|
set_bmi2: InitStaticCCtxSetBmi2Fn,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(size_of::<InitStaticCCtxCreateWorkspaceFn>() == size_of::<usize>());
|
|
assert!(size_of::<InitStaticCCtxReserveObjectFn>() == size_of::<usize>());
|
|
assert!(size_of::<InitStaticCCtxZeroFn>() == size_of::<usize>());
|
|
assert!(size_of::<InitStaticCCtxMoveWorkspaceFn>() == size_of::<usize>());
|
|
assert!(size_of::<InitStaticCCtxCheckAvailableFn>() == size_of::<usize>());
|
|
assert!(size_of::<InitStaticCCtxReserveBlockStateFn>() == size_of::<usize>());
|
|
assert!(size_of::<InitStaticCCtxReserveTmpWorkspaceFn>() == size_of::<usize>());
|
|
assert!(size_of::<InitStaticCCtxSetBmi2Fn>() == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initStaticCCtxState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_initStaticCCtxState, workspace) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initStaticCCtxState, workspace_size) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initStaticCCtxState, cctx_size) == 3 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initStaticCCtxState, create_workspace) == 4 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initStaticCCtxState, reserve_object) == 5 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initStaticCCtxState, zero) == 6 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initStaticCCtxState, move_workspace) == 7 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initStaticCCtxState, check_available) == 8 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_initStaticCCtxState, reserve_block_state) == 9 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_initStaticCCtxState, reserve_tmp_workspace) == 10 * size_of::<usize>()
|
|
);
|
|
assert!(offset_of!(ZSTD_rust_initStaticCCtxState, set_bmi2) == 11 * size_of::<usize>());
|
|
assert!(size_of::<ZSTD_rust_initStaticCCtxState>() == size_of::<[usize; 12]>());
|
|
};
|
|
|
|
/// Validate and sequence static CCtx construction before entering C leaves.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_initStaticCCtx(
|
|
state: *const ZSTD_rust_initStaticCCtxState,
|
|
) -> *mut c_void {
|
|
if state.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
let state = unsafe { &*state };
|
|
if state.callback_context.is_null()
|
|
|| state.workspace.is_null()
|
|
|| (state.workspace as usize) & (ZSTD_STATIC_WORKSPACE_ALIGNMENT - 1) != 0
|
|
|| state.workspace_size <= state.cctx_size
|
|
{
|
|
return ptr::null_mut();
|
|
}
|
|
unsafe {
|
|
(state.create_workspace)(state.callback_context);
|
|
let cctx = (state.reserve_object)(state.callback_context);
|
|
if cctx.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
(state.zero)(state.callback_context, cctx);
|
|
(state.move_workspace)(state.callback_context, cctx);
|
|
if (state.check_available)(state.callback_context) == 0 {
|
|
return ptr::null_mut();
|
|
}
|
|
if (state.reserve_block_state)(state.callback_context, INIT_STATIC_CCTX_PREV_CBLOCK)
|
|
.is_null()
|
|
{
|
|
return ptr::null_mut();
|
|
}
|
|
if (state.reserve_block_state)(state.callback_context, INIT_STATIC_CCTX_NEXT_CBLOCK)
|
|
.is_null()
|
|
{
|
|
return ptr::null_mut();
|
|
}
|
|
if (state.reserve_tmp_workspace)(state.callback_context).is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
(state.set_bmi2)(state.callback_context);
|
|
cctx
|
|
}
|
|
}
|
|
|
|
type FreeCCtxContentFn = unsafe extern "C" fn(*mut c_void);
|
|
type FreeCCtxObjectFn = unsafe extern "C" fn(*mut c_void);
|
|
|
|
/// Explicit projection for the public `ZSTD_freeCCtx` wrapper.
|
|
///
|
|
/// Rust owns the null/static/embedded-object policy and callback ordering.
|
|
/// C retains the private content teardown and custom allocator callbacks.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_freeCCtxState {
|
|
callback_context: *mut c_void,
|
|
static_size: usize,
|
|
cctx_in_workspace: c_int,
|
|
free_content: FreeCCtxContentFn,
|
|
free_object: FreeCCtxObjectFn,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(size_of::<FreeCCtxContentFn>() == size_of::<usize>());
|
|
assert!(size_of::<FreeCCtxObjectFn>() == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_freeCCtxState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_freeCCtxState, static_size) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_freeCCtxState, cctx_in_workspace) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_freeCCtxState, free_content) == 3 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_freeCCtxState, free_object) == 4 * size_of::<usize>());
|
|
assert!(size_of::<ZSTD_rust_freeCCtxState>() == 5 * size_of::<usize>());
|
|
};
|
|
|
|
/// Free the C-owned context content and, when applicable, its outer object.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_freeCCtx(state: *const ZSTD_rust_freeCCtxState) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
if state.callback_context.is_null() {
|
|
return 0;
|
|
}
|
|
if state.static_size != 0 {
|
|
return ERROR(ZstdErrorCode::MemoryAllocation);
|
|
}
|
|
unsafe {
|
|
(state.free_content)(state.callback_context);
|
|
if state.cctx_in_workspace == 0 {
|
|
(state.free_object)(state.callback_context);
|
|
}
|
|
}
|
|
0
|
|
}
|
|
|
|
type ClearAllDictsCallbackFn = unsafe extern "C" fn(*mut c_void);
|
|
|
|
/// Compose the dictionary teardown operations while leaving the private
|
|
/// ZSTD_CCtx fields behind C callbacks.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_clearAllDictsState {
|
|
callback_context: *mut c_void,
|
|
free_local_dict_buffer: ClearAllDictsCallbackFn,
|
|
free_local_cdict: ClearAllDictsCallbackFn,
|
|
clear_local_dict: ClearAllDictsCallbackFn,
|
|
clear_prefix_dict: ClearAllDictsCallbackFn,
|
|
clear_cdict: ClearAllDictsCallbackFn,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(size_of::<ClearAllDictsCallbackFn>() == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_clearAllDictsState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_clearAllDictsState, free_local_dict_buffer) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_clearAllDictsState, free_local_cdict) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_clearAllDictsState, clear_local_dict) == 3 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_clearAllDictsState, clear_prefix_dict) == 4 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_clearAllDictsState, clear_cdict) == 5 * size_of::<usize>());
|
|
assert!(size_of::<ZSTD_rust_clearAllDictsState>() == 6 * size_of::<usize>());
|
|
};
|
|
|
|
/// Preserve the original local-dictionary, prefix-dictionary, and attached
|
|
/// CDict teardown order while keeping their private storage C-owned.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_clearAllDicts(state: *const ZSTD_rust_clearAllDictsState) {
|
|
if state.is_null() {
|
|
return;
|
|
}
|
|
let state = unsafe { &*state };
|
|
if state.callback_context.is_null() {
|
|
return;
|
|
}
|
|
unsafe {
|
|
(state.free_local_dict_buffer)(state.callback_context);
|
|
(state.free_local_cdict)(state.callback_context);
|
|
(state.clear_local_dict)(state.callback_context);
|
|
(state.clear_prefix_dict)(state.callback_context);
|
|
(state.clear_cdict)(state.callback_context);
|
|
}
|
|
}
|
|
|
|
type ResetCCtxClearAllDictsFn = unsafe extern "C" fn(*mut c_void);
|
|
type ResetCCtxResetParamsFn = unsafe extern "C" fn(*mut c_void) -> usize;
|
|
|
|
/// Explicit projection for the public `ZSTD_CCtx_reset` wrapper.
|
|
///
|
|
/// Rust owns the reset-directive policy and ordering. The callback context
|
|
/// remains in C, where the dictionary and parameter reset operations retain
|
|
/// access to the private `ZSTD_CCtx` layout.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_resetCCtxState {
|
|
callback_context: *mut c_void,
|
|
rust_simple_compress2_completed: *mut c_uint,
|
|
stream_stage: *mut c_int,
|
|
pledged_src_size_plus_one: *mut u64,
|
|
rust_simple_compress2_max_block_size_set: *mut c_uint,
|
|
clear_all_dicts: ResetCCtxClearAllDictsFn,
|
|
reset_params: ResetCCtxResetParamsFn,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(size_of::<ResetCCtxClearAllDictsFn>() == size_of::<usize>());
|
|
assert!(size_of::<ResetCCtxResetParamsFn>() == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_resetCCtxState, callback_context) == 0);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxState, rust_simple_compress2_completed) == size_of::<usize>()
|
|
);
|
|
assert!(offset_of!(ZSTD_rust_resetCCtxState, stream_stage) == 2 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxState, pledged_src_size_plus_one) == 3 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(
|
|
ZSTD_rust_resetCCtxState,
|
|
rust_simple_compress2_max_block_size_set
|
|
) == 4 * size_of::<usize>()
|
|
);
|
|
assert!(offset_of!(ZSTD_rust_resetCCtxState, clear_all_dicts) == 5 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_resetCCtxState, reset_params) == 6 * size_of::<usize>());
|
|
assert!(size_of::<ZSTD_rust_resetCCtxState>() == 7 * size_of::<usize>());
|
|
};
|
|
|
|
/// Drive the public `ZSTD_CCtx_reset` policy through C-owned callbacks.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_resetCCtx(
|
|
state: *const ZSTD_rust_resetCCtxState,
|
|
reset: c_int,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
|
|
unsafe {
|
|
*state.rust_simple_compress2_completed = 0;
|
|
}
|
|
if reset == ZSTD_RESET_SESSION_ONLY || reset == ZSTD_RESET_SESSION_AND_PARAMETERS {
|
|
unsafe {
|
|
*state.stream_stage = ZSTD_CSTREAM_STAGE_INIT;
|
|
*state.pledged_src_size_plus_one = 0;
|
|
}
|
|
}
|
|
if reset == ZSTD_RESET_PARAMETERS || reset == ZSTD_RESET_SESSION_AND_PARAMETERS {
|
|
if unsafe { *state.stream_stage } != ZSTD_CSTREAM_STAGE_INIT {
|
|
return ERROR(ZstdErrorCode::StageWrong);
|
|
}
|
|
unsafe { (state.clear_all_dicts)(state.callback_context) };
|
|
unsafe {
|
|
*state.rust_simple_compress2_max_block_size_set = 0;
|
|
}
|
|
return unsafe { (state.reset_params)(state.callback_context) };
|
|
}
|
|
0
|
|
}
|
|
|
|
type CopyCCtxInternalFn = unsafe extern "C" fn(
|
|
*mut c_void,
|
|
*const c_void,
|
|
*const ZSTD_frameParameters,
|
|
u64,
|
|
c_int,
|
|
) -> usize;
|
|
|
|
/// Explicit projection for the public `ZSTD_copyCCtx` wrapper.
|
|
///
|
|
/// Rust owns zero-to-unknown pledged-size normalization and frame-parameter
|
|
/// construction. C retains the private table and workspace copy operation.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_copyCCtxState {
|
|
callback_context: *mut c_void,
|
|
src_cctx: *const c_void,
|
|
f_params: *const ZSTD_frameParameters,
|
|
pledged_src_size: *const u64,
|
|
zbuff: *const c_int,
|
|
copy_internal: Option<CopyCCtxInternalFn>,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(size_of::<CopyCCtxInternalFn>() == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_copyCCtxState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_copyCCtxState, src_cctx) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_copyCCtxState, f_params) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_copyCCtxState, pledged_src_size) == 3 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_copyCCtxState, zbuff) == 4 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_copyCCtxState, copy_internal) == 5 * size_of::<usize>());
|
|
assert!(size_of::<ZSTD_rust_copyCCtxState>() == size_of::<[usize; 6]>());
|
|
};
|
|
|
|
/// Normalize the public copy request before invoking the private C operation.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_copyCCtx(state: *const ZSTD_rust_copyCCtxState) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
let Some(copy_internal) = state.copy_internal else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
if state.callback_context.is_null()
|
|
|| state.src_cctx.is_null()
|
|
|| state.f_params.is_null()
|
|
|| state.pledged_src_size.is_null()
|
|
|| state.zbuff.is_null()
|
|
{
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
let pledged_src_size = unsafe { *state.pledged_src_size };
|
|
let pledged_src_size = if pledged_src_size == 0 {
|
|
ZSTD_CONTENTSIZE_UNKNOWN
|
|
} else {
|
|
pledged_src_size
|
|
};
|
|
let mut f_params = unsafe { *state.f_params };
|
|
f_params.contentSizeFlag = c_int::from(pledged_src_size != ZSTD_CONTENTSIZE_UNKNOWN);
|
|
unsafe {
|
|
copy_internal(
|
|
state.callback_context,
|
|
state.src_cctx,
|
|
&f_params,
|
|
pledged_src_size,
|
|
*state.zbuff,
|
|
)
|
|
}
|
|
}
|
|
|
|
type CopyCCtxResetFn = unsafe extern "C" fn(
|
|
*mut c_void,
|
|
*const c_void,
|
|
*const ZSTD_frameParameters,
|
|
u64,
|
|
c_int,
|
|
) -> usize;
|
|
type CopyCCtxMarkTablesFn = unsafe extern "C" fn(*mut c_void);
|
|
|
|
#[repr(C)]
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
struct ZSTD_rust_copyWindowState {
|
|
next_src: *const c_void,
|
|
base: *const c_void,
|
|
dict_base: *const c_void,
|
|
dict_limit: c_uint,
|
|
low_limit: c_uint,
|
|
nb_overflow_corrections: c_uint,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_copyWindowState, next_src) == 0);
|
|
assert!(offset_of!(ZSTD_rust_copyWindowState, base) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_copyWindowState, dict_base) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_copyWindowState, dict_limit) == 3 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_copyWindowState, low_limit)
|
|
== 3 * size_of::<usize>() + size_of::<c_uint>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_copyWindowState, nb_overflow_corrections)
|
|
== 3 * size_of::<usize>() + 2 * size_of::<c_uint>()
|
|
);
|
|
assert!(
|
|
size_of::<ZSTD_rust_copyWindowState>()
|
|
== (3 * size_of::<usize>() + 3 * size_of::<c_uint>()).div_ceil(size_of::<usize>())
|
|
* size_of::<usize>()
|
|
);
|
|
};
|
|
|
|
const _: () = {
|
|
assert!(size_of::<ZSTD_customMem>() == 3 * size_of::<usize>());
|
|
};
|
|
|
|
/// Projection for the private `ZSTD_copyCCtx_internal` operation.
|
|
///
|
|
/// Rust owns the stage/error branch and the order of the reset, workspace,
|
|
/// table, window, dictionary, and block-state operations. C retains access to
|
|
/// the private `ZSTD_CCtx` layout behind callbacks.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_copyCCtxInternalState {
|
|
callback_context: *mut c_void,
|
|
src_cctx: *const c_void,
|
|
f_params: *const ZSTD_frameParameters,
|
|
pledged_src_size: u64,
|
|
source_stage: *const c_int,
|
|
destination_custom_mem: *mut c_void,
|
|
source_custom_mem: *const c_void,
|
|
reset: Option<CopyCCtxResetFn>,
|
|
mark_tables_dirty: Option<CopyCCtxMarkTablesFn>,
|
|
mark_tables_clean: Option<CopyCCtxMarkTablesFn>,
|
|
destination_hash_table: *mut *mut c_uint,
|
|
source_hash_table: *const c_uint,
|
|
source_hash_log: *const c_uint,
|
|
destination_chain_table: *mut *mut c_uint,
|
|
source_chain_table: *const c_uint,
|
|
source_chain_log: *const c_uint,
|
|
destination_hash_table3: *mut *mut c_uint,
|
|
source_hash_table3: *const c_uint,
|
|
source_hash_log3: *const c_uint,
|
|
source_strategy: *const c_int,
|
|
source_use_row_match_finder: *const c_int,
|
|
destination_window: *mut c_void,
|
|
source_window: *const c_void,
|
|
destination_next_to_update: *mut c_uint,
|
|
source_next_to_update: *const c_uint,
|
|
destination_loaded_dict_end: *mut c_uint,
|
|
source_loaded_dict_end: *const c_uint,
|
|
destination_dict_id: *mut c_uint,
|
|
source_dict_id: *const c_uint,
|
|
destination_dict_content_size: *mut usize,
|
|
source_dict_content_size: *const usize,
|
|
destination_block_state: *mut *mut ZSTD_compressedBlockState_t,
|
|
source_block_state: *const ZSTD_compressedBlockState_t,
|
|
zbuff: c_int,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(size_of::<CopyCCtxResetFn>() == size_of::<usize>());
|
|
assert!(size_of::<CopyCCtxMarkTablesFn>() == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_copyCCtxInternalState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_copyCCtxInternalState, src_cctx) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_copyCCtxInternalState, f_params) == 2 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_copyCCtxInternalState, pledged_src_size) == 3 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_copyCCtxInternalState, source_stage)
|
|
== 3 * size_of::<usize>() + size_of::<u64>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_copyCCtxInternalState, zbuff)
|
|
== 3 * size_of::<usize>() + size_of::<u64>() + 29 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
size_of::<ZSTD_rust_copyCCtxInternalState>()
|
|
== (offset_of!(ZSTD_rust_copyCCtxInternalState, zbuff) + size_of::<c_int>())
|
|
.div_ceil(size_of::<usize>())
|
|
* size_of::<usize>()
|
|
);
|
|
};
|
|
|
|
/// Run the private context-copy operation through C-owned layout callbacks
|
|
/// and copy the compressed-block state directly in Rust.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_copyCCtxInternal(
|
|
state: *const ZSTD_rust_copyCCtxInternalState,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
let (Some(reset), Some(mark_tables_dirty), Some(mark_tables_clean)) = (
|
|
state.reset,
|
|
state.mark_tables_dirty,
|
|
state.mark_tables_clean,
|
|
) else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
if state.callback_context.is_null()
|
|
|| state.src_cctx.is_null()
|
|
|| state.f_params.is_null()
|
|
|| state.source_stage.is_null()
|
|
|| state.destination_custom_mem.is_null()
|
|
|| state.source_custom_mem.is_null()
|
|
|| state.destination_hash_table.is_null()
|
|
|| state.destination_chain_table.is_null()
|
|
|| state.destination_hash_table3.is_null()
|
|
|| state.source_hash_log.is_null()
|
|
|| state.source_chain_log.is_null()
|
|
|| state.source_hash_log3.is_null()
|
|
|| state.source_strategy.is_null()
|
|
|| state.source_use_row_match_finder.is_null()
|
|
|| state.destination_window.is_null()
|
|
|| state.source_window.is_null()
|
|
|| state.destination_next_to_update.is_null()
|
|
|| state.source_next_to_update.is_null()
|
|
|| state.destination_loaded_dict_end.is_null()
|
|
|| state.source_loaded_dict_end.is_null()
|
|
|| state.destination_dict_id.is_null()
|
|
|| state.source_dict_id.is_null()
|
|
|| state.destination_dict_content_size.is_null()
|
|
|| state.source_dict_content_size.is_null()
|
|
|| state.destination_block_state.is_null()
|
|
|| state.source_block_state.is_null()
|
|
{
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
if unsafe { *state.source_stage } != ZSTD_COMPRESSION_STAGE_INIT {
|
|
return ERROR(ZstdErrorCode::StageWrong);
|
|
}
|
|
|
|
let source_hash_log = unsafe { *state.source_hash_log };
|
|
let source_chain_log = unsafe { *state.source_chain_log };
|
|
let source_hash_log3 = unsafe { *state.source_hash_log3 };
|
|
let source_strategy = unsafe { *state.source_strategy };
|
|
let source_use_row_match_finder = unsafe { *state.source_use_row_match_finder };
|
|
let Some(hash_table_size) = 1usize.checked_shl(source_hash_log) else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let chain_table_size = if ZSTD_rust_params_allocateChainTable(
|
|
source_strategy,
|
|
source_use_row_match_finder,
|
|
0,
|
|
) != 0
|
|
{
|
|
let Some(size) = 1usize.checked_shl(source_chain_log) else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
size
|
|
} else {
|
|
0
|
|
};
|
|
let hash_table3_size = if source_hash_log3 != 0 {
|
|
let Some(size) = 1usize.checked_shl(source_hash_log3) else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
size
|
|
} else {
|
|
0
|
|
};
|
|
|
|
unsafe {
|
|
ptr::copy(
|
|
state.source_custom_mem.cast::<ZSTD_customMem>(),
|
|
state.destination_custom_mem.cast::<ZSTD_customMem>(),
|
|
1,
|
|
);
|
|
let reset_error = reset(
|
|
state.callback_context,
|
|
state.src_cctx,
|
|
state.f_params,
|
|
state.pledged_src_size,
|
|
state.zbuff,
|
|
);
|
|
if ERR_isError(reset_error) {
|
|
return reset_error;
|
|
}
|
|
let destination_hash_table = *state.destination_hash_table;
|
|
let destination_chain_table = *state.destination_chain_table;
|
|
let destination_hash_table3 = *state.destination_hash_table3;
|
|
if (hash_table_size != 0
|
|
&& (destination_hash_table.is_null() || state.source_hash_table.is_null()))
|
|
|| (chain_table_size != 0
|
|
&& (destination_chain_table.is_null() || state.source_chain_table.is_null()))
|
|
|| (hash_table3_size != 0
|
|
&& (destination_hash_table3.is_null() || state.source_hash_table3.is_null()))
|
|
{
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
mark_tables_dirty(state.callback_context);
|
|
if hash_table_size != 0 {
|
|
ptr::copy_nonoverlapping(
|
|
state.source_hash_table,
|
|
destination_hash_table,
|
|
hash_table_size,
|
|
);
|
|
}
|
|
if chain_table_size != 0 {
|
|
ptr::copy_nonoverlapping(
|
|
state.source_chain_table,
|
|
destination_chain_table,
|
|
chain_table_size,
|
|
);
|
|
}
|
|
if hash_table3_size != 0 {
|
|
ptr::copy_nonoverlapping(
|
|
state.source_hash_table3,
|
|
destination_hash_table3,
|
|
hash_table3_size,
|
|
);
|
|
}
|
|
mark_tables_clean(state.callback_context);
|
|
ptr::copy(
|
|
state.source_window.cast::<ZSTD_rust_copyWindowState>(),
|
|
state.destination_window.cast::<ZSTD_rust_copyWindowState>(),
|
|
1,
|
|
);
|
|
*state.destination_next_to_update = *state.source_next_to_update;
|
|
*state.destination_loaded_dict_end = *state.source_loaded_dict_end;
|
|
*state.destination_dict_id = *state.source_dict_id;
|
|
*state.destination_dict_content_size = *state.source_dict_content_size;
|
|
let destination_block_state = *state.destination_block_state;
|
|
if destination_block_state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
ptr::copy_nonoverlapping(state.source_block_state, destination_block_state, 1);
|
|
}
|
|
0
|
|
}
|
|
|
|
type CompressAdvancedInitParamsFn = unsafe extern "C" fn(*mut c_void, *const ZSTD_parameters);
|
|
type CompressAdvancedInternalFn = unsafe extern "C" fn(
|
|
*mut c_void,
|
|
*mut c_void,
|
|
usize,
|
|
*const c_void,
|
|
usize,
|
|
*const c_void,
|
|
usize,
|
|
) -> usize;
|
|
|
|
/// Explicit projection for the public `ZSTD_compress_advanced` wrapper.
|
|
///
|
|
/// Rust owns parameter validation and the init-then-compress order. C retains
|
|
/// the private `simpleApiParams` initialization and advanced compression call.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_compressAdvancedState {
|
|
callback_context: *mut c_void,
|
|
init_params: Option<CompressAdvancedInitParamsFn>,
|
|
compress_internal: Option<CompressAdvancedInternalFn>,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(size_of::<CompressAdvancedInitParamsFn>() == size_of::<usize>());
|
|
assert!(size_of::<CompressAdvancedInternalFn>() == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_compressAdvancedState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_compressAdvancedState, init_params) == size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressAdvancedState, compress_internal) == 2 * size_of::<usize>()
|
|
);
|
|
assert!(size_of::<ZSTD_rust_compressAdvancedState>() == size_of::<[usize; 3]>());
|
|
};
|
|
|
|
/// Validate parameters and invoke the C-owned advanced compression operation.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_compressAdvanced(
|
|
state: *const ZSTD_rust_compressAdvancedState,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
params: *const ZSTD_parameters,
|
|
) -> usize {
|
|
if state.is_null() || params.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
let Some(init_params) = state.init_params else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(compress_internal) = state.compress_internal else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
if state.callback_context.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
let params = unsafe { &*params };
|
|
let check_result = ZSTD_rust_params_checkCParams(params.cParams);
|
|
if ERR_isError(check_result) {
|
|
return check_result;
|
|
}
|
|
unsafe { init_params(state.callback_context, params) };
|
|
unsafe {
|
|
compress_internal(
|
|
state.callback_context,
|
|
dst,
|
|
dst_capacity,
|
|
src,
|
|
src_size,
|
|
dict,
|
|
dict_size,
|
|
)
|
|
}
|
|
}
|
|
|
|
type CompressBeginAdvancedInitParamsFn = unsafe extern "C" fn(*mut c_void, *const ZSTD_parameters);
|
|
type CompressBeginAdvancedBeginFn =
|
|
unsafe extern "C" fn(*mut c_void, *const c_void, usize, *const c_void, u64) -> usize;
|
|
|
|
/// Explicit projection for the public `ZSTD_compressBegin_advanced` wrapper.
|
|
///
|
|
/// Rust owns public parameter validation and init-then-begin ordering. C
|
|
/// retains private `ZSTD_CCtx_params` initialization and the final begin
|
|
/// operation behind callbacks.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_compressBeginAdvancedState {
|
|
cctx: *mut c_void,
|
|
cctx_params: *mut c_void,
|
|
init_params: CompressBeginAdvancedInitParamsFn,
|
|
begin: CompressBeginAdvancedBeginFn,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_compressBeginAdvancedState, cctx) == 0);
|
|
assert!(offset_of!(ZSTD_rust_compressBeginAdvancedState, cctx_params) == size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressBeginAdvancedState, init_params) == 2 * size_of::<usize>()
|
|
);
|
|
assert!(offset_of!(ZSTD_rust_compressBeginAdvancedState, begin) == 3 * size_of::<usize>());
|
|
assert!(size_of::<ZSTD_rust_compressBeginAdvancedState>() == size_of::<[usize; 4]>());
|
|
};
|
|
|
|
/// Validate parameters and begin a frame through the C-owned operation.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_compressBeginAdvanced(
|
|
state: *const ZSTD_rust_compressBeginAdvancedState,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
params: *const ZSTD_parameters,
|
|
pledged_src_size: u64,
|
|
) -> usize {
|
|
if state.is_null() || params.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
if state.cctx.is_null() || state.cctx_params.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let params = unsafe { &*params };
|
|
let check_result = ZSTD_rust_params_checkCParams(params.cParams);
|
|
if ERR_isError(check_result) {
|
|
return check_result;
|
|
}
|
|
unsafe {
|
|
(state.init_params)(state.cctx_params, params);
|
|
(state.begin)(
|
|
state.cctx,
|
|
dict,
|
|
dict_size,
|
|
state.cctx_params,
|
|
pledged_src_size,
|
|
)
|
|
}
|
|
}
|
|
|
|
type CompressUsingDictInitParamsFn =
|
|
unsafe extern "C" fn(*mut c_void, *const ZSTD_parameters, c_int);
|
|
|
|
/// Explicit projection for the public `ZSTD_compress_usingDict` wrapper.
|
|
///
|
|
/// Rust owns compression-parameter selection, dictionary-presence handling,
|
|
/// and the default-level normalization. C retains the private
|
|
/// `ZSTD_CCtx_params` initialization and advanced compression operation.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_compressUsingDictState {
|
|
callback_context: *mut c_void,
|
|
exclusion_mask: *const c_uint,
|
|
init_params: Option<CompressUsingDictInitParamsFn>,
|
|
compress_internal: Option<CompressAdvancedInternalFn>,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(size_of::<CompressUsingDictInitParamsFn>() == size_of::<usize>());
|
|
assert!(size_of::<CompressAdvancedInternalFn>() == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_compressUsingDictState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_compressUsingDictState, exclusion_mask) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_compressUsingDictState, init_params) == 2 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressUsingDictState, compress_internal) == 3 * size_of::<usize>()
|
|
);
|
|
assert!(size_of::<ZSTD_rust_compressUsingDictState>() == size_of::<[usize; 4]>());
|
|
};
|
|
|
|
/// Select parameters and invoke the C-owned dictionary compression operation.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_compressUsingDict(
|
|
state: *const ZSTD_rust_compressUsingDictState,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
compression_level: c_int,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
let Some(init_params) = state.init_params else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(compress_internal) = state.compress_internal else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
if state.callback_context.is_null() || state.exclusion_mask.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
let dict_size_for_params = if dict.is_null() { 0 } else { dict_size };
|
|
let params = ZSTD_rust_params_getParamsInternal(
|
|
compression_level,
|
|
src_size as u64,
|
|
dict_size_for_params,
|
|
ZSTD_RUST_CPM_NO_ATTACH_DICT,
|
|
unsafe { *state.exclusion_mask },
|
|
);
|
|
let init_level = if compression_level == 0 {
|
|
ZSTD_rust_params_defaultCLevel()
|
|
} else {
|
|
compression_level
|
|
};
|
|
unsafe { init_params(state.callback_context, ¶ms, init_level) };
|
|
unsafe {
|
|
compress_internal(
|
|
state.callback_context,
|
|
dst,
|
|
dst_capacity,
|
|
src,
|
|
src_size,
|
|
dict,
|
|
dict_size,
|
|
)
|
|
}
|
|
}
|
|
|
|
type ResetCStreamResetFn = unsafe extern "C" fn(*mut c_void) -> usize;
|
|
type ResetCStreamSetPledgedSrcSizeFn = unsafe extern "C" fn(*mut c_void, u64) -> usize;
|
|
|
|
/// Explicit projection for the public `ZSTD_resetCStream` wrapper.
|
|
///
|
|
/// Rust owns the zero-to-unknown conversion, callback ordering, and error
|
|
/// propagation. The opaque callback context stays in C, where the callbacks
|
|
/// retain access to the private `ZSTD_CCtx` layout.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_resetCStreamState {
|
|
callback_context: *mut c_void,
|
|
reset_session: ResetCStreamResetFn,
|
|
set_pledged_src_size: ResetCStreamSetPledgedSrcSizeFn,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(size_of::<ResetCStreamResetFn>() == size_of::<usize>());
|
|
assert!(size_of::<ResetCStreamSetPledgedSrcSizeFn>() == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_resetCStreamState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_resetCStreamState, reset_session) == size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCStreamState, set_pledged_src_size) == 2 * size_of::<usize>()
|
|
);
|
|
assert!(size_of::<ZSTD_rust_resetCStreamState>() == 3 * size_of::<usize>());
|
|
};
|
|
|
|
/// Drive the public `ZSTD_resetCStream` policy through C-owned callbacks.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_resetCStream(
|
|
state: *const ZSTD_rust_resetCStreamState,
|
|
pss: u64,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
let pledged_src_size = if pss == 0 {
|
|
ZSTD_CONTENTSIZE_UNKNOWN
|
|
} else {
|
|
pss
|
|
};
|
|
|
|
let reset_result = unsafe { (state.reset_session)(state.callback_context) };
|
|
if ERR_isError(reset_result) {
|
|
return reset_result;
|
|
}
|
|
let pledged_size_result =
|
|
unsafe { (state.set_pledged_src_size)(state.callback_context, pledged_src_size) };
|
|
if ERR_isError(pledged_size_result) {
|
|
return pledged_size_result;
|
|
}
|
|
0
|
|
}
|
|
|
|
type InitCStreamUsingCDictAdvancedResetFn = unsafe extern "C" fn(*mut c_void) -> usize;
|
|
type InitCStreamUsingCDictAdvancedSetPledgedSrcSizeFn =
|
|
unsafe extern "C" fn(*mut c_void, u64) -> usize;
|
|
type InitCStreamUsingCDictAdvancedSetFrameParamsFn =
|
|
unsafe extern "C" fn(*mut c_void, c_uint, c_uint, c_uint);
|
|
type InitCStreamUsingCDictAdvancedRefCDictFn =
|
|
unsafe extern "C" fn(*mut c_void, *const c_void) -> usize;
|
|
|
|
/// Explicit projection for `ZSTD_initCStream_usingCDict_advanced`.
|
|
///
|
|
/// Rust owns the public wrapper's callback order and error propagation while
|
|
/// C retains the private context and dictionary/parameter mutations.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_initCStreamUsingCDictAdvancedState {
|
|
callback_context: *mut c_void,
|
|
reset_session: InitCStreamUsingCDictAdvancedResetFn,
|
|
set_pledged_src_size: InitCStreamUsingCDictAdvancedSetPledgedSrcSizeFn,
|
|
set_frame_params: InitCStreamUsingCDictAdvancedSetFrameParamsFn,
|
|
ref_cdict: InitCStreamUsingCDictAdvancedRefCDictFn,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(size_of::<InitCStreamUsingCDictAdvancedResetFn>() == size_of::<usize>());
|
|
assert!(size_of::<InitCStreamUsingCDictAdvancedSetPledgedSrcSizeFn>() == size_of::<usize>());
|
|
assert!(size_of::<InitCStreamUsingCDictAdvancedSetFrameParamsFn>() == size_of::<usize>());
|
|
assert!(size_of::<InitCStreamUsingCDictAdvancedRefCDictFn>() == size_of::<usize>());
|
|
assert!(
|
|
offset_of!(
|
|
ZSTD_rust_initCStreamUsingCDictAdvancedState,
|
|
callback_context
|
|
) == 0
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_initCStreamUsingCDictAdvancedState, reset_session)
|
|
== size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(
|
|
ZSTD_rust_initCStreamUsingCDictAdvancedState,
|
|
set_pledged_src_size
|
|
) == 2 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(
|
|
ZSTD_rust_initCStreamUsingCDictAdvancedState,
|
|
set_frame_params
|
|
) == 3 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_initCStreamUsingCDictAdvancedState, ref_cdict)
|
|
== 4 * size_of::<usize>()
|
|
);
|
|
assert!(size_of::<ZSTD_rust_initCStreamUsingCDictAdvancedState>() == 5 * size_of::<usize>());
|
|
};
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_initCStreamUsingCDictAdvanced(
|
|
state: *const ZSTD_rust_initCStreamUsingCDictAdvancedState,
|
|
pledged_src_size: u64,
|
|
content_size_flag: c_uint,
|
|
checksum_flag: c_uint,
|
|
no_dict_id_flag: c_uint,
|
|
cdict: *const c_void,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
|
|
let result = unsafe { (state.reset_session)(state.callback_context) };
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
let result = unsafe { (state.set_pledged_src_size)(state.callback_context, pledged_src_size) };
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
unsafe {
|
|
(state.set_frame_params)(
|
|
state.callback_context,
|
|
content_size_flag,
|
|
checksum_flag,
|
|
no_dict_id_flag,
|
|
)
|
|
};
|
|
let result = unsafe { (state.ref_cdict)(state.callback_context, cdict) };
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
0
|
|
}
|
|
|
|
/// Explicit projection for `ZSTD_initCStream_usingCDict`.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_initCStreamUsingCDictState {
|
|
callback_context: *mut c_void,
|
|
reset_session: InitCStreamUsingCDictAdvancedResetFn,
|
|
ref_cdict: InitCStreamUsingCDictAdvancedRefCDictFn,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_initCStreamUsingCDictState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_initCStreamUsingCDictState, reset_session) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initCStreamUsingCDictState, ref_cdict) == 2 * size_of::<usize>());
|
|
assert!(size_of::<ZSTD_rust_initCStreamUsingCDictState>() == 3 * size_of::<usize>());
|
|
};
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_initCStreamUsingCDict(
|
|
state: *const ZSTD_rust_initCStreamUsingCDictState,
|
|
cdict: *const c_void,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
|
|
let result = unsafe { (state.reset_session)(state.callback_context) };
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
let result = unsafe { (state.ref_cdict)(state.callback_context, cdict) };
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
0
|
|
}
|
|
|
|
type InitCStreamSrcSizeSetLevelFn = unsafe extern "C" fn(*mut c_void, c_int) -> usize;
|
|
|
|
/// Explicit projection for `ZSTD_initCStream_srcSize`.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_initCStreamSrcSizeState {
|
|
callback_context: *mut c_void,
|
|
reset_session: InitCStreamUsingCDictAdvancedResetFn,
|
|
ref_cdict: InitCStreamUsingCDictAdvancedRefCDictFn,
|
|
set_level: InitCStreamSrcSizeSetLevelFn,
|
|
set_pledged_src_size: InitCStreamUsingCDictAdvancedSetPledgedSrcSizeFn,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_initCStreamSrcSizeState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_initCStreamSrcSizeState, reset_session) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initCStreamSrcSizeState, ref_cdict) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initCStreamSrcSizeState, set_level) == 3 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_initCStreamSrcSizeState, set_pledged_src_size)
|
|
== 4 * size_of::<usize>()
|
|
);
|
|
assert!(size_of::<ZSTD_rust_initCStreamSrcSizeState>() == 5 * size_of::<usize>());
|
|
};
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_initCStreamSrcSize(
|
|
state: *const ZSTD_rust_initCStreamSrcSizeState,
|
|
pss: u64,
|
|
compression_level: c_int,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
let pledged_src_size = if pss == 0 {
|
|
ZSTD_CONTENTSIZE_UNKNOWN
|
|
} else {
|
|
pss
|
|
};
|
|
|
|
let result = unsafe { (state.reset_session)(state.callback_context) };
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
let result = unsafe { (state.ref_cdict)(state.callback_context, ptr::null()) };
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
let result = unsafe { (state.set_level)(state.callback_context, compression_level) };
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
let result = unsafe { (state.set_pledged_src_size)(state.callback_context, pledged_src_size) };
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
0
|
|
}
|
|
|
|
/// Explicit projection for `ZSTD_initCStream`.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_initCStreamState {
|
|
callback_context: *mut c_void,
|
|
reset_session: InitCStreamUsingCDictAdvancedResetFn,
|
|
ref_cdict: InitCStreamUsingCDictAdvancedRefCDictFn,
|
|
set_level: InitCStreamSrcSizeSetLevelFn,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_initCStreamState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_initCStreamState, reset_session) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initCStreamState, ref_cdict) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initCStreamState, set_level) == 3 * size_of::<usize>());
|
|
assert!(size_of::<ZSTD_rust_initCStreamState>() == 4 * size_of::<usize>());
|
|
};
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_initCStream(
|
|
state: *const ZSTD_rust_initCStreamState,
|
|
compression_level: c_int,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
|
|
let result = unsafe { (state.reset_session)(state.callback_context) };
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
let result = unsafe { (state.ref_cdict)(state.callback_context, ptr::null()) };
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
let result = unsafe { (state.set_level)(state.callback_context, compression_level) };
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
0
|
|
}
|
|
|
|
type InitCStreamUsingDictLoadDictionaryFn =
|
|
unsafe extern "C" fn(*mut c_void, *const c_void, usize) -> usize;
|
|
|
|
/// Explicit projection for `ZSTD_initCStream_usingDict`.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_initCStreamUsingDictState {
|
|
callback_context: *mut c_void,
|
|
reset_session: InitCStreamUsingCDictAdvancedResetFn,
|
|
set_level: InitCStreamSrcSizeSetLevelFn,
|
|
load_dictionary: InitCStreamUsingDictLoadDictionaryFn,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_initCStreamUsingDictState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_initCStreamUsingDictState, reset_session) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initCStreamUsingDictState, set_level) == 2 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_initCStreamUsingDictState, load_dictionary) == 3 * size_of::<usize>()
|
|
);
|
|
assert!(size_of::<ZSTD_rust_initCStreamUsingDictState>() == 4 * size_of::<usize>());
|
|
};
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_initCStreamUsingDict(
|
|
state: *const ZSTD_rust_initCStreamUsingDictState,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
compression_level: c_int,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
|
|
let result = unsafe { (state.reset_session)(state.callback_context) };
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
let result = unsafe { (state.set_level)(state.callback_context, compression_level) };
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
let result = unsafe { (state.load_dictionary)(state.callback_context, dict, dict_size) };
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
0
|
|
}
|
|
|
|
type InitCStreamAdvancedCheckCParamsFn =
|
|
unsafe extern "C" fn(*mut c_void, ZSTD_compressionParameters) -> usize;
|
|
type InitCStreamAdvancedSetZstdParamsFn = unsafe extern "C" fn(*mut c_void, *const ZSTD_parameters);
|
|
|
|
/// Explicit projection for `ZSTD_initCStream_advanced`.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_initCStreamAdvancedState {
|
|
callback_context: *mut c_void,
|
|
reset_session: InitCStreamUsingCDictAdvancedResetFn,
|
|
set_pledged_src_size: InitCStreamUsingCDictAdvancedSetPledgedSrcSizeFn,
|
|
check_c_params: InitCStreamAdvancedCheckCParamsFn,
|
|
set_zstd_params: InitCStreamAdvancedSetZstdParamsFn,
|
|
load_dictionary: InitCStreamUsingDictLoadDictionaryFn,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_initCStreamAdvancedState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_initCStreamAdvancedState, reset_session) == size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_initCStreamAdvancedState, set_pledged_src_size)
|
|
== 2 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_initCStreamAdvancedState, check_c_params) == 3 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_initCStreamAdvancedState, set_zstd_params) == 4 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_initCStreamAdvancedState, load_dictionary) == 5 * size_of::<usize>()
|
|
);
|
|
assert!(size_of::<ZSTD_rust_initCStreamAdvancedState>() == 6 * size_of::<usize>());
|
|
};
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_initCStreamAdvanced(
|
|
state: *const ZSTD_rust_initCStreamAdvancedState,
|
|
params: *const ZSTD_parameters,
|
|
pss: u64,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
) -> usize {
|
|
if state.is_null() || params.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
let params = unsafe { &*params };
|
|
let pledged_src_size = if pss == 0 && params.fParams.contentSizeFlag == 0 {
|
|
ZSTD_CONTENTSIZE_UNKNOWN
|
|
} else {
|
|
pss
|
|
};
|
|
|
|
let result = unsafe { (state.reset_session)(state.callback_context) };
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
let result = unsafe { (state.set_pledged_src_size)(state.callback_context, pledged_src_size) };
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
let result = unsafe { (state.check_c_params)(state.callback_context, params.cParams) };
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
unsafe { (state.set_zstd_params)(state.callback_context, params) };
|
|
let result = unsafe { (state.load_dictionary)(state.callback_context, dict, dict_size) };
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
0
|
|
}
|
|
|
|
type SetCParamsCheckCParamsFn =
|
|
unsafe extern "C" fn(*mut c_void, ZSTD_compressionParameters) -> usize;
|
|
type SetCParamsSetParameterFn = unsafe extern "C" fn(*mut c_void, c_int, c_int) -> usize;
|
|
|
|
/// Explicit projection for `ZSTD_CCtx_setCParams`.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_setCParamsState {
|
|
callback_context: *mut c_void,
|
|
check_c_params: SetCParamsCheckCParamsFn,
|
|
set_parameter: SetCParamsSetParameterFn,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_setCParamsState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_setCParamsState, check_c_params) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_setCParamsState, set_parameter) == 2 * size_of::<usize>());
|
|
assert!(size_of::<ZSTD_rust_setCParamsState>() == 3 * size_of::<usize>());
|
|
};
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_setCParams(
|
|
state: *const ZSTD_rust_setCParamsState,
|
|
cparams: ZSTD_compressionParameters,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
let result = unsafe { (state.check_c_params)(state.callback_context, cparams) };
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
|
|
let parameters = [
|
|
(ZSTD_C_WINDOW_LOG, cparams.windowLog as c_int),
|
|
(ZSTD_C_CHAIN_LOG, cparams.chainLog as c_int),
|
|
(ZSTD_C_HASH_LOG, cparams.hashLog as c_int),
|
|
(ZSTD_C_SEARCH_LOG, cparams.searchLog as c_int),
|
|
(ZSTD_C_MIN_MATCH, cparams.minMatch as c_int),
|
|
(ZSTD_C_TARGET_LENGTH, cparams.targetLength as c_int),
|
|
(ZSTD_C_STRATEGY, cparams.strategy),
|
|
];
|
|
for (param, value) in parameters {
|
|
let result = unsafe { (state.set_parameter)(state.callback_context, param, value) };
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
}
|
|
0
|
|
}
|
|
|
|
type SetFParamsSetParameterFn = unsafe extern "C" fn(*mut c_void, c_int, c_int) -> usize;
|
|
|
|
/// Explicit projection for `ZSTD_CCtx_setFParams`.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_setFParamsState {
|
|
callback_context: *mut c_void,
|
|
set_parameter: SetFParamsSetParameterFn,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_setFParamsState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_setFParamsState, set_parameter) == size_of::<usize>());
|
|
assert!(size_of::<ZSTD_rust_setFParamsState>() == 2 * size_of::<usize>());
|
|
};
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_setFParams(
|
|
state: *const ZSTD_rust_setFParamsState,
|
|
fparams: ZSTD_frameParameters,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
let parameters = [
|
|
(
|
|
ZSTD_C_CONTENT_SIZE_FLAG,
|
|
(fparams.contentSizeFlag != 0) as c_int,
|
|
),
|
|
(ZSTD_C_CHECKSUM_FLAG, (fparams.checksumFlag != 0) as c_int),
|
|
(ZSTD_C_DICT_ID_FLAG, (fparams.noDictIDFlag == 0) as c_int),
|
|
];
|
|
for (param, value) in parameters {
|
|
let result = unsafe { (state.set_parameter)(state.callback_context, param, value) };
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
}
|
|
0
|
|
}
|
|
|
|
type SetParamsCheckCParamsFn =
|
|
unsafe extern "C" fn(*mut c_void, ZSTD_compressionParameters) -> usize;
|
|
type SetParamsSetFParamsFn = unsafe extern "C" fn(*mut c_void, ZSTD_frameParameters) -> usize;
|
|
type SetParamsSetCParamsFn = unsafe extern "C" fn(*mut c_void, ZSTD_compressionParameters) -> usize;
|
|
|
|
/// Explicit projection for `ZSTD_CCtx_setParams`.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_setParamsState {
|
|
callback_context: *mut c_void,
|
|
check_c_params: SetParamsCheckCParamsFn,
|
|
set_f_params: SetParamsSetFParamsFn,
|
|
set_c_params: SetParamsSetCParamsFn,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_setParamsState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_setParamsState, check_c_params) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_setParamsState, set_f_params) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_setParamsState, set_c_params) == 3 * size_of::<usize>());
|
|
assert!(size_of::<ZSTD_rust_setParamsState>() == 4 * size_of::<usize>());
|
|
};
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_setParams(
|
|
state: *const ZSTD_rust_setParamsState,
|
|
params: ZSTD_parameters,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
|
|
let result = unsafe { (state.check_c_params)(state.callback_context, params.cParams) };
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
|
|
let result = unsafe { (state.set_f_params)(state.callback_context, params.fParams) };
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
|
|
let result = unsafe { (state.set_c_params)(state.callback_context, params.cParams) };
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
0
|
|
}
|
|
|
|
type CompressStreamResetFn = unsafe extern "C" fn(*mut c_void) -> usize;
|
|
|
|
/// Explicit projection of the single-threaded `ZSTD_compressStream_generic`
|
|
/// state machine.
|
|
///
|
|
/// Rust owns buffering, output-drain policy, directive handling, and stream
|
|
/// stage transitions. Continue/end state projections retain the migrated
|
|
/// frame construction path; the opaque callback context remains only for
|
|
/// session reset.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_compressStreamState {
|
|
callback_context: *mut c_void,
|
|
in_buffer_mode: c_int,
|
|
out_buffer_mode: c_int,
|
|
stream_stage: *mut c_int,
|
|
block_size_max: usize,
|
|
stable_in_not_consumed: *mut usize,
|
|
in_buff: *mut c_void,
|
|
in_buff_size: usize,
|
|
in_to_compress: *mut usize,
|
|
in_buff_pos: *mut usize,
|
|
in_buff_target: *mut usize,
|
|
out_buff: *mut c_void,
|
|
out_buff_size: usize,
|
|
out_buff_content_size: *mut usize,
|
|
out_buff_flushed_size: *mut usize,
|
|
frame_ended: *mut c_uint,
|
|
compress_continue_state: *const ZSTD_rust_compressContinueState,
|
|
compress_end_state: *const ZSTD_rust_compressEndState,
|
|
reset_session: CompressStreamResetFn,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_compressStreamState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_compressStreamState, in_buffer_mode) == size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressStreamState, out_buffer_mode)
|
|
== size_of::<usize>() + size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressStreamState, stream_stage)
|
|
== size_of::<usize>() + 2 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressStreamState, block_size_max)
|
|
== 2 * size_of::<usize>() + 2 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressStreamState, stable_in_not_consumed)
|
|
== 3 * size_of::<usize>() + 2 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressStreamState, in_buff)
|
|
== 4 * size_of::<usize>() + 2 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressStreamState, in_buff_size)
|
|
== 5 * size_of::<usize>() + 2 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressStreamState, in_to_compress)
|
|
== 5 * size_of::<usize>() + 2 * size_of::<c_int>() + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressStreamState, in_buff_pos)
|
|
== 6 * size_of::<usize>() + 2 * size_of::<c_int>() + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressStreamState, in_buff_target)
|
|
== 7 * size_of::<usize>() + 2 * size_of::<c_int>() + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressStreamState, out_buff)
|
|
== 9 * size_of::<usize>() + 2 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressStreamState, out_buff_size)
|
|
== 9 * size_of::<usize>() + 2 * size_of::<c_int>() + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressStreamState, out_buff_content_size)
|
|
== 9 * size_of::<usize>() + 2 * size_of::<c_int>() + 2 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressStreamState, out_buff_flushed_size)
|
|
== 10 * size_of::<usize>() + 2 * size_of::<c_int>() + 2 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressStreamState, frame_ended)
|
|
== 11 * size_of::<usize>() + 2 * size_of::<c_int>() + 2 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressStreamState, compress_continue_state)
|
|
== 12 * size_of::<usize>() + 2 * size_of::<c_int>() + 2 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressStreamState, compress_end_state)
|
|
== 13 * size_of::<usize>() + 2 * size_of::<c_int>() + 2 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressStreamState, reset_session)
|
|
== 14 * size_of::<usize>() + 2 * size_of::<c_int>() + 2 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
size_of::<ZSTD_rust_compressStreamState>()
|
|
== 15 * size_of::<usize>() + 2 * size_of::<c_int>() + 2 * size_of::<usize>()
|
|
);
|
|
};
|
|
|
|
const ZSTD_RUST_INIT_RESOLVE_BLOCK_SPLITTER: c_int = 0;
|
|
const ZSTD_RUST_INIT_RESOLVE_LDM: c_int = 1;
|
|
const ZSTD_RUST_INIT_RESOLVE_ROW_MATCH_FINDER: c_int = 2;
|
|
const ZSTD_RUST_INIT_RESOLVE_VALIDATE_SEQUENCES: c_int = 3;
|
|
const ZSTD_RUST_INIT_RESOLVE_MAX_BLOCK_SIZE: c_int = 4;
|
|
const ZSTD_RUST_INIT_RESOLVE_EXTERNAL_REPCODE_SEARCH: c_int = 5;
|
|
|
|
type CompressStreamInitGetLocalDictFn =
|
|
unsafe extern "C" fn(*mut c_void, *mut ZSTD_rust_compressStreamInitLocalDictState);
|
|
type CompressStreamInitCreateLocalDictFn =
|
|
unsafe extern "C" fn(*mut c_void, *mut ZSTD_rust_compressStreamInitLocalDictState) -> usize;
|
|
type CompressStreamInitPublishLocalDictFn =
|
|
unsafe extern "C" fn(*mut c_void, *const ZSTD_rust_compressStreamInitLocalDictState);
|
|
type CompressStreamInitRefreshCDictFn =
|
|
unsafe extern "C" fn(*mut c_void, *mut ZSTD_rust_compressStreamInitDictionaryState);
|
|
type CompressStreamInitClearPrefixFn = unsafe extern "C" fn(*mut c_void);
|
|
type CompressStreamInitAssertDictionariesFn = unsafe extern "C" fn(*mut c_void, *const c_void);
|
|
type CompressStreamInitSetLevelFn = unsafe extern "C" fn(*mut c_void, c_int);
|
|
type CompressStreamInitDebugFn = unsafe extern "C" fn(*mut c_void);
|
|
type CompressStreamInitGetPledgedFn = unsafe extern "C" fn(*mut c_void) -> u64;
|
|
type CompressStreamInitSetPledgedFn = unsafe extern "C" fn(*mut c_void, usize);
|
|
type CompressStreamInitGetCParamModeFn =
|
|
unsafe extern "C" fn(*mut c_void, *const c_void, u64) -> c_int;
|
|
type CompressStreamInitBuildCParamsFn = unsafe extern "C" fn(*mut c_void, u64, usize, c_int);
|
|
type CompressStreamInitResolveParamsFn = unsafe extern "C" fn(*mut c_void, c_int);
|
|
type CompressStreamInitGetNbWorkersFn = unsafe extern "C" fn(*mut c_void) -> c_uint;
|
|
type CompressStreamInitSetNbWorkersFn = unsafe extern "C" fn(*mut c_void, c_uint);
|
|
type CompressStreamInitHasExtSeqProdFn = unsafe extern "C" fn(*mut c_void) -> c_int;
|
|
type CompressStreamInitTraceFn = unsafe extern "C" fn(*mut c_void);
|
|
type CompressStreamInitGetMTContextFn = unsafe extern "C" fn(*mut c_void) -> *mut c_void;
|
|
type CompressStreamInitCreateMTContextFn = unsafe extern "C" fn(*mut c_void, c_uint) -> usize;
|
|
type CompressStreamInitMTFn = unsafe extern "C" fn(
|
|
*mut c_void,
|
|
*mut c_void,
|
|
*const ZSTD_rust_compressStreamInitDictionaryState,
|
|
*mut c_void,
|
|
u64,
|
|
) -> usize;
|
|
type CompressStreamInitCommitMTFn = unsafe extern "C" fn(
|
|
*mut c_void,
|
|
*const ZSTD_rust_compressStreamInitDictionaryState,
|
|
*mut c_void,
|
|
);
|
|
type CompressStreamInitCheckCParamsFn = unsafe extern "C" fn(*mut c_void);
|
|
type CompressStreamInitBeginFn = unsafe extern "C" fn(
|
|
*mut c_void,
|
|
*const ZSTD_rust_compressStreamInitDictionaryState,
|
|
*mut c_void,
|
|
u64,
|
|
) -> usize;
|
|
type CompressStreamInitAssertOrdinaryFn = unsafe extern "C" fn(*mut c_void);
|
|
type CompressStreamInitGetBufferModeFn = unsafe extern "C" fn(*mut c_void) -> c_int;
|
|
type CompressStreamInitGetBlockSizeFn = unsafe extern "C" fn(*mut c_void) -> usize;
|
|
type CompressStreamInitCommitOrdinaryFn = unsafe extern "C" fn(*mut c_void, usize);
|
|
|
|
/// Private local-dictionary projection used by transparent stream initialization.
|
|
///
|
|
/// Rust owns the no-dictionary, already-initialized, create, and publish order;
|
|
/// C retains the private dictionary storage and allocator-sensitive callbacks.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_compressStreamInitLocalDictState {
|
|
dict: *const c_void,
|
|
dict_buffer: *mut c_void,
|
|
dict_size: usize,
|
|
dict_content_type: c_int,
|
|
local_cdict: *mut c_void,
|
|
cdict: *const c_void,
|
|
prefix_dict: *const c_void,
|
|
created_cdict: *mut c_void,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_compressStreamInitLocalDictState, dict) == 0);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressStreamInitLocalDictState, dict_buffer) == size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressStreamInitLocalDictState, dict_size) == 2 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(
|
|
ZSTD_rust_compressStreamInitLocalDictState,
|
|
dict_content_type
|
|
) == 3 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressStreamInitLocalDictState, local_cdict)
|
|
== 4 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressStreamInitLocalDictState, cdict) == 5 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressStreamInitLocalDictState, prefix_dict)
|
|
== 6 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressStreamInitLocalDictState, created_cdict)
|
|
== 7 * size_of::<usize>()
|
|
);
|
|
assert!(size_of::<ZSTD_rust_compressStreamInitLocalDictState>() == size_of::<[usize; 8]>());
|
|
};
|
|
|
|
/// Scalar dictionary snapshot used by transparent stream initialization.
|
|
///
|
|
/// The prefix is populated by C before local-dictionary initialization, which
|
|
/// preserves the original single-use snapshot order. C refreshes only the
|
|
/// CDict fields after local-dictionary initialization because that operation
|
|
/// may create the local CDict.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_compressStreamInitDictionaryState {
|
|
prefix_dict: *const c_void,
|
|
prefix_dict_size: usize,
|
|
prefix_dict_content_type: c_int,
|
|
cdict: *const c_void,
|
|
cdict_is_local: c_int,
|
|
cdict_compression_level: c_int,
|
|
cdict_dict_content_size: usize,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_compressStreamInitDictionaryState, prefix_dict) == 0);
|
|
assert!(
|
|
offset_of!(
|
|
ZSTD_rust_compressStreamInitDictionaryState,
|
|
prefix_dict_size
|
|
) == size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(
|
|
ZSTD_rust_compressStreamInitDictionaryState,
|
|
prefix_dict_content_type
|
|
) == 2 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressStreamInitDictionaryState, cdict) == 3 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressStreamInitDictionaryState, cdict_is_local)
|
|
== 4 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(
|
|
ZSTD_rust_compressStreamInitDictionaryState,
|
|
cdict_compression_level
|
|
) == 4 * size_of::<usize>() + size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(
|
|
ZSTD_rust_compressStreamInitDictionaryState,
|
|
cdict_dict_content_size
|
|
) == 4 * size_of::<usize>() + 2 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
size_of::<ZSTD_rust_compressStreamInitDictionaryState>()
|
|
== if size_of::<usize>() == 8 { 48 } else { 28 }
|
|
);
|
|
};
|
|
|
|
/// Projection for `ZSTD_CCtx_init_compressStream2()`.
|
|
///
|
|
/// Rust owns the ordering and branch policy. The C callback slots retain
|
|
/// private parameter/context layouts, local dictionary storage, allocators,
|
|
/// trace setup, MT construction/init, and codec/reset operations.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_compressStreamInitState {
|
|
callback_context: *mut c_void,
|
|
params: *mut c_void,
|
|
dictionaries: *mut ZSTD_rust_compressStreamInitDictionaryState,
|
|
end_op: c_int,
|
|
in_size: usize,
|
|
multithreaded: c_int,
|
|
mt_job_size_min: usize,
|
|
get_local_dict: CompressStreamInitGetLocalDictFn,
|
|
create_local_dict: CompressStreamInitCreateLocalDictFn,
|
|
publish_local_dict: CompressStreamInitPublishLocalDictFn,
|
|
refresh_cdict: CompressStreamInitRefreshCDictFn,
|
|
clear_prefix: CompressStreamInitClearPrefixFn,
|
|
assert_dictionaries: CompressStreamInitAssertDictionariesFn,
|
|
set_compression_level: CompressStreamInitSetLevelFn,
|
|
debug_init: CompressStreamInitDebugFn,
|
|
get_pledged_src_size_plus_one: CompressStreamInitGetPledgedFn,
|
|
set_pledged_src_size: CompressStreamInitSetPledgedFn,
|
|
get_cparam_mode: CompressStreamInitGetCParamModeFn,
|
|
build_cparams: CompressStreamInitBuildCParamsFn,
|
|
resolve_params: CompressStreamInitResolveParamsFn,
|
|
get_nb_workers: CompressStreamInitGetNbWorkersFn,
|
|
set_nb_workers: CompressStreamInitSetNbWorkersFn,
|
|
has_ext_seq_prod: CompressStreamInitHasExtSeqProdFn,
|
|
trace_begin: CompressStreamInitTraceFn,
|
|
get_mt_context: CompressStreamInitGetMTContextFn,
|
|
create_mt_context: CompressStreamInitCreateMTContextFn,
|
|
init_mt: CompressStreamInitMTFn,
|
|
commit_mt: CompressStreamInitCommitMTFn,
|
|
check_cparams: CompressStreamInitCheckCParamsFn,
|
|
compress_begin: CompressStreamInitBeginFn,
|
|
assert_ordinary: CompressStreamInitAssertOrdinaryFn,
|
|
get_buffer_mode: CompressStreamInitGetBufferModeFn,
|
|
get_block_size: CompressStreamInitGetBlockSizeFn,
|
|
commit_ordinary: CompressStreamInitCommitOrdinaryFn,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_compressStreamInitState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_compressStreamInitState, params) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_compressStreamInitState, dictionaries) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_compressStreamInitState, end_op) == 3 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_compressStreamInitState, in_size) == 4 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressStreamInitState, multithreaded)
|
|
== 4 * size_of::<usize>() + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressStreamInitState, mt_job_size_min)
|
|
== 5 * size_of::<usize>() + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
size_of::<ZSTD_rust_compressStreamInitState>()
|
|
== if size_of::<usize>() == 8 { 272 } else { 144 }
|
|
);
|
|
};
|
|
|
|
#[inline]
|
|
unsafe fn compress_stream_init_local_dict_with(state: &ZSTD_rust_compressStreamInitState) -> usize {
|
|
let mut local_dict = ZSTD_rust_compressStreamInitLocalDictState {
|
|
dict: ptr::null(),
|
|
dict_buffer: ptr::null_mut(),
|
|
dict_size: 0,
|
|
dict_content_type: 0,
|
|
local_cdict: ptr::null_mut(),
|
|
cdict: ptr::null(),
|
|
prefix_dict: ptr::null(),
|
|
created_cdict: ptr::null_mut(),
|
|
};
|
|
unsafe {
|
|
(state.get_local_dict)(state.callback_context, &mut local_dict);
|
|
}
|
|
if local_dict.dict.is_null() {
|
|
debug_assert!(local_dict.dict_buffer.is_null());
|
|
debug_assert!(local_dict.local_cdict.is_null());
|
|
debug_assert_eq!(local_dict.dict_size, 0);
|
|
return 0;
|
|
}
|
|
if !local_dict.local_cdict.is_null() {
|
|
debug_assert_eq!(local_dict.cdict, local_dict.local_cdict.cast_const(),);
|
|
return 0;
|
|
}
|
|
debug_assert!(local_dict.dict_size > 0);
|
|
debug_assert!(local_dict.cdict.is_null());
|
|
debug_assert!(local_dict.prefix_dict.is_null());
|
|
|
|
let result = unsafe { (state.create_local_dict)(state.callback_context, &mut local_dict) };
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
debug_assert!(!local_dict.created_cdict.is_null());
|
|
unsafe {
|
|
(state.publish_local_dict)(state.callback_context, &local_dict);
|
|
}
|
|
0
|
|
}
|
|
|
|
#[inline]
|
|
unsafe fn compress_stream_init_body_with(state: &ZSTD_rust_compressStreamInitState) -> usize {
|
|
if state.params.is_null() || state.dictionaries.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
let result = unsafe { compress_stream_init_local_dict_with(state) };
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
|
|
unsafe { (state.refresh_cdict)(state.callback_context, state.dictionaries) };
|
|
let dictionaries = unsafe { &*state.dictionaries };
|
|
unsafe { (state.clear_prefix)(state.callback_context) };
|
|
unsafe { (state.assert_dictionaries)(state.callback_context, dictionaries.prefix_dict) };
|
|
if !dictionaries.cdict.is_null() && dictionaries.cdict_is_local == 0 {
|
|
unsafe {
|
|
(state.set_compression_level)(state.params, dictionaries.cdict_compression_level)
|
|
};
|
|
}
|
|
unsafe { (state.debug_init)(state.callback_context) };
|
|
|
|
if state.end_op == ZSTD_E_END {
|
|
unsafe { (state.set_pledged_src_size)(state.callback_context, state.in_size) };
|
|
}
|
|
let pledged_src_size_plus_one =
|
|
unsafe { (state.get_pledged_src_size_plus_one)(state.callback_context) };
|
|
let pledged_src_size = pledged_src_size_plus_one.wrapping_sub(1);
|
|
let dict_size = if !dictionaries.prefix_dict.is_null() {
|
|
dictionaries.prefix_dict_size
|
|
} else if !dictionaries.cdict.is_null() {
|
|
dictionaries.cdict_dict_content_size
|
|
} else {
|
|
0
|
|
};
|
|
let mode =
|
|
unsafe { (state.get_cparam_mode)(state.params, dictionaries.cdict, pledged_src_size) };
|
|
unsafe {
|
|
(state.build_cparams)(state.params, pledged_src_size, dict_size, mode);
|
|
(state.resolve_params)(state.params, ZSTD_RUST_INIT_RESOLVE_BLOCK_SPLITTER);
|
|
(state.resolve_params)(state.params, ZSTD_RUST_INIT_RESOLVE_LDM);
|
|
(state.resolve_params)(state.params, ZSTD_RUST_INIT_RESOLVE_ROW_MATCH_FINDER);
|
|
(state.resolve_params)(state.params, ZSTD_RUST_INIT_RESOLVE_VALIDATE_SEQUENCES);
|
|
(state.resolve_params)(state.params, ZSTD_RUST_INIT_RESOLVE_MAX_BLOCK_SIZE);
|
|
(state.resolve_params)(state.params, ZSTD_RUST_INIT_RESOLVE_EXTERNAL_REPCODE_SEARCH);
|
|
}
|
|
|
|
if state.multithreaded != 0 {
|
|
let has_ext_seq_prod = unsafe { (state.has_ext_seq_prod)(state.params) };
|
|
let mut nb_workers = unsafe { (state.get_nb_workers)(state.params) };
|
|
if has_ext_seq_prod != 0 && nb_workers >= 1 {
|
|
return ERROR(ZstdErrorCode::ParameterCombinationUnsupported);
|
|
}
|
|
if pledged_src_size <= state.mt_job_size_min as u64 {
|
|
nb_workers = 0;
|
|
unsafe { (state.set_nb_workers)(state.params, 0) };
|
|
}
|
|
if nb_workers > 0 {
|
|
unsafe { (state.trace_begin)(state.callback_context) };
|
|
if unsafe { (state.get_mt_context)(state.callback_context) }.is_null() {
|
|
let result =
|
|
unsafe { (state.create_mt_context)(state.callback_context, nb_workers) };
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
}
|
|
let result = unsafe {
|
|
(state.init_mt)(
|
|
state.callback_context,
|
|
(state.get_mt_context)(state.callback_context),
|
|
dictionaries,
|
|
state.params,
|
|
pledged_src_size,
|
|
)
|
|
};
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
unsafe { (state.commit_mt)(state.callback_context, dictionaries, state.params) };
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
unsafe { (state.check_cparams)(state.params) };
|
|
let result = unsafe {
|
|
(state.compress_begin)(
|
|
state.callback_context,
|
|
dictionaries,
|
|
state.params,
|
|
pledged_src_size,
|
|
)
|
|
};
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
unsafe { (state.assert_ordinary)(state.callback_context) };
|
|
let buffer_mode = unsafe { (state.get_buffer_mode)(state.callback_context) };
|
|
let block_size = unsafe { (state.get_block_size)(state.callback_context) };
|
|
let in_buff_target = if buffer_mode == ZSTD_BM_BUFFERED {
|
|
block_size.wrapping_add(usize::from(block_size as u64 == pledged_src_size))
|
|
} else {
|
|
0
|
|
};
|
|
unsafe { (state.commit_ordinary)(state.callback_context, in_buff_target) };
|
|
0
|
|
}
|
|
|
|
/// Rust-owned high-level policy for transparent stream initialization.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_compressStreamInit(
|
|
state: *const ZSTD_rust_compressStreamInitState,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
unsafe { compress_stream_init_body_with(&*state) }
|
|
}
|
|
|
|
#[inline]
|
|
unsafe fn stream_limit_copy(
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
) -> usize {
|
|
let length = dst_capacity.min(src_size);
|
|
if length != 0 {
|
|
unsafe {
|
|
ptr::copy_nonoverlapping(src.cast::<u8>(), dst.cast::<u8>(), length);
|
|
}
|
|
}
|
|
length
|
|
}
|
|
|
|
#[inline]
|
|
unsafe fn compress_stream_generic_body_with(
|
|
state: &ZSTD_rust_compressStreamState,
|
|
output: &mut ZSTD_outBuffer,
|
|
input: &mut ZSTD_inBuffer,
|
|
flush_mode: c_int,
|
|
) -> usize {
|
|
debug_assert!((ZSTD_E_CONTINUE..=ZSTD_E_END).contains(&flush_mode));
|
|
debug_assert!(input.pos <= input.size);
|
|
debug_assert!(output.pos <= output.size);
|
|
debug_assert!(!input.src.is_null() || input.size == 0);
|
|
debug_assert!(!output.dst.is_null() || output.size == 0);
|
|
|
|
if state.stream_stage.is_null()
|
|
|| state.stable_in_not_consumed.is_null()
|
|
|| state.in_to_compress.is_null()
|
|
|| state.in_buff_pos.is_null()
|
|
|| state.in_buff_target.is_null()
|
|
|| state.out_buff_content_size.is_null()
|
|
|| state.out_buff_flushed_size.is_null()
|
|
|| state.frame_ended.is_null()
|
|
|| state.compress_continue_state.is_null()
|
|
|| state.compress_end_state.is_null()
|
|
{
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
if state.in_buffer_mode == ZSTD_BM_STABLE {
|
|
let stable = unsafe { *state.stable_in_not_consumed };
|
|
debug_assert!(input.pos >= stable);
|
|
input.pos = input.pos.wrapping_sub(stable);
|
|
*unsafe { state.stable_in_not_consumed.as_mut().unwrap_unchecked() } = 0;
|
|
}
|
|
|
|
debug_assert!(
|
|
state.in_buffer_mode == ZSTD_BM_BUFFERED || state.in_buffer_mode == ZSTD_BM_STABLE
|
|
);
|
|
if state.in_buffer_mode == ZSTD_BM_BUFFERED {
|
|
debug_assert!(!state.in_buff.is_null());
|
|
debug_assert!(state.in_buff_size != 0);
|
|
}
|
|
if state.out_buffer_mode == ZSTD_BM_BUFFERED {
|
|
debug_assert!(!state.out_buff.is_null());
|
|
debug_assert!(state.out_buff_size != 0);
|
|
}
|
|
|
|
let mut some_more_work = true;
|
|
while some_more_work {
|
|
let stage = unsafe { *state.stream_stage };
|
|
match stage {
|
|
ZSTD_CSTREAM_STAGE_INIT => return ERROR(ZstdErrorCode::InitMissing),
|
|
ZSTD_CSTREAM_STAGE_LOAD => {
|
|
let input_remaining = input.size - input.pos;
|
|
let output_remaining = output.size - output.pos;
|
|
let bound = crate::zstd_compress_api::ZSTD_compressBound(input_remaining);
|
|
if flush_mode == ZSTD_E_END
|
|
&& (output_remaining >= bound || state.out_buffer_mode == ZSTD_BM_STABLE)
|
|
&& unsafe { *state.in_buff_pos } == 0
|
|
{
|
|
let dst = if output.dst.is_null() {
|
|
ptr::null_mut()
|
|
} else {
|
|
unsafe { output.dst.cast::<u8>().add(output.pos).cast() }
|
|
};
|
|
let src = if input.src.is_null() {
|
|
ptr::null()
|
|
} else {
|
|
unsafe { input.src.cast::<u8>().add(input.pos).cast() }
|
|
};
|
|
let c_size = unsafe {
|
|
ZSTD_rust_compressEnd(
|
|
state.compress_end_state,
|
|
dst,
|
|
output_remaining,
|
|
src,
|
|
input_remaining,
|
|
)
|
|
};
|
|
if ERR_isError(c_size) {
|
|
return c_size;
|
|
}
|
|
input.pos = input.size;
|
|
output.pos += c_size;
|
|
unsafe { *state.frame_ended = 1 };
|
|
unsafe { (state.reset_session)(state.callback_context) };
|
|
some_more_work = false;
|
|
continue;
|
|
}
|
|
|
|
if state.in_buffer_mode == ZSTD_BM_BUFFERED {
|
|
let to_load =
|
|
unsafe { (*state.in_buff_target).wrapping_sub(*state.in_buff_pos) };
|
|
let src = if input.src.is_null() {
|
|
ptr::null()
|
|
} else {
|
|
unsafe { input.src.cast::<u8>().add(input.pos).cast() }
|
|
};
|
|
let dst = if state.in_buff.is_null() {
|
|
ptr::null_mut()
|
|
} else {
|
|
unsafe { state.in_buff.cast::<u8>().add(*state.in_buff_pos).cast() }
|
|
};
|
|
let loaded = unsafe { stream_limit_copy(dst, to_load, src, input_remaining) };
|
|
input.pos += loaded;
|
|
unsafe { *state.in_buff_pos += loaded };
|
|
|
|
if flush_mode == ZSTD_E_CONTINUE
|
|
&& unsafe { *state.in_buff_pos < *state.in_buff_target }
|
|
{
|
|
some_more_work = false;
|
|
continue;
|
|
}
|
|
if flush_mode == ZSTD_E_FLUSH
|
|
&& unsafe { *state.in_buff_pos == *state.in_to_compress }
|
|
{
|
|
some_more_work = false;
|
|
continue;
|
|
}
|
|
} else {
|
|
debug_assert_eq!(state.in_buffer_mode, ZSTD_BM_STABLE);
|
|
let input_remaining = input.size - input.pos;
|
|
if flush_mode == ZSTD_E_CONTINUE && input_remaining < state.block_size_max {
|
|
unsafe { *state.stable_in_not_consumed = input_remaining };
|
|
input.pos = input.size;
|
|
some_more_work = false;
|
|
continue;
|
|
}
|
|
if flush_mode == ZSTD_E_FLUSH && input.pos == input.size {
|
|
some_more_work = false;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
let input_buffered = state.in_buffer_mode == ZSTD_BM_BUFFERED;
|
|
let mut output_size = output.size - output.pos;
|
|
let input_size = if input_buffered {
|
|
unsafe { (*state.in_buff_pos).wrapping_sub(*state.in_to_compress) }
|
|
} else {
|
|
(input.size - input.pos).min(state.block_size_max)
|
|
};
|
|
let output_dst = if output_size
|
|
>= crate::zstd_compress_api::ZSTD_compressBound(input_size)
|
|
|| state.out_buffer_mode == ZSTD_BM_STABLE
|
|
{
|
|
if output.dst.is_null() {
|
|
ptr::null_mut()
|
|
} else {
|
|
unsafe { output.dst.cast::<u8>().add(output.pos).cast() }
|
|
}
|
|
} else {
|
|
output_size = state.out_buff_size;
|
|
state.out_buff
|
|
};
|
|
|
|
let last_block = flush_mode == ZSTD_E_END
|
|
&& if input_buffered {
|
|
input.pos == input.size
|
|
} else {
|
|
input.pos + input_size == input.size
|
|
};
|
|
let source = if input_buffered {
|
|
if state.in_buff.is_null() {
|
|
ptr::null()
|
|
} else {
|
|
unsafe { state.in_buff.cast::<u8>().add(*state.in_to_compress).cast() }
|
|
}
|
|
} else if input.src.is_null() {
|
|
ptr::null()
|
|
} else {
|
|
unsafe { input.src.cast::<u8>().add(input.pos).cast() }
|
|
};
|
|
let c_size = unsafe {
|
|
if last_block {
|
|
ZSTD_rust_compressEnd(
|
|
state.compress_end_state,
|
|
output_dst,
|
|
output_size,
|
|
source,
|
|
input_size,
|
|
)
|
|
} else {
|
|
ZSTD_rust_compressContinue(
|
|
state.compress_continue_state,
|
|
output_dst,
|
|
output_size,
|
|
source,
|
|
input_size,
|
|
1,
|
|
0,
|
|
)
|
|
}
|
|
};
|
|
if !input_buffered {
|
|
/* Match C: stable input is consumed before forwarding a
|
|
* compression error. */
|
|
input.pos += input_size;
|
|
}
|
|
if ERR_isError(c_size) {
|
|
return c_size;
|
|
}
|
|
|
|
unsafe { *state.frame_ended = c_uint::from(last_block) };
|
|
if input_buffered {
|
|
unsafe {
|
|
*state.in_buff_target =
|
|
(*state.in_buff_pos).wrapping_add(state.block_size_max);
|
|
if *state.in_buff_target > state.in_buff_size {
|
|
*state.in_buff_pos = 0;
|
|
*state.in_buff_target = state.block_size_max;
|
|
}
|
|
*state.in_to_compress = *state.in_buff_pos;
|
|
}
|
|
}
|
|
|
|
let current_output = if output.dst.is_null() {
|
|
ptr::null_mut()
|
|
} else {
|
|
unsafe { output.dst.cast::<u8>().add(output.pos).cast() }
|
|
};
|
|
if output_dst == current_output {
|
|
output.pos += c_size;
|
|
if unsafe { *state.frame_ended } != 0 {
|
|
unsafe { (state.reset_session)(state.callback_context) };
|
|
some_more_work = false;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
unsafe {
|
|
*state.out_buff_content_size = c_size;
|
|
*state.out_buff_flushed_size = 0;
|
|
*state.stream_stage = ZSTD_CSTREAM_STAGE_FLUSH;
|
|
}
|
|
}
|
|
ZSTD_CSTREAM_STAGE_FLUSH => {
|
|
debug_assert_eq!(state.out_buffer_mode, ZSTD_BM_BUFFERED);
|
|
let to_flush = unsafe {
|
|
(*state.out_buff_content_size).wrapping_sub(*state.out_buff_flushed_size)
|
|
};
|
|
let src = if state.out_buff.is_null() {
|
|
ptr::null()
|
|
} else {
|
|
unsafe {
|
|
state
|
|
.out_buff
|
|
.cast::<u8>()
|
|
.add(*state.out_buff_flushed_size)
|
|
.cast()
|
|
}
|
|
};
|
|
let dst = if output.dst.is_null() {
|
|
ptr::null_mut()
|
|
} else {
|
|
unsafe { output.dst.cast::<u8>().add(output.pos).cast() }
|
|
};
|
|
let flushed =
|
|
unsafe { stream_limit_copy(dst, output.size - output.pos, src, to_flush) };
|
|
output.pos += flushed;
|
|
unsafe { *state.out_buff_flushed_size += flushed };
|
|
if to_flush != flushed {
|
|
some_more_work = false;
|
|
continue;
|
|
}
|
|
|
|
unsafe {
|
|
*state.out_buff_content_size = 0;
|
|
*state.out_buff_flushed_size = 0;
|
|
}
|
|
if unsafe { *state.frame_ended } != 0 {
|
|
unsafe { (state.reset_session)(state.callback_context) };
|
|
some_more_work = false;
|
|
continue;
|
|
}
|
|
unsafe { *state.stream_stage = ZSTD_CSTREAM_STAGE_LOAD };
|
|
}
|
|
_ => {
|
|
debug_assert!(false, "invalid C stream stage");
|
|
return ERROR(ZstdErrorCode::StageWrong);
|
|
}
|
|
}
|
|
}
|
|
|
|
if unsafe { *state.frame_ended } != 0 {
|
|
return 0;
|
|
}
|
|
next_input_size_hint(
|
|
state.in_buffer_mode,
|
|
state.block_size_max,
|
|
unsafe { *state.stable_in_not_consumed },
|
|
unsafe { *state.in_buff_target },
|
|
unsafe { *state.in_buff_pos },
|
|
)
|
|
}
|
|
|
|
/// Drive the single-threaded stream state machine through a C-owned context.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_compressStreamGeneric(
|
|
state: *const ZSTD_rust_compressStreamState,
|
|
output: *mut ZSTD_outBuffer,
|
|
input: *mut ZSTD_inBuffer,
|
|
flush_mode: c_int,
|
|
) -> usize {
|
|
if state.is_null() || output.is_null() || input.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
unsafe { compress_stream_generic_body_with(&*state, &mut *output, &mut *input, flush_mode) }
|
|
}
|
|
|
|
/// Explicit projection of the state used by `ZSTD_compressSequences_internal`.
|
|
///
|
|
/// The C context and its function-pointer-bearing parameter structure remain
|
|
/// private to C. Only the sequence store, compressed-block state slots, and
|
|
/// scalar policy/workspace fields read by the per-block loop cross the ABI.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_sequenceCompressionState {
|
|
seq_store: *mut SeqStore_t,
|
|
prev_c_block: *mut *mut ZSTD_compressedBlockState_t,
|
|
next_c_block: *mut *mut ZSTD_compressedBlockState_t,
|
|
tmp_workspace: *mut c_void,
|
|
tmp_wksp_size: usize,
|
|
block_size_max: usize,
|
|
bmi2: c_int,
|
|
block_delimiters: c_int,
|
|
strategy: c_int,
|
|
disable_literal_compression: c_int,
|
|
search_for_external_repcodes: c_int,
|
|
validate_sequences: c_int,
|
|
min_match: c_uint,
|
|
window_log: c_uint,
|
|
dict_size: c_uint,
|
|
use_sequence_producer: c_int,
|
|
is_first_block: *mut c_int,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_sequenceCompressionState, seq_store) == 0);
|
|
assert!(offset_of!(ZSTD_rust_sequenceCompressionState, prev_c_block) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_sequenceCompressionState, next_c_block) == 2 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_sequenceCompressionState, tmp_workspace) == 3 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_sequenceCompressionState, tmp_wksp_size) == 4 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_sequenceCompressionState, block_size_max) == 5 * size_of::<usize>()
|
|
);
|
|
assert!(offset_of!(ZSTD_rust_sequenceCompressionState, bmi2) == 6 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_sequenceCompressionState, min_match)
|
|
== 6 * size_of::<usize>() + 6 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_sequenceCompressionState, use_sequence_producer)
|
|
== 6 * size_of::<usize>() + 6 * size_of::<c_int>() + 3 * size_of::<c_uint>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_sequenceCompressionState, is_first_block)
|
|
== 6 * size_of::<usize>() + 7 * size_of::<c_int>() + 3 * size_of::<c_uint>()
|
|
);
|
|
assert!(
|
|
size_of::<ZSTD_rust_sequenceCompressionState>()
|
|
== if size_of::<usize>() == 8 { 96 } else { 68 }
|
|
);
|
|
};
|
|
|
|
/// C callback signature for the CCtx-dependent sequence conversion leaf.
|
|
type SequenceLiteralsConvertFn =
|
|
unsafe extern "C" fn(*mut c_void, *const ZSTD_Sequence, usize, c_int) -> usize;
|
|
|
|
/// Explicit projection of the state used by
|
|
/// ZSTD_compressSequencesAndLiterals_internal.
|
|
///
|
|
/// CCtx initialization, parameter validation, and the sequence conversion
|
|
/// callback remain in C. Rust owns the block loop, external literal cursor,
|
|
/// entropy invocation, block framing, and per-frame completion checks.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_sequenceLiteralsState {
|
|
seq_store: *mut SeqStore_t,
|
|
prev_c_block: *mut *mut ZSTD_compressedBlockState_t,
|
|
next_c_block: *mut *mut ZSTD_compressedBlockState_t,
|
|
tmp_workspace: *mut c_void,
|
|
tmp_wksp_size: usize,
|
|
block_size_max: usize,
|
|
bmi2: c_int,
|
|
strategy: c_int,
|
|
disable_literal_compression: c_int,
|
|
repcode_resolution: c_int,
|
|
is_first_block: *mut c_int,
|
|
callback_context: *mut c_void,
|
|
convert_block_sequences: SequenceLiteralsConvertFn,
|
|
}
|
|
|
|
/// Explicit projection for the public block-sequence conversion entry point.
|
|
/// The surrounding `ZSTD_CCtx` remains opaque; only the sequence store and
|
|
/// compressed-block repcode slots are needed by the converter.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_convertBlockSequencesState {
|
|
seq_store: *mut SeqStore_t,
|
|
prev_c_block: *mut *mut ZSTD_compressedBlockState_t,
|
|
next_c_block: *mut *mut ZSTD_compressedBlockState_t,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_convertBlockSequencesState, seq_store) == 0);
|
|
assert!(offset_of!(ZSTD_rust_convertBlockSequencesState, prev_c_block) == size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_convertBlockSequencesState, next_c_block) == 2 * size_of::<usize>()
|
|
);
|
|
assert!(size_of::<ZSTD_rust_convertBlockSequencesState>() == 3 * size_of::<usize>());
|
|
};
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_sequenceLiteralsState, seq_store) == 0);
|
|
assert!(offset_of!(ZSTD_rust_sequenceLiteralsState, prev_c_block) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_sequenceLiteralsState, next_c_block) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_sequenceLiteralsState, tmp_workspace) == 3 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_sequenceLiteralsState, tmp_wksp_size) == 4 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_sequenceLiteralsState, block_size_max) == 5 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_sequenceLiteralsState, bmi2) == 6 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_sequenceLiteralsState, strategy)
|
|
== 6 * size_of::<usize>() + size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_sequenceLiteralsState, disable_literal_compression)
|
|
== 6 * size_of::<usize>() + 2 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_sequenceLiteralsState, repcode_resolution)
|
|
== 6 * size_of::<usize>() + 3 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_sequenceLiteralsState, is_first_block)
|
|
== 6 * size_of::<usize>() + 4 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_sequenceLiteralsState, callback_context)
|
|
== 6 * size_of::<usize>() + 4 * size_of::<c_int>() + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_sequenceLiteralsState, convert_block_sequences)
|
|
== 6 * size_of::<usize>() + 4 * size_of::<c_int>() + 2 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
size_of::<ZSTD_rust_sequenceLiteralsState>()
|
|
== 6 * size_of::<usize>() + 4 * size_of::<c_int>() + 3 * size_of::<usize>()
|
|
);
|
|
};
|
|
|
|
type SequenceApiInitFn =
|
|
unsafe extern "C" fn(*mut c_void, usize, *mut ZSTD_rust_sequenceApiState) -> usize;
|
|
|
|
/// Explicit projection for the public sequence-compression API orchestration.
|
|
///
|
|
/// Rust owns validation ordering, frame-header/checksum sequencing, and
|
|
/// output accounting. Frame-header parameters are projected as scalars; C
|
|
/// retains the private CCtx and sequence-store/block layouts through the
|
|
/// initialization callback and two block-state projections. The XXH64 state
|
|
/// is projected directly so checksum updates and serialization stay in Rust.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_sequenceApiState {
|
|
callback_context: *mut c_void,
|
|
sequence_state: *mut ZSTD_rust_sequenceCompressionState,
|
|
sequence_literals_state: *mut ZSTD_rust_sequenceLiteralsState,
|
|
init: SequenceApiInitFn,
|
|
checksum_state: *mut XXH64_state_t,
|
|
checksum_flag: c_int,
|
|
block_delimiters: c_int,
|
|
validate_sequences: c_int,
|
|
no_dict_id_flag: c_int,
|
|
content_size_flag: c_int,
|
|
format: c_int,
|
|
window_log: c_uint,
|
|
dict_id: c_uint,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_sequenceApiState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_sequenceApiState, sequence_state) == size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_sequenceApiState, sequence_literals_state) == 2 * size_of::<usize>()
|
|
);
|
|
assert!(offset_of!(ZSTD_rust_sequenceApiState, init) == 3 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_sequenceApiState, checksum_state) == 4 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_sequenceApiState, checksum_flag) == size_of::<[usize; 5]>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_sequenceApiState, block_delimiters)
|
|
== size_of::<[usize; 5]>() + size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_sequenceApiState, validate_sequences)
|
|
== size_of::<[usize; 5]>() + size_of::<[c_int; 2]>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_sequenceApiState, no_dict_id_flag)
|
|
== size_of::<[usize; 5]>() + size_of::<[c_int; 3]>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_sequenceApiState, content_size_flag)
|
|
== size_of::<[usize; 5]>() + size_of::<[c_int; 4]>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_sequenceApiState, format)
|
|
== size_of::<[usize; 5]>() + size_of::<[c_int; 5]>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_sequenceApiState, window_log)
|
|
== size_of::<[usize; 5]>() + size_of::<[c_int; 6]>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_sequenceApiState, dict_id)
|
|
== size_of::<[usize; 5]>() + size_of::<[c_int; 6]>() + size_of::<c_uint>()
|
|
);
|
|
assert!(
|
|
size_of::<ZSTD_rust_sequenceApiState>() == if size_of::<usize>() == 8 { 72 } else { 52 }
|
|
);
|
|
};
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
struct SequenceApiPlan {
|
|
update_input_checksum: bool,
|
|
append_frame_checksum: bool,
|
|
}
|
|
|
|
/// Apply the public sequence API's post-initialization validation policy.
|
|
///
|
|
/// The order is intentional and matches the C entry point: the literals
|
|
/// variant rejects no-delimiter mode first, then sequence validation, then a
|
|
/// frame checksum. The ordinary variant permits all three independently.
|
|
fn sequence_api_plan(
|
|
with_literals: bool,
|
|
block_delimiters: c_int,
|
|
validate_sequences: c_int,
|
|
checksum_flag: c_int,
|
|
) -> Result<SequenceApiPlan, usize> {
|
|
if with_literals {
|
|
if block_delimiters == ZSTD_SF_NO_BLOCK_DELIMITERS {
|
|
return Err(ERROR(ZstdErrorCode::FrameParameterUnsupported));
|
|
}
|
|
if validate_sequences != 0 {
|
|
return Err(ERROR(ZstdErrorCode::ParameterUnsupported));
|
|
}
|
|
if checksum_flag != 0 {
|
|
return Err(ERROR(ZstdErrorCode::FrameParameterUnsupported));
|
|
}
|
|
return Ok(SequenceApiPlan {
|
|
update_input_checksum: false,
|
|
append_frame_checksum: false,
|
|
});
|
|
}
|
|
|
|
Ok(SequenceApiPlan {
|
|
update_input_checksum: checksum_flag != 0,
|
|
append_frame_checksum: checksum_flag != 0,
|
|
})
|
|
}
|
|
|
|
#[inline]
|
|
fn sequence_api_validate_literal_capacity(lit_size: usize, lit_capacity: usize) -> usize {
|
|
if lit_capacity < lit_size {
|
|
ERROR(ZstdErrorCode::WorkSpaceTooSmall)
|
|
} else {
|
|
0
|
|
}
|
|
}
|
|
|
|
/// Explicit projection of the state used by `ZSTD_compressSeqStore_singleBlock`.
|
|
///
|
|
/// Sequence-store construction and split discovery remain in C. Only the
|
|
/// sequence store, simulated repcode histories, compressed-block state slots,
|
|
/// sequence collector, and scalar compression settings cross the ABI.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_seqStoreSingleBlockState {
|
|
seq_store: *const SeqStore_t,
|
|
d_rep: *mut u32,
|
|
c_rep: *mut u32,
|
|
prev_c_block: *mut *mut ZSTD_compressedBlockState_t,
|
|
next_c_block: *mut *mut ZSTD_compressedBlockState_t,
|
|
tmp_workspace: *mut c_void,
|
|
tmp_wksp_size: usize,
|
|
seq_collector: *mut SeqCollector,
|
|
strategy: c_int,
|
|
disable_literal_compression: c_int,
|
|
bmi2: c_int,
|
|
is_first_block: c_int,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_seqStoreSingleBlockState, seq_store) == 0);
|
|
assert!(offset_of!(ZSTD_rust_seqStoreSingleBlockState, d_rep) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_seqStoreSingleBlockState, c_rep) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_seqStoreSingleBlockState, prev_c_block) == 3 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_seqStoreSingleBlockState, next_c_block) == 4 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_seqStoreSingleBlockState, tmp_workspace) == 5 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_seqStoreSingleBlockState, tmp_wksp_size) == 6 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_seqStoreSingleBlockState, seq_collector) == 7 * size_of::<usize>()
|
|
);
|
|
assert!(offset_of!(ZSTD_rust_seqStoreSingleBlockState, strategy) == usize::BITS as usize);
|
|
assert!(
|
|
offset_of!(
|
|
ZSTD_rust_seqStoreSingleBlockState,
|
|
disable_literal_compression
|
|
) == usize::BITS as usize + size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_seqStoreSingleBlockState, bmi2)
|
|
== usize::BITS as usize + 2 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_seqStoreSingleBlockState, is_first_block)
|
|
== usize::BITS as usize + 3 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
size_of::<ZSTD_rust_seqStoreSingleBlockState>()
|
|
== usize::BITS as usize + 4 * size_of::<c_int>()
|
|
);
|
|
};
|
|
|
|
/// Explicit projection of the state used by the post-split partition loop.
|
|
///
|
|
/// The C caller still owns the private compression context and derives the
|
|
/// partition boundaries. Rust owns the partition accounting, repcode
|
|
/// simulation, and repeated single-block dispatch.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_splitBlockState {
|
|
seq_store: *const SeqStore_t,
|
|
partitions: *const u32,
|
|
next_seq_store: *mut SeqStore_t,
|
|
curr_seq_store: *mut SeqStore_t,
|
|
prev_c_block: *mut *mut ZSTD_compressedBlockState_t,
|
|
next_c_block: *mut *mut ZSTD_compressedBlockState_t,
|
|
tmp_workspace: *mut c_void,
|
|
tmp_wksp_size: usize,
|
|
seq_collector: *mut SeqCollector,
|
|
block_size_max: usize,
|
|
strategy: c_int,
|
|
disable_literal_compression: c_int,
|
|
bmi2: c_int,
|
|
is_first_block: c_int,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_splitBlockState, seq_store) == 0);
|
|
assert!(offset_of!(ZSTD_rust_splitBlockState, partitions) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_splitBlockState, next_seq_store) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_splitBlockState, curr_seq_store) == 3 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_splitBlockState, prev_c_block) == 4 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_splitBlockState, next_c_block) == 5 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_splitBlockState, tmp_workspace) == 6 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_splitBlockState, tmp_wksp_size) == 7 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_splitBlockState, seq_collector) == usize::BITS as usize);
|
|
assert!(offset_of!(ZSTD_rust_splitBlockState, block_size_max) == 9 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_splitBlockState, strategy) == 10 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_splitBlockState, disable_literal_compression)
|
|
== 10 * size_of::<usize>() + size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_splitBlockState, bmi2)
|
|
== 10 * size_of::<usize>() + 2 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_splitBlockState, is_first_block)
|
|
== 10 * size_of::<usize>() + 3 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
size_of::<ZSTD_rust_splitBlockState>() == 10 * size_of::<usize>() + 4 * size_of::<c_int>()
|
|
);
|
|
};
|
|
|
|
/// Explicit projection of the state used by `ZSTD_compressBlock_internal`.
|
|
///
|
|
/// Sequence-store construction enters through an explicit build-state
|
|
/// projection. Rust owns sequence collection, entropy emission, the legacy
|
|
/// first-block RLE gate, and the compressed-block state transitions after the
|
|
/// store is ready.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_blockInternalState {
|
|
seq_store: *const SeqStore_t,
|
|
prev_c_block: *mut *mut ZSTD_compressedBlockState_t,
|
|
next_c_block: *mut *mut ZSTD_compressedBlockState_t,
|
|
tmp_workspace: *mut c_void,
|
|
tmp_wksp_size: usize,
|
|
seq_collector: *mut SeqCollector,
|
|
strategy: c_int,
|
|
disable_literal_compression: c_int,
|
|
bmi2: c_int,
|
|
is_first_block: c_int,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_blockInternalState, seq_store) == 0);
|
|
assert!(offset_of!(ZSTD_rust_blockInternalState, prev_c_block) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_blockInternalState, next_c_block) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_blockInternalState, tmp_workspace) == 3 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_blockInternalState, tmp_wksp_size) == 4 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_blockInternalState, seq_collector) == 5 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_blockInternalState, strategy) == 6 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_blockInternalState, disable_literal_compression)
|
|
== 6 * size_of::<usize>() + size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_blockInternalState, bmi2)
|
|
== 6 * size_of::<usize>() + 2 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_blockInternalState, is_first_block)
|
|
== 6 * size_of::<usize>() + 3 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
size_of::<ZSTD_rust_blockInternalState>()
|
|
== 6 * size_of::<usize>() + 4 * size_of::<c_int>()
|
|
);
|
|
};
|
|
|
|
/// Explicit projection of the state used by the target-sized block body.
|
|
///
|
|
/// Sequence-store construction and the matchfinder remain in C. This state
|
|
/// contains only the already-Rust-owned block-compression inputs and the two
|
|
/// pointer slots that the compressed-block confirmation leaf swaps.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_targetCBlockSizeState {
|
|
seq_store: *mut SeqStore_t,
|
|
prev_c_block: *mut *mut ZSTD_compressedBlockState_t,
|
|
next_c_block: *mut *mut ZSTD_compressedBlockState_t,
|
|
tmp_workspace: *mut c_void,
|
|
tmp_wksp_size: usize,
|
|
strategy: c_int,
|
|
disable_literal_compression: c_int,
|
|
bmi2: c_int,
|
|
window_log: c_uint,
|
|
target_c_block_size: usize,
|
|
is_first_block: c_int,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_targetCBlockSizeState, seq_store) == 0);
|
|
assert!(offset_of!(ZSTD_rust_targetCBlockSizeState, prev_c_block) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_targetCBlockSizeState, next_c_block) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_targetCBlockSizeState, tmp_workspace) == 3 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_targetCBlockSizeState, tmp_wksp_size) == 4 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_targetCBlockSizeState, strategy) == 5 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_targetCBlockSizeState, disable_literal_compression)
|
|
== 5 * size_of::<usize>() + size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_targetCBlockSizeState, bmi2)
|
|
== 5 * size_of::<usize>() + 2 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_targetCBlockSizeState, window_log)
|
|
== 5 * size_of::<usize>() + 3 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_targetCBlockSizeState, target_c_block_size)
|
|
== 5 * size_of::<usize>() + 4 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_targetCBlockSizeState, is_first_block)
|
|
== 5 * size_of::<usize>() + 4 * size_of::<c_int>() + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
size_of::<ZSTD_rust_targetCBlockSizeState>()
|
|
== if size_of::<usize>() == 8 { 72 } else { 44 }
|
|
);
|
|
};
|
|
|
|
#[repr(i32)]
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
enum TargetCBlockAction {
|
|
Raw = 0,
|
|
Rle = 1,
|
|
Compressed = 2,
|
|
Error = 3,
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
enum SequenceBlockAction {
|
|
Raw,
|
|
Rle,
|
|
Compressed,
|
|
}
|
|
|
|
type SingleBlockEntropyFn = unsafe extern "C" fn(
|
|
*const SeqStore_t,
|
|
*const ZSTD_entropyCTables_t,
|
|
*mut ZSTD_entropyCTables_t,
|
|
c_int,
|
|
c_int,
|
|
*mut c_void,
|
|
usize,
|
|
usize,
|
|
*mut c_void,
|
|
usize,
|
|
c_int,
|
|
) -> usize;
|
|
type SingleBlockResolveOffCodesFn =
|
|
unsafe extern "C" fn(*mut u32, *mut u32, *const SeqStore_t, c_uint);
|
|
type SingleBlockCopySequencesFn =
|
|
unsafe extern "C" fn(*mut SeqCollector, *const SeqStore_t, *const u32) -> usize;
|
|
type SingleBlockConfirmFn = unsafe extern "C" fn(
|
|
*mut *mut ZSTD_compressedBlockState_t,
|
|
*mut *mut ZSTD_compressedBlockState_t,
|
|
);
|
|
type SingleBlockRawFn =
|
|
unsafe extern "C" fn(*mut c_void, usize, *const c_void, usize, c_uint) -> usize;
|
|
type SingleBlockRleFn = unsafe extern "C" fn(*mut c_void, usize, u8, usize, c_uint) -> usize;
|
|
type SingleBlockWriteHeaderFn = unsafe extern "C" fn(*mut c_void, usize, usize, c_uint);
|
|
type SingleBlockIsRleFn = unsafe extern "C" fn(*const u8, usize) -> c_int;
|
|
|
|
#[derive(Clone, Copy)]
|
|
struct SingleBlockSeams {
|
|
entropy_compress: SingleBlockEntropyFn,
|
|
resolve_off_codes: SingleBlockResolveOffCodesFn,
|
|
copy_sequences: SingleBlockCopySequencesFn,
|
|
confirm: SingleBlockConfirmFn,
|
|
raw: SingleBlockRawFn,
|
|
rle: SingleBlockRleFn,
|
|
write_header: SingleBlockWriteHeaderFn,
|
|
is_rle: SingleBlockIsRleFn,
|
|
}
|
|
|
|
impl SingleBlockSeams {
|
|
fn production() -> Self {
|
|
Self {
|
|
entropy_compress: ZSTD_rust_entropyCompressSeqStore,
|
|
resolve_off_codes: ZSTD_rust_seqStore_resolveOffCodes,
|
|
copy_sequences: ZSTD_rust_copyBlockSequences,
|
|
confirm: ZSTD_rust_confirmRepcodesAndEntropyTables,
|
|
raw: ZSTD_rust_noCompressBlock,
|
|
rle: ZSTD_rust_rleCompressBlock,
|
|
write_header: ZSTD_rust_writeBlockHeader,
|
|
is_rle: ZSTD_rust_isRLE,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Rust-owned sequence-store branch policy. C callbacks perform the
|
|
/// operations which need the private matchfinder or CCtx
|
|
/// parameter/function-pointer state.
|
|
#[allow(clippy::too_many_arguments)]
|
|
unsafe fn build_seq_store_select_sequences_body_with(
|
|
state: &ZSTD_rust_buildSeqStoreState,
|
|
seq_store: *mut SeqStore_t,
|
|
next_rep: *mut u32,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
seq_store_complete: &mut c_int,
|
|
) -> usize {
|
|
if seq_store.is_null()
|
|
|| next_rep.is_null()
|
|
|| src.is_null()
|
|
|| state.compress_external_sequences as usize == 0
|
|
|| state.compress_ldm as usize == 0
|
|
|| state.try_external_sequence_producer as usize == 0
|
|
|| state.compress_internal as usize == 0
|
|
|| state.clear_ldm_seq_store as usize == 0
|
|
{
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
*seq_store_complete = 0;
|
|
if state.has_external_sequences != 0 {
|
|
if state.has_external_sequence_producer != 0 {
|
|
return ERROR(ZstdErrorCode::ParameterCombinationUnsupported);
|
|
}
|
|
return unsafe {
|
|
(state.compress_external_sequences)(
|
|
state.callback_context,
|
|
seq_store,
|
|
next_rep,
|
|
src,
|
|
src_size,
|
|
)
|
|
};
|
|
}
|
|
|
|
if state.ldm_enabled != 0 {
|
|
if state.has_external_sequence_producer != 0 {
|
|
return ERROR(ZstdErrorCode::ParameterCombinationUnsupported);
|
|
}
|
|
return unsafe {
|
|
(state.compress_ldm)(state.callback_context, seq_store, next_rep, src, src_size)
|
|
};
|
|
}
|
|
|
|
if state.has_external_sequence_producer != 0 {
|
|
let mut allow_fallback = 0;
|
|
let producer_result = unsafe {
|
|
(state.try_external_sequence_producer)(
|
|
state.callback_context,
|
|
src,
|
|
src_size,
|
|
seq_store_complete,
|
|
&mut allow_fallback,
|
|
)
|
|
};
|
|
if *seq_store_complete != 0 {
|
|
unsafe { (state.clear_ldm_seq_store)(state.callback_context) };
|
|
return producer_result;
|
|
}
|
|
if allow_fallback == 0 || state.enable_match_finder_fallback == 0 {
|
|
return producer_result;
|
|
}
|
|
unsafe { (state.clear_ldm_seq_store)(state.callback_context) };
|
|
return unsafe {
|
|
(state.compress_internal)(state.callback_context, seq_store, next_rep, src, src_size)
|
|
};
|
|
}
|
|
|
|
unsafe { (state.clear_ldm_seq_store)(state.callback_context) };
|
|
unsafe { (state.compress_internal)(state.callback_context, seq_store, next_rep, src, src_size) }
|
|
}
|
|
|
|
/// Rust-owned sequence-store boundary. C callbacks perform the operations
|
|
/// which need the private matchfinder or CCtx parameter/function-pointer state.
|
|
#[allow(clippy::too_many_arguments)]
|
|
unsafe fn build_seq_store_body_with(
|
|
state: &ZSTD_rust_buildSeqStoreState,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
) -> usize {
|
|
if state.seq_store.is_null()
|
|
|| state.skip_small_block as usize == 0
|
|
|| state.prepare_match_state as usize == 0
|
|
{
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
if src_size > ZSTD_BLOCKSIZE_MAX {
|
|
return ERROR(ZstdErrorCode::SrcSizeWrong);
|
|
}
|
|
|
|
if src_size < MIN_COMPRESSIBLE_BLOCK_SIZE {
|
|
unsafe { (state.skip_small_block)(state.callback_context, src_size) };
|
|
return ZSTD_BSS_NO_COMPRESS as usize;
|
|
}
|
|
|
|
if src.is_null() || state.prev_c_block.is_null() || state.next_c_block.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
let prev_c_block = unsafe { *state.prev_c_block };
|
|
let next_c_block = unsafe { *state.next_c_block };
|
|
if prev_c_block.is_null() || next_c_block.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
unsafe { ZSTD_rust_resetSeqStore(state.seq_store) };
|
|
unsafe {
|
|
(state.prepare_match_state)(state.callback_context, src, src_size);
|
|
}
|
|
|
|
unsafe {
|
|
ptr::copy_nonoverlapping(
|
|
(*prev_c_block).rep.as_ptr(),
|
|
(*next_c_block).rep.as_mut_ptr(),
|
|
ZSTD_REP_NUM,
|
|
);
|
|
}
|
|
|
|
let mut seq_store_complete = 0;
|
|
let last_literals_size = unsafe {
|
|
build_seq_store_select_sequences_body_with(
|
|
state,
|
|
state.seq_store,
|
|
(*next_c_block).rep.as_mut_ptr(),
|
|
src,
|
|
src_size,
|
|
&mut seq_store_complete,
|
|
)
|
|
};
|
|
if ERR_isError(last_literals_size) {
|
|
return last_literals_size;
|
|
}
|
|
if seq_store_complete != 0 {
|
|
return ZSTD_BSS_COMPRESS as usize;
|
|
}
|
|
if last_literals_size > src_size {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
unsafe {
|
|
ZSTD_rust_storeLastLiterals(
|
|
state.seq_store,
|
|
src.cast::<u8>().add(src_size - last_literals_size),
|
|
last_literals_size,
|
|
);
|
|
}
|
|
if state.validate_seq_store != 0 {
|
|
unsafe { ZSTD_rust_validateSeqStore(state.seq_store, state.min_match) };
|
|
}
|
|
ZSTD_BSS_COMPRESS as usize
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_buildSeqStore(
|
|
state: *const ZSTD_rust_buildSeqStoreState,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
unsafe { build_seq_store_body_with(&*state, src, src_size) }
|
|
}
|
|
|
|
/// Rust implementation of `ZSTD_compressBlock_internal` after the C caller
|
|
/// has built the sequence store.
|
|
#[allow(clippy::too_many_arguments)]
|
|
unsafe fn compress_block_internal_body_with(
|
|
state: &ZSTD_rust_blockInternalState,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
frame: c_uint,
|
|
seams: SingleBlockSeams,
|
|
) -> usize {
|
|
const RLE_MAX_LENGTH: usize = 25;
|
|
const FSE_REPEAT_CHECK: c_int = 1;
|
|
const FSE_REPEAT_VALID: c_int = 2;
|
|
|
|
if state.seq_store.is_null()
|
|
|| state.prev_c_block.is_null()
|
|
|| state.next_c_block.is_null()
|
|
|| state.seq_collector.is_null()
|
|
{
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
let prev_c_block = unsafe { *state.prev_c_block };
|
|
let next_c_block = unsafe { *state.next_c_block };
|
|
if prev_c_block.is_null() || next_c_block.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
if unsafe { (*state.seq_collector).collectSequences } != 0 {
|
|
let result = unsafe {
|
|
(seams.copy_sequences)(
|
|
state.seq_collector,
|
|
state.seq_store,
|
|
(*prev_c_block).rep.as_ptr(),
|
|
)
|
|
};
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
unsafe { (seams.confirm)(state.prev_c_block, state.next_c_block) };
|
|
return 0;
|
|
}
|
|
|
|
let mut c_size = unsafe {
|
|
(seams.entropy_compress)(
|
|
state.seq_store,
|
|
ptr::addr_of!((*prev_c_block).entropy),
|
|
ptr::addr_of_mut!((*next_c_block).entropy),
|
|
state.strategy,
|
|
state.disable_literal_compression,
|
|
dst,
|
|
dst_capacity,
|
|
src_size,
|
|
state.tmp_workspace,
|
|
state.tmp_wksp_size,
|
|
state.bmi2,
|
|
)
|
|
};
|
|
|
|
if frame != 0
|
|
&& state.is_first_block == 0
|
|
&& c_size < RLE_MAX_LENGTH
|
|
&& unsafe { (seams.is_rle)(src.cast(), src_size) } != 0
|
|
{
|
|
/* Preserve the legacy decoder compatibility rule for the first frame
|
|
* block: later repeated blocks may use the one-byte RLE form. */
|
|
c_size = 1;
|
|
unsafe {
|
|
*dst.cast::<u8>() = *src.cast::<u8>();
|
|
}
|
|
}
|
|
|
|
if !ERR_isError(c_size) && c_size > 1 {
|
|
unsafe { (seams.confirm)(state.prev_c_block, state.next_c_block) };
|
|
}
|
|
|
|
let prev_c_block = unsafe { *state.prev_c_block };
|
|
if prev_c_block.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
if unsafe { (*prev_c_block).entropy.fse.offcode_repeatMode } == FSE_REPEAT_VALID {
|
|
unsafe { (*prev_c_block).entropy.fse.offcode_repeatMode = FSE_REPEAT_CHECK };
|
|
}
|
|
|
|
c_size
|
|
}
|
|
|
|
unsafe fn compress_block_internal_after_build_body_with(
|
|
state: &ZSTD_rust_blockInternalState,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
frame: c_uint,
|
|
bss: c_int,
|
|
seams: SingleBlockSeams,
|
|
) -> usize {
|
|
if bss == ZSTD_BSS_NO_COMPRESS {
|
|
if state.seq_collector.is_null() || state.prev_c_block.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
if unsafe { (*state.seq_collector).collectSequences } != 0 {
|
|
return ERROR(ZstdErrorCode::SequenceProducerFailed);
|
|
}
|
|
let prev_c_block = unsafe { *state.prev_c_block };
|
|
if prev_c_block.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
if unsafe { (*prev_c_block).entropy.fse.offcode_repeatMode } == FSE_REPEAT_VALID {
|
|
unsafe { (*prev_c_block).entropy.fse.offcode_repeatMode = FSE_REPEAT_CHECK };
|
|
}
|
|
return 0;
|
|
}
|
|
if bss != ZSTD_BSS_COMPRESS {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
unsafe {
|
|
compress_block_internal_body_with(state, dst, dst_capacity, src, src_size, frame, seams)
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_compressBlockInternal(
|
|
state: *const ZSTD_rust_blockInternalState,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
frame: c_uint,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
unsafe {
|
|
compress_block_internal_body_with(
|
|
&*state,
|
|
dst,
|
|
dst_capacity,
|
|
src,
|
|
src_size,
|
|
frame,
|
|
SingleBlockSeams::production(),
|
|
)
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_compressBlockInternalAfterBuild(
|
|
state: *const ZSTD_rust_blockInternalState,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
frame: c_uint,
|
|
bss: c_int,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
unsafe {
|
|
compress_block_internal_after_build_body_with(
|
|
&*state,
|
|
dst,
|
|
dst_capacity,
|
|
src,
|
|
src_size,
|
|
frame,
|
|
bss,
|
|
SingleBlockSeams::production(),
|
|
)
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn sequence_block_action(
|
|
is_first_block: c_int,
|
|
maybe_rle: c_int,
|
|
is_rle: c_int,
|
|
compressed_size: usize,
|
|
) -> SequenceBlockAction {
|
|
if is_first_block == 0 && maybe_rle != 0 && is_rle != 0 {
|
|
return SequenceBlockAction::Rle;
|
|
}
|
|
match compressed_size {
|
|
0 => SequenceBlockAction::Raw,
|
|
1 => SequenceBlockAction::Rle,
|
|
_ => SequenceBlockAction::Compressed,
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn target_c_block_size_action(
|
|
bss: c_int,
|
|
is_first_block: c_int,
|
|
maybe_rle: c_int,
|
|
is_rle: c_int,
|
|
c_size: usize,
|
|
src_size: usize,
|
|
strategy: c_int,
|
|
) -> TargetCBlockAction {
|
|
if bss != ZSTD_TARGET_CBLOCK_BSS_COMPRESS {
|
|
return TargetCBlockAction::Raw;
|
|
}
|
|
if is_first_block == 0 && maybe_rle != 0 && is_rle != 0 {
|
|
return TargetCBlockAction::Rle;
|
|
}
|
|
if c_size == 0 || c_size == ERROR(ZstdErrorCode::DstSizeTooSmall) {
|
|
return TargetCBlockAction::Raw;
|
|
}
|
|
if ERR_isError(c_size) {
|
|
return TargetCBlockAction::Error;
|
|
}
|
|
|
|
let max_c_size = src_size.wrapping_sub(min_gain(src_size, strategy));
|
|
if c_size < max_c_size.wrapping_add(ZSTD_BLOCK_HEADER_SIZE) {
|
|
TargetCBlockAction::Compressed
|
|
} else {
|
|
TargetCBlockAction::Raw
|
|
}
|
|
}
|
|
|
|
type TargetCBlockSuperBlockFn = unsafe extern "C" fn(
|
|
*const c_void,
|
|
*const c_void,
|
|
*mut c_void,
|
|
c_int,
|
|
c_int,
|
|
*mut c_void,
|
|
usize,
|
|
c_int,
|
|
c_uint,
|
|
usize,
|
|
*mut c_void,
|
|
usize,
|
|
*const c_void,
|
|
usize,
|
|
c_uint,
|
|
) -> usize;
|
|
|
|
/// Rust implementation of `ZSTD_compressBlock_targetCBlockSize_body()`.
|
|
///
|
|
/// The C caller has already built the sequence store. This function owns
|
|
/// only the RLE/superblock/raw decision and leaves context construction,
|
|
/// matchfinder state, frame progress, and outer repeat-mode cleanup in C.
|
|
#[allow(clippy::too_many_arguments)]
|
|
unsafe fn compress_block_target_c_block_size_body_with(
|
|
state: &ZSTD_rust_targetCBlockSizeState,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
bss: c_int,
|
|
last_block: c_uint,
|
|
compress_super_block: TargetCBlockSuperBlockFn,
|
|
) -> usize {
|
|
if state.seq_store.is_null() || state.prev_c_block.is_null() || state.next_c_block.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
let prev_c_block = unsafe { *state.prev_c_block };
|
|
let next_c_block = unsafe { *state.next_c_block };
|
|
if prev_c_block.is_null() || next_c_block.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
let is_compress = bss == ZSTD_TARGET_CBLOCK_BSS_COMPRESS;
|
|
let (maybe_rle, is_rle) = if is_compress {
|
|
let maybe_rle = unsafe { ZSTD_rust_maybeRLE(state.seq_store) };
|
|
let is_rle = unsafe { ZSTD_rust_isRLE(src.cast(), src_size) };
|
|
(maybe_rle, is_rle)
|
|
} else {
|
|
(0, 0)
|
|
};
|
|
|
|
let action = target_c_block_size_action(
|
|
bss,
|
|
state.is_first_block,
|
|
maybe_rle,
|
|
is_rle,
|
|
0,
|
|
src_size,
|
|
state.strategy,
|
|
);
|
|
if action == TargetCBlockAction::Rle {
|
|
return unsafe {
|
|
ZSTD_rust_rleCompressBlock(dst, dst_capacity, *src.cast::<u8>(), src_size, last_block)
|
|
};
|
|
}
|
|
|
|
if is_compress {
|
|
/* The superblock result is not bounded by ZSTD_compressBound(). The
|
|
* policy helper therefore falls back to a raw block for zero,
|
|
* dstSize_tooSmall, or an expansion beyond blockBound(srcSize). */
|
|
let c_size = unsafe {
|
|
compress_super_block(
|
|
state.seq_store.cast(),
|
|
prev_c_block.cast(),
|
|
next_c_block.cast(),
|
|
state.strategy,
|
|
state.disable_literal_compression,
|
|
state.tmp_workspace,
|
|
state.tmp_wksp_size,
|
|
state.bmi2,
|
|
state.window_log,
|
|
state.target_c_block_size,
|
|
dst,
|
|
dst_capacity,
|
|
src,
|
|
src_size,
|
|
last_block,
|
|
)
|
|
};
|
|
let action = target_c_block_size_action(
|
|
bss,
|
|
state.is_first_block,
|
|
0,
|
|
0,
|
|
c_size,
|
|
src_size,
|
|
state.strategy,
|
|
);
|
|
if action == TargetCBlockAction::Error {
|
|
return c_size;
|
|
}
|
|
if action == TargetCBlockAction::Compressed {
|
|
unsafe {
|
|
ZSTD_rust_confirmRepcodesAndEntropyTables(state.prev_c_block, state.next_c_block);
|
|
}
|
|
return c_size;
|
|
}
|
|
}
|
|
|
|
unsafe { ZSTD_rust_noCompressBlock(dst, dst_capacity, src, src_size, last_block) }
|
|
}
|
|
|
|
unsafe fn compress_block_target_c_block_size_after_build_body_with(
|
|
state: &ZSTD_rust_targetCBlockSizeState,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
bss: c_int,
|
|
last_block: c_uint,
|
|
compress_super_block: TargetCBlockSuperBlockFn,
|
|
) -> usize {
|
|
if bss != ZSTD_BSS_COMPRESS && bss != ZSTD_BSS_NO_COMPRESS {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let c_size = unsafe {
|
|
compress_block_target_c_block_size_body_with(
|
|
state,
|
|
dst,
|
|
dst_capacity,
|
|
src,
|
|
src_size,
|
|
bss,
|
|
last_block,
|
|
compress_super_block,
|
|
)
|
|
};
|
|
if ERR_isError(c_size) {
|
|
return c_size;
|
|
}
|
|
if state.prev_c_block.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let prev_c_block = unsafe { *state.prev_c_block };
|
|
if prev_c_block.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
if unsafe { (*prev_c_block).entropy.fse.offcode_repeatMode } == FSE_REPEAT_VALID {
|
|
unsafe { (*prev_c_block).entropy.fse.offcode_repeatMode = FSE_REPEAT_CHECK };
|
|
}
|
|
c_size
|
|
}
|
|
|
|
/// C ABI entry point for the target-sized block body. The public and outer
|
|
/// block APIs remain C-owned; this is only the body after `ZSTD_buildSeqStore`.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_compressBlockTargetCBlockSize(
|
|
state: *const ZSTD_rust_targetCBlockSizeState,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
bss: c_int,
|
|
last_block: c_uint,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
unsafe {
|
|
compress_block_target_c_block_size_body_with(
|
|
&*state,
|
|
dst,
|
|
dst_capacity,
|
|
src,
|
|
src_size,
|
|
bss,
|
|
last_block,
|
|
ZSTD_rust_compressSuperBlock,
|
|
)
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_compressBlockTargetCBlockSizeAfterBuild(
|
|
state: *const ZSTD_rust_targetCBlockSizeState,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
bss: c_int,
|
|
last_block: c_uint,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
unsafe {
|
|
compress_block_target_c_block_size_after_build_body_with(
|
|
&*state,
|
|
dst,
|
|
dst_capacity,
|
|
src,
|
|
src_size,
|
|
bss,
|
|
last_block,
|
|
ZSTD_rust_compressSuperBlock,
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Rust implementation of `ZSTD_compressSeqStore_singleBlock`.
|
|
///
|
|
/// The C caller still owns sequence-store construction, split-block control,
|
|
/// and the private compression context. This body owns the exact block
|
|
/// decision and the associated state transitions after the sequence store is
|
|
/// ready.
|
|
#[allow(clippy::too_many_arguments)]
|
|
unsafe fn compress_seq_store_single_block_body_with(
|
|
state: &ZSTD_rust_seqStoreSingleBlockState,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
last_block: c_uint,
|
|
is_partition: c_uint,
|
|
seams: SingleBlockSeams,
|
|
) -> usize {
|
|
const RLE_MAX_LENGTH: usize = 25;
|
|
|
|
if state.seq_store.is_null()
|
|
|| state.d_rep.is_null()
|
|
|| state.c_rep.is_null()
|
|
|| state.prev_c_block.is_null()
|
|
|| state.next_c_block.is_null()
|
|
|| state.seq_collector.is_null()
|
|
{
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
let prev_c_block = unsafe { *state.prev_c_block };
|
|
let next_c_block = unsafe { *state.next_c_block };
|
|
if prev_c_block.is_null() || next_c_block.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
/* Preserve the decompression-side history for raw/RLE output and for the
|
|
* sequence collector, exactly as the original C body does. */
|
|
let mut d_rep_original = [0u32; ZSTD_REP_NUM];
|
|
unsafe {
|
|
ptr::copy_nonoverlapping(state.d_rep, d_rep_original.as_mut_ptr(), ZSTD_REP_NUM);
|
|
}
|
|
|
|
if is_partition != 0 {
|
|
let seq_store = unsafe { &*state.seq_store };
|
|
let nb_seq = unsafe { seq_store.sequences.offset_from(seq_store.sequencesStart) } as c_uint;
|
|
unsafe {
|
|
(seams.resolve_off_codes)(state.d_rep, state.c_rep, state.seq_store, nb_seq);
|
|
}
|
|
}
|
|
|
|
if dst_capacity < ZSTD_BLOCK_HEADER_SIZE {
|
|
return ERROR(ZstdErrorCode::DstSizeTooSmall);
|
|
}
|
|
|
|
let op = dst.cast::<u8>();
|
|
let ip = src.cast::<u8>();
|
|
let mut compressed_sequences_size = unsafe {
|
|
(seams.entropy_compress)(
|
|
state.seq_store,
|
|
ptr::addr_of!((*prev_c_block).entropy),
|
|
ptr::addr_of_mut!((*next_c_block).entropy),
|
|
state.strategy,
|
|
state.disable_literal_compression,
|
|
op.add(ZSTD_BLOCK_HEADER_SIZE).cast(),
|
|
dst_capacity - ZSTD_BLOCK_HEADER_SIZE,
|
|
src_size,
|
|
state.tmp_workspace,
|
|
state.tmp_wksp_size,
|
|
state.bmi2,
|
|
)
|
|
};
|
|
if ERR_isError(compressed_sequences_size) {
|
|
return compressed_sequences_size;
|
|
}
|
|
|
|
if state.is_first_block == 0
|
|
&& compressed_sequences_size < RLE_MAX_LENGTH
|
|
&& unsafe { (seams.is_rle)(ip, src_size) } != 0
|
|
{
|
|
/* Do not emit the first frame block as RLE; this preserves the legacy
|
|
* decoder compatibility rule in the original implementation. */
|
|
compressed_sequences_size = 1;
|
|
}
|
|
|
|
/* Sequence collection is deliberately before block serialization and
|
|
* returns without the outer repeat-mode cleanup, matching C. */
|
|
if unsafe { (*state.seq_collector).collectSequences } != 0 {
|
|
let result = unsafe {
|
|
(seams.copy_sequences)(
|
|
state.seq_collector,
|
|
state.seq_store,
|
|
d_rep_original.as_ptr(),
|
|
)
|
|
};
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
unsafe { (seams.confirm)(state.prev_c_block, state.next_c_block) };
|
|
return 0;
|
|
}
|
|
|
|
let c_size = if compressed_sequences_size == 0 {
|
|
let c_size = unsafe { (seams.raw)(dst, dst_capacity, src, src_size, last_block) };
|
|
if ERR_isError(c_size) {
|
|
return c_size;
|
|
}
|
|
unsafe {
|
|
ptr::copy_nonoverlapping(d_rep_original.as_ptr(), state.d_rep, ZSTD_REP_NUM);
|
|
}
|
|
c_size
|
|
} else if compressed_sequences_size == 1 {
|
|
let c_size = unsafe { (seams.rle)(dst, dst_capacity, *ip, src_size, last_block) };
|
|
if ERR_isError(c_size) {
|
|
return c_size;
|
|
}
|
|
unsafe {
|
|
ptr::copy_nonoverlapping(d_rep_original.as_ptr(), state.d_rep, ZSTD_REP_NUM);
|
|
}
|
|
c_size
|
|
} else {
|
|
unsafe { (seams.confirm)(state.prev_c_block, state.next_c_block) };
|
|
unsafe {
|
|
(seams.write_header)(dst, compressed_sequences_size, src_size, last_block);
|
|
}
|
|
ZSTD_BLOCK_HEADER_SIZE.wrapping_add(compressed_sequences_size)
|
|
};
|
|
|
|
let prev_c_block = unsafe { *state.prev_c_block };
|
|
if unsafe { (*prev_c_block).entropy.fse.offcode_repeatMode } == 2 {
|
|
unsafe { (*prev_c_block).entropy.fse.offcode_repeatMode = 1 };
|
|
}
|
|
|
|
c_size
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_compressSeqStoreSingleBlock(
|
|
state: *const ZSTD_rust_seqStoreSingleBlockState,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
last_block: c_uint,
|
|
is_partition: c_uint,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
unsafe {
|
|
compress_seq_store_single_block_body_with(
|
|
&*state,
|
|
dst,
|
|
dst_capacity,
|
|
src,
|
|
src_size,
|
|
last_block,
|
|
is_partition,
|
|
SingleBlockSeams::production(),
|
|
)
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn split_single_block_state(
|
|
split_state: &ZSTD_rust_splitBlockState,
|
|
seq_store: *const SeqStore_t,
|
|
d_rep: &mut [u32; ZSTD_REP_NUM],
|
|
c_rep: &mut [u32; ZSTD_REP_NUM],
|
|
) -> ZSTD_rust_seqStoreSingleBlockState {
|
|
ZSTD_rust_seqStoreSingleBlockState {
|
|
seq_store,
|
|
d_rep: d_rep.as_mut_ptr(),
|
|
c_rep: c_rep.as_mut_ptr(),
|
|
prev_c_block: split_state.prev_c_block,
|
|
next_c_block: split_state.next_c_block,
|
|
tmp_workspace: split_state.tmp_workspace,
|
|
tmp_wksp_size: split_state.tmp_wksp_size,
|
|
seq_collector: split_state.seq_collector,
|
|
strategy: split_state.strategy,
|
|
disable_literal_compression: split_state.disable_literal_compression,
|
|
bmi2: split_state.bmi2,
|
|
is_first_block: split_state.is_first_block,
|
|
}
|
|
}
|
|
|
|
/// Rust implementation of the post-split partition loop.
|
|
///
|
|
/// C retains the private `ZSTD_CCtx` and block-split storage; Rust receives
|
|
/// projected estimator inputs alongside the sequence-store views and owns the
|
|
/// partition accounting, repcode simulation, and block emission.
|
|
#[allow(clippy::too_many_arguments)]
|
|
unsafe fn compress_block_split_body_with(
|
|
state: &ZSTD_rust_splitBlockState,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
block_size: usize,
|
|
last_block: c_uint,
|
|
num_splits: usize,
|
|
seams: SingleBlockSeams,
|
|
) -> usize {
|
|
if state.seq_store.is_null()
|
|
|| state.partitions.is_null()
|
|
|| state.next_seq_store.is_null()
|
|
|| state.curr_seq_store.is_null()
|
|
|| state.prev_c_block.is_null()
|
|
|| state.next_c_block.is_null()
|
|
|| state.seq_collector.is_null()
|
|
{
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
let prev_c_block = unsafe { *state.prev_c_block };
|
|
let next_c_block = unsafe { *state.next_c_block };
|
|
if prev_c_block.is_null() || next_c_block.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
/* cRep and dRep start from the preceding block and diverge only when a
|
|
* partition is emitted as raw or RLE. */
|
|
let mut d_rep = [0u32; ZSTD_REP_NUM];
|
|
unsafe {
|
|
ptr::copy_nonoverlapping(
|
|
(*prev_c_block).rep.as_ptr(),
|
|
d_rep.as_mut_ptr(),
|
|
ZSTD_REP_NUM,
|
|
);
|
|
}
|
|
let mut c_rep = d_rep;
|
|
unsafe {
|
|
ptr::write_bytes(
|
|
state.next_seq_store.cast::<u8>(),
|
|
0,
|
|
size_of::<SeqStore_t>(),
|
|
);
|
|
}
|
|
|
|
if num_splits == 0 {
|
|
let single_state = split_single_block_state(state, state.seq_store, &mut d_rep, &mut c_rep);
|
|
let c_size = unsafe {
|
|
compress_seq_store_single_block_body_with(
|
|
&single_state,
|
|
dst,
|
|
dst_capacity,
|
|
src,
|
|
block_size,
|
|
last_block,
|
|
0,
|
|
seams,
|
|
)
|
|
};
|
|
debug_assert!(state.block_size_max <= ZSTD_BLOCKSIZE_MAX);
|
|
debug_assert!(
|
|
c_size <= state.block_size_max.wrapping_add(ZSTD_BLOCK_HEADER_SIZE)
|
|
|| ERR_isError(c_size)
|
|
);
|
|
return c_size;
|
|
}
|
|
|
|
unsafe {
|
|
ZSTD_rust_deriveSeqStoreChunk(
|
|
state.curr_seq_store,
|
|
state.seq_store,
|
|
0,
|
|
*state.partitions as usize,
|
|
);
|
|
}
|
|
|
|
let mut c_size = 0usize;
|
|
let mut src_bytes_total = 0usize;
|
|
let mut ip = src.cast::<u8>();
|
|
let mut op = dst.cast::<u8>();
|
|
let mut remaining_capacity = dst_capacity;
|
|
|
|
for i in 0..=num_splits {
|
|
let last_partition = i == num_splits;
|
|
let mut last_block_entire_src = 0;
|
|
let partition_seq_store = state.curr_seq_store as *const SeqStore_t;
|
|
let partition_bytes = unsafe {
|
|
ZSTD_rust_countSeqStoreLiteralsBytes(partition_seq_store)
|
|
.wrapping_add(ZSTD_rust_countSeqStoreMatchBytes(partition_seq_store))
|
|
};
|
|
src_bytes_total = src_bytes_total.wrapping_add(partition_bytes);
|
|
let src_bytes = if last_partition {
|
|
last_block_entire_src = last_block;
|
|
partition_bytes.wrapping_add(block_size.wrapping_sub(src_bytes_total))
|
|
} else {
|
|
unsafe {
|
|
ZSTD_rust_deriveSeqStoreChunk(
|
|
state.next_seq_store,
|
|
state.seq_store,
|
|
*state.partitions.add(i) as usize,
|
|
*state.partitions.add(i + 1) as usize,
|
|
);
|
|
}
|
|
partition_bytes
|
|
};
|
|
|
|
let single_state =
|
|
split_single_block_state(state, partition_seq_store, &mut d_rep, &mut c_rep);
|
|
let c_size_chunk = unsafe {
|
|
compress_seq_store_single_block_body_with(
|
|
&single_state,
|
|
op.cast(),
|
|
remaining_capacity,
|
|
ip.cast(),
|
|
src_bytes,
|
|
last_block_entire_src,
|
|
1,
|
|
seams,
|
|
)
|
|
};
|
|
if ERR_isError(c_size_chunk) {
|
|
return c_size_chunk;
|
|
}
|
|
|
|
unsafe {
|
|
ip = ip.add(src_bytes);
|
|
op = op.add(c_size_chunk);
|
|
}
|
|
remaining_capacity = remaining_capacity.wrapping_sub(c_size_chunk);
|
|
c_size = c_size.wrapping_add(c_size_chunk);
|
|
unsafe {
|
|
ptr::copy_nonoverlapping(state.next_seq_store, state.curr_seq_store, 1);
|
|
}
|
|
debug_assert!(c_size_chunk <= state.block_size_max.wrapping_add(ZSTD_BLOCK_HEADER_SIZE));
|
|
}
|
|
|
|
let final_prev_c_block = unsafe { *state.prev_c_block };
|
|
if final_prev_c_block.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
unsafe {
|
|
ptr::copy_nonoverlapping(
|
|
d_rep.as_ptr(),
|
|
(*final_prev_c_block).rep.as_mut_ptr(),
|
|
ZSTD_REP_NUM,
|
|
);
|
|
}
|
|
c_size
|
|
}
|
|
|
|
unsafe fn compress_block_split_after_build_body_with(
|
|
state: &ZSTD_rust_splitBlockState,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
block_size: usize,
|
|
last_block: c_uint,
|
|
num_splits: usize,
|
|
bss: c_int,
|
|
seams: SingleBlockSeams,
|
|
) -> usize {
|
|
if bss == ZSTD_BSS_NO_COMPRESS {
|
|
if state.seq_collector.is_null() || state.prev_c_block.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
if unsafe { (*state.seq_collector).collectSequences } != 0 {
|
|
return ERROR(ZstdErrorCode::SequenceProducerFailed);
|
|
}
|
|
let prev_c_block = unsafe { *state.prev_c_block };
|
|
if prev_c_block.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
if unsafe { (*prev_c_block).entropy.fse.offcode_repeatMode } == FSE_REPEAT_VALID {
|
|
unsafe { (*prev_c_block).entropy.fse.offcode_repeatMode = FSE_REPEAT_CHECK };
|
|
}
|
|
return unsafe {
|
|
ZSTD_rust_noCompressBlock(dst, dst_capacity, src, block_size, last_block)
|
|
};
|
|
}
|
|
if bss != ZSTD_BSS_COMPRESS {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
unsafe {
|
|
compress_block_split_body_with(
|
|
state,
|
|
dst,
|
|
dst_capacity,
|
|
src,
|
|
block_size,
|
|
last_block,
|
|
num_splits,
|
|
seams,
|
|
)
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_compressBlockSplit(
|
|
state: *const ZSTD_rust_splitBlockState,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
block_size: usize,
|
|
last_block: c_uint,
|
|
num_splits: usize,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
unsafe {
|
|
compress_block_split_body_with(
|
|
&*state,
|
|
dst,
|
|
dst_capacity,
|
|
src,
|
|
block_size,
|
|
last_block,
|
|
num_splits,
|
|
SingleBlockSeams::production(),
|
|
)
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_compressBlockSplitAfterBuild(
|
|
state: *const ZSTD_rust_splitBlockState,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
block_size: usize,
|
|
last_block: c_uint,
|
|
num_splits: usize,
|
|
bss: c_int,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
unsafe {
|
|
compress_block_split_after_build_body_with(
|
|
&*state,
|
|
dst,
|
|
dst_capacity,
|
|
src,
|
|
block_size,
|
|
last_block,
|
|
num_splits,
|
|
bss,
|
|
SingleBlockSeams::production(),
|
|
)
|
|
}
|
|
}
|
|
|
|
unsafe fn store_converted_sequence(
|
|
seq_store: &mut SeqStore_t,
|
|
lit_length: u32,
|
|
off_base: u32,
|
|
match_length: u32,
|
|
) -> bool {
|
|
let sequence_index = unsafe { seq_store.sequences.offset_from(seq_store.sequencesStart) };
|
|
if sequence_index < 0 || sequence_index as usize >= seq_store.maxNbSeq {
|
|
return false;
|
|
}
|
|
let ml_base = match (match_length as usize).checked_sub(MINMATCH) {
|
|
Some(value) => value,
|
|
None => return false,
|
|
};
|
|
|
|
if lit_length as usize > u16::MAX as usize {
|
|
if seq_store.longLengthType != 0 {
|
|
return false;
|
|
}
|
|
seq_store.longLengthType = ZSTD_LLT_LITERAL_LENGTH;
|
|
seq_store.longLengthPos = sequence_index as u32;
|
|
}
|
|
if ml_base > u16::MAX as usize {
|
|
if seq_store.longLengthType != 0 {
|
|
return false;
|
|
}
|
|
seq_store.longLengthType = ZSTD_LLT_MATCH_LENGTH;
|
|
seq_store.longLengthPos = sequence_index as u32;
|
|
}
|
|
|
|
unsafe {
|
|
(*seq_store.sequences).litLength = lit_length as u16;
|
|
(*seq_store.sequences).offBase = off_base;
|
|
(*seq_store.sequences).mlBase = ml_base as u16;
|
|
seq_store.sequences = seq_store.sequences.add(1);
|
|
}
|
|
true
|
|
}
|
|
|
|
unsafe fn convert_block_sequences_body_with(
|
|
state: &ZSTD_rust_convertBlockSequencesState,
|
|
in_seqs: *const ZSTD_Sequence,
|
|
nb_sequences: usize,
|
|
repcode_resolution: c_int,
|
|
) -> usize {
|
|
if state.seq_store.is_null()
|
|
|| state.prev_c_block.is_null()
|
|
|| state.next_c_block.is_null()
|
|
|| in_seqs.is_null()
|
|
|| nb_sequences == 0
|
|
{
|
|
return ERROR(ZstdErrorCode::ExternalSequencesInvalid);
|
|
}
|
|
|
|
let seq_store = unsafe { &mut *state.seq_store };
|
|
if nb_sequences >= seq_store.maxNbSeq {
|
|
return ERROR(ZstdErrorCode::ExternalSequencesInvalid);
|
|
}
|
|
let delimiter = unsafe { *in_seqs.add(nb_sequences - 1) };
|
|
if delimiter.matchLength != 0 || delimiter.offset != 0 {
|
|
return ERROR(ZstdErrorCode::ExternalSequencesInvalid);
|
|
}
|
|
|
|
let prev_c_block = unsafe { *state.prev_c_block };
|
|
let next_c_block = unsafe { *state.next_c_block };
|
|
if prev_c_block.is_null() || next_c_block.is_null() {
|
|
return ERROR(ZstdErrorCode::ExternalSequencesInvalid);
|
|
}
|
|
|
|
let mut updated_repcodes = [0u32; ZSTD_REP_NUM];
|
|
unsafe {
|
|
ptr::copy_nonoverlapping(
|
|
(*prev_c_block).rep.as_ptr(),
|
|
updated_repcodes.as_mut_ptr(),
|
|
ZSTD_REP_NUM,
|
|
);
|
|
}
|
|
|
|
if repcode_resolution == 0 {
|
|
let full_sequence_count = nb_sequences - 1;
|
|
let long_length = unsafe {
|
|
ZSTD_rust_convertSequencesNoRepcodes(
|
|
seq_store.sequencesStart,
|
|
in_seqs,
|
|
full_sequence_count,
|
|
)
|
|
};
|
|
if long_length != 0 {
|
|
if seq_store.longLengthType != 0 {
|
|
return ERROR(ZstdErrorCode::ExternalSequencesInvalid);
|
|
}
|
|
if long_length <= full_sequence_count {
|
|
seq_store.longLengthType = ZSTD_LLT_MATCH_LENGTH;
|
|
seq_store.longLengthPos = (long_length - 1) as u32;
|
|
} else if long_length <= full_sequence_count.saturating_mul(2) {
|
|
seq_store.longLengthType = ZSTD_LLT_LITERAL_LENGTH;
|
|
seq_store.longLengthPos = (long_length - nb_sequences) as u32;
|
|
} else {
|
|
return ERROR(ZstdErrorCode::ExternalSequencesInvalid);
|
|
}
|
|
}
|
|
seq_store.sequences = unsafe { seq_store.sequencesStart.add(full_sequence_count) };
|
|
|
|
if nb_sequences > 1 {
|
|
let rep = &mut updated_repcodes;
|
|
if nb_sequences >= 4 {
|
|
let last_seq_idx = nb_sequences - 2;
|
|
rep[2] = unsafe { (*in_seqs.add(last_seq_idx - 2)).offset };
|
|
rep[1] = unsafe { (*in_seqs.add(last_seq_idx - 1)).offset };
|
|
rep[0] = unsafe { (*in_seqs.add(last_seq_idx)).offset };
|
|
} else if nb_sequences == 3 {
|
|
rep[2] = rep[0];
|
|
rep[1] = unsafe { (*in_seqs).offset };
|
|
rep[0] = unsafe { (*in_seqs.add(1)).offset };
|
|
} else {
|
|
rep[2] = rep[1];
|
|
rep[1] = rep[0];
|
|
rep[0] = unsafe { (*in_seqs).offset };
|
|
}
|
|
}
|
|
} else {
|
|
for index in 0..(nb_sequences - 1) {
|
|
let sequence = unsafe { *in_seqs.add(index) };
|
|
let ll0 = sequence.litLength == 0;
|
|
let off_base = unsafe {
|
|
ZSTD_rust_finalizeOffBase(
|
|
sequence.offset,
|
|
updated_repcodes.as_ptr(),
|
|
u32::from(ll0),
|
|
)
|
|
};
|
|
if !unsafe {
|
|
store_converted_sequence(
|
|
seq_store,
|
|
sequence.litLength,
|
|
off_base,
|
|
sequence.matchLength,
|
|
)
|
|
} {
|
|
return ERROR(ZstdErrorCode::ExternalSequencesInvalid);
|
|
}
|
|
update_rep(&mut updated_repcodes, off_base, ll0);
|
|
}
|
|
}
|
|
|
|
unsafe {
|
|
ptr::copy_nonoverlapping(
|
|
updated_repcodes.as_ptr(),
|
|
(*next_c_block).rep.as_mut_ptr(),
|
|
ZSTD_REP_NUM,
|
|
);
|
|
}
|
|
0
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_convertBlockSequences(
|
|
state: *const ZSTD_rust_convertBlockSequencesState,
|
|
in_seqs: *const ZSTD_Sequence,
|
|
nb_sequences: usize,
|
|
repcode_resolution: c_int,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::ExternalSequencesInvalid);
|
|
}
|
|
unsafe { convert_block_sequences_body_with(&*state, in_seqs, nb_sequences, repcode_resolution) }
|
|
}
|
|
|
|
/// Select the strategy used by the simple compression entry points.
|
|
///
|
|
/// This is the Rust equivalent of the strategy portion of
|
|
/// `ZSTD_getParams_internal(..., ZSTD_cpm_noAttachDict)`: use the actual
|
|
/// source size for table selection, then apply the ordinary automatic
|
|
/// parameter adjustment before dispatching to the Rust or C frame path.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_compressCCtxStrategy(
|
|
src_size: usize,
|
|
compression_level: c_int,
|
|
) -> c_int {
|
|
let cparams = ZSTD_rust_params_selectCParams(
|
|
compression_level,
|
|
src_size as u64,
|
|
0,
|
|
ZSTD_RUST_CPM_NO_ATTACH_DICT,
|
|
);
|
|
ZSTD_rust_params_adjustCParams(
|
|
cparams,
|
|
src_size as u64,
|
|
0,
|
|
ZSTD_RUST_CPM_NO_ATTACH_DICT,
|
|
ZSTD_RUST_PS_AUTO,
|
|
)
|
|
.strategy
|
|
}
|
|
|
|
/// Classify the target-sized block policy without crossing the C context ABI.
|
|
///
|
|
/// C calls this once before the superblock attempt to classify the RLE
|
|
/// precondition, then again with the superblock result and zero RLE flags.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_targetCBlockSizeAction(
|
|
bss: c_int,
|
|
is_first_block: c_int,
|
|
maybe_rle: c_int,
|
|
is_rle: c_int,
|
|
c_size: usize,
|
|
src_size: usize,
|
|
strategy: c_int,
|
|
) -> c_int {
|
|
target_c_block_size_action(
|
|
bss,
|
|
is_first_block,
|
|
maybe_rle,
|
|
is_rle,
|
|
c_size,
|
|
src_size,
|
|
strategy,
|
|
) as c_int
|
|
}
|
|
|
|
#[repr(C)]
|
|
pub struct ZSTD_inBuffer {
|
|
pub src: *const c_void,
|
|
pub size: usize,
|
|
pub pos: usize,
|
|
}
|
|
|
|
#[repr(C)]
|
|
pub struct ZSTD_outBuffer {
|
|
pub dst: *mut c_void,
|
|
pub size: usize,
|
|
pub pos: usize,
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_inBufferForEndFlush(
|
|
in_buffer_mode: c_int,
|
|
expected_src: *const c_void,
|
|
expected_size: usize,
|
|
expected_pos: usize,
|
|
) -> ZSTD_inBuffer {
|
|
if in_buffer_mode == ZSTD_BM_STABLE {
|
|
ZSTD_inBuffer {
|
|
src: expected_src,
|
|
size: expected_size,
|
|
pos: expected_pos,
|
|
}
|
|
} else {
|
|
ZSTD_inBuffer {
|
|
src: ptr::null(),
|
|
size: 0,
|
|
pos: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn end_stream_remaining(
|
|
remaining_to_flush: usize,
|
|
frame_ended: c_int,
|
|
checksum_flag: c_int,
|
|
) -> usize {
|
|
if frame_ended != 0 {
|
|
return remaining_to_flush;
|
|
}
|
|
|
|
remaining_to_flush
|
|
.wrapping_add(ZSTD_BLOCK_HEADER_SIZE)
|
|
.wrapping_add((checksum_flag as usize).wrapping_mul(4))
|
|
}
|
|
|
|
/// Estimate single-threaded end-stream output without crossing C context state.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_endStreamRemaining(
|
|
remaining_to_flush: usize,
|
|
frame_ended: c_int,
|
|
checksum_flag: c_int,
|
|
) -> usize {
|
|
end_stream_remaining(remaining_to_flush, frame_ended, checksum_flag)
|
|
}
|
|
|
|
#[inline]
|
|
fn check_buffer_stability(
|
|
in_buffer_mode: c_int,
|
|
out_buffer_mode: c_int,
|
|
expected_in_src: *const c_void,
|
|
expected_in_pos: usize,
|
|
input_src: *const c_void,
|
|
input_pos: usize,
|
|
expected_out_buffer_size: usize,
|
|
output_size: usize,
|
|
output_pos: usize,
|
|
) -> usize {
|
|
if in_buffer_mode == ZSTD_BM_STABLE
|
|
&& (expected_in_src != input_src || expected_in_pos != input_pos)
|
|
{
|
|
return ERROR(ZstdErrorCode::StabilityConditionNotRespected);
|
|
}
|
|
|
|
if out_buffer_mode == ZSTD_BM_STABLE
|
|
&& expected_out_buffer_size != output_size.wrapping_sub(output_pos)
|
|
{
|
|
return ERROR(ZstdErrorCode::StabilityConditionNotRespected);
|
|
}
|
|
|
|
0
|
|
}
|
|
|
|
/// Validate the stable input/output buffer expectations without crossing the
|
|
/// private `ZSTD_CCtx` layout into Rust. Raw pointers are compared only for
|
|
/// identity and are never dereferenced.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_checkBufferStability(
|
|
in_buffer_mode: c_int,
|
|
out_buffer_mode: c_int,
|
|
expected_in_src: *const c_void,
|
|
expected_in_pos: usize,
|
|
input_src: *const c_void,
|
|
input_pos: usize,
|
|
expected_out_buffer_size: usize,
|
|
output_size: usize,
|
|
output_pos: usize,
|
|
) -> usize {
|
|
check_buffer_stability(
|
|
in_buffer_mode,
|
|
out_buffer_mode,
|
|
expected_in_src,
|
|
expected_in_pos,
|
|
input_src,
|
|
input_pos,
|
|
expected_out_buffer_size,
|
|
output_size,
|
|
output_pos,
|
|
)
|
|
}
|
|
|
|
#[inline]
|
|
unsafe fn set_buffer_expectations(
|
|
in_buffer_mode: c_int,
|
|
out_buffer_mode: c_int,
|
|
expected_in_buffer: *mut ZSTD_inBuffer,
|
|
expected_out_buffer_size: *mut usize,
|
|
output: *const ZSTD_outBuffer,
|
|
input: *const ZSTD_inBuffer,
|
|
) {
|
|
if in_buffer_mode == ZSTD_BM_STABLE {
|
|
unsafe {
|
|
*expected_in_buffer = ptr::read(input);
|
|
}
|
|
}
|
|
if out_buffer_mode == ZSTD_BM_STABLE {
|
|
let output = unsafe { &*output };
|
|
unsafe {
|
|
*expected_out_buffer_size = output.size.wrapping_sub(output.pos);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Update the stable input/output expectations after a compression call.
|
|
///
|
|
/// C retains the private `ZSTD_CCtx` fields and passes only their projections;
|
|
/// Rust owns the buffer-mode policy and C-size arithmetic.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_setBufferExpectations(
|
|
in_buffer_mode: c_int,
|
|
out_buffer_mode: c_int,
|
|
expected_in_buffer: *mut ZSTD_inBuffer,
|
|
expected_out_buffer_size: *mut usize,
|
|
output: *const ZSTD_outBuffer,
|
|
input: *const ZSTD_inBuffer,
|
|
) {
|
|
unsafe {
|
|
set_buffer_expectations(
|
|
in_buffer_mode,
|
|
out_buffer_mode,
|
|
expected_in_buffer,
|
|
expected_out_buffer_size,
|
|
output,
|
|
input,
|
|
);
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn select_sequence_copier(mode: c_int) -> c_int {
|
|
debug_assert!(
|
|
(ZSTD_SF_NO_BLOCK_DELIMITERS..=ZSTD_SF_EXPLICIT_BLOCK_DELIMITERS).contains(&mode)
|
|
);
|
|
if mode == ZSTD_SF_EXPLICIT_BLOCK_DELIMITERS {
|
|
ZSTD_SF_EXPLICIT_BLOCK_DELIMITERS
|
|
} else {
|
|
debug_assert_eq!(mode, ZSTD_SF_NO_BLOCK_DELIMITERS);
|
|
ZSTD_SF_NO_BLOCK_DELIMITERS
|
|
}
|
|
}
|
|
|
|
/// Select the C-side sequence transfer policy without crossing private
|
|
/// function pointers through the Rust ABI. Invalid values retain the C
|
|
/// release fallback to the no-delimiter policy after debug validation.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_selectSequenceCopier(mode: c_int) -> c_int {
|
|
select_sequence_copier(mode)
|
|
}
|
|
|
|
/* HUF_WORKSPACE_SIZE + (MaxSeq + 2) * sizeof(unsigned), rounded up. The
|
|
* superblock leaf also accepts the larger pre-split workspace, so a fixed
|
|
* 16 KiB buffer is sufficient for this first non-splitting path on both
|
|
* supported pointer widths. */
|
|
const TMP_WORKSPACE_SIZE: usize = 16 << 10;
|
|
|
|
#[inline]
|
|
fn update_frame_progression(
|
|
consumed_src_size: &mut u64,
|
|
produced_c_size: &mut u64,
|
|
pledged_src_size_plus_one: u64,
|
|
src_size: usize,
|
|
c_size: usize,
|
|
frame_header_size: usize,
|
|
) -> bool {
|
|
*consumed_src_size = consumed_src_size.wrapping_add(src_size as u64);
|
|
*produced_c_size = produced_c_size.wrapping_add(c_size.wrapping_add(frame_header_size) as u64);
|
|
|
|
pledged_src_size_plus_one != 0 && consumed_src_size.wrapping_add(1) > pledged_src_size_plus_one
|
|
}
|
|
|
|
/// Update C-owned frame counters after successful compression.
|
|
///
|
|
/// A zero pledge means that the source size is unknown. C keeps the
|
|
/// diagnostic that accompanies a nonzero overrun result.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_updateFrameProgression(
|
|
consumed_src_size: *mut u64,
|
|
produced_c_size: *mut u64,
|
|
pledged_src_size_plus_one: u64,
|
|
src_size: usize,
|
|
c_size: usize,
|
|
frame_header_size: usize,
|
|
) -> c_int {
|
|
let consumed_src_size = unsafe { &mut *consumed_src_size };
|
|
let produced_c_size = unsafe { &mut *produced_c_size };
|
|
update_frame_progression(
|
|
consumed_src_size,
|
|
produced_c_size,
|
|
pledged_src_size_plus_one,
|
|
src_size,
|
|
c_size,
|
|
frame_header_size,
|
|
) as c_int
|
|
}
|
|
|
|
/// ABI-compatible representation of `ZSTD_frameProgression` from `zstd.h`.
|
|
#[repr(C)]
|
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
pub struct ZSTD_frameProgression {
|
|
pub ingested: u64,
|
|
pub consumed: u64,
|
|
pub produced: u64,
|
|
pub flushed: u64,
|
|
pub currentJobID: c_uint,
|
|
pub nbActiveWorkers: c_uint,
|
|
}
|
|
|
|
#[inline]
|
|
fn frame_progression(
|
|
consumed_src_size: u64,
|
|
buffered: usize,
|
|
produced_c_size: u64,
|
|
) -> ZSTD_frameProgression {
|
|
ZSTD_frameProgression {
|
|
// C's usual arithmetic conversions promote size_t to U64 before wrapping.
|
|
ingested: consumed_src_size.wrapping_add(buffered as u64),
|
|
consumed: consumed_src_size,
|
|
produced: produced_c_size,
|
|
flushed: produced_c_size,
|
|
currentJobID: 0,
|
|
nbActiveWorkers: 0,
|
|
}
|
|
}
|
|
|
|
/// Construct single-threaded frame progression from C-owned scalar state.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_frameProgression(
|
|
consumed_src_size: u64,
|
|
buffered: usize,
|
|
produced_c_size: u64,
|
|
) -> ZSTD_frameProgression {
|
|
frame_progression(consumed_src_size, buffered, produced_c_size)
|
|
}
|
|
|
|
type ToFlushNowFn = unsafe extern "C" fn(*mut c_void) -> usize;
|
|
|
|
/// Explicit projection for the `ZSTD_toFlushNow` multithread policy.
|
|
///
|
|
/// Rust owns the worker-count branch; C retains the private CCtx and MT
|
|
/// context behind the callback.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_toFlushNowState {
|
|
callback_context: *mut c_void,
|
|
nb_workers: c_int,
|
|
to_flush_now: ToFlushNowFn,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_toFlushNowState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_toFlushNowState, nb_workers) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_toFlushNowState, to_flush_now) == 2 * size_of::<usize>());
|
|
assert!(size_of::<ZSTD_rust_toFlushNowState>() == 3 * size_of::<usize>());
|
|
};
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_toFlushNow(state: *const ZSTD_rust_toFlushNowState) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
if state.nb_workers <= 0 {
|
|
return 0;
|
|
}
|
|
unsafe { (state.to_flush_now)(state.callback_context) }
|
|
}
|
|
|
|
#[inline]
|
|
fn window_correct_overflow(curr: u32, cycle_log: u32, max_dist: u32) -> u32 {
|
|
let cycle_size = 1u32.wrapping_shl(cycle_log);
|
|
let cycle_mask = cycle_size.wrapping_sub(1);
|
|
let current_cycle = curr & cycle_mask;
|
|
let current_cycle_correction = if current_cycle < ZSTD_WINDOW_START_INDEX {
|
|
cycle_size.max(ZSTD_WINDOW_START_INDEX)
|
|
} else {
|
|
0
|
|
};
|
|
let new_current = current_cycle
|
|
.wrapping_add(current_cycle_correction)
|
|
.wrapping_add(max_dist.max(cycle_size));
|
|
curr.wrapping_sub(new_current)
|
|
}
|
|
|
|
/// Return the scalar correction for C's window-overflow state transition.
|
|
///
|
|
/// C retains pointer arithmetic, invariant checks, and all `ZSTD_window_t`
|
|
/// mutation. This leaf owns only the U32 cycle arithmetic so its wrapping
|
|
/// behavior is explicit and independent of the host pointer width.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_windowCorrectOverflow(curr: u32, cycle_log: u32, max_dist: u32) -> u32 {
|
|
window_correct_overflow(curr, cycle_log, max_dist)
|
|
}
|
|
|
|
#[inline]
|
|
fn window_can_overflow_correct(
|
|
curr: u32,
|
|
cycle_log: u32,
|
|
max_dist: u32,
|
|
loaded_dict_end: u32,
|
|
nb_overflow_corrections: u32,
|
|
) -> bool {
|
|
let cycle_size = 1u32.wrapping_shl(cycle_log);
|
|
let min_index_to_overflow_correct = cycle_size
|
|
.wrapping_add(max_dist.max(cycle_size))
|
|
.wrapping_add(ZSTD_WINDOW_START_INDEX);
|
|
let adjustment = nb_overflow_corrections.wrapping_add(1);
|
|
let adjusted_index = min_index_to_overflow_correct
|
|
.wrapping_mul(adjustment)
|
|
.max(min_index_to_overflow_correct);
|
|
let index_large_enough = curr > adjusted_index;
|
|
let dictionary_invalidated = curr > max_dist.wrapping_add(loaded_dict_end);
|
|
index_large_enough && dictionary_invalidated
|
|
}
|
|
|
|
/// Return whether the C-owned window state should enter overflow correction.
|
|
///
|
|
/// C retains both pointer subtractions and the `ZSTD_window_t` state. The
|
|
/// early-correction result is supplied as a scalar because it is based on the
|
|
/// block-start index, while `curr` is the block-end index used by the normal
|
|
/// current-limit fallback. The current limit and fuzzing policy are supplied
|
|
/// by C so their 32/64-bit and build-mode choices remain authoritative.
|
|
#[inline]
|
|
fn window_need_overflow_correction(
|
|
curr: u32,
|
|
can_overflow_correct: bool,
|
|
current_max: u32,
|
|
overflow_correct_frequently: bool,
|
|
) -> bool {
|
|
(overflow_correct_frequently && can_overflow_correct) || curr > current_max
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_windowCanOverflowCorrect(
|
|
curr: u32,
|
|
cycle_log: u32,
|
|
max_dist: u32,
|
|
loaded_dict_end: u32,
|
|
nb_overflow_corrections: u32,
|
|
) -> u32 {
|
|
window_can_overflow_correct(
|
|
curr,
|
|
cycle_log,
|
|
max_dist,
|
|
loaded_dict_end,
|
|
nb_overflow_corrections,
|
|
) as u32
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_windowNeedOverflowCorrection(
|
|
curr: u32,
|
|
can_overflow_correct: u32,
|
|
current_max: u32,
|
|
overflow_correct_frequently: c_int,
|
|
) -> u32 {
|
|
window_need_overflow_correction(
|
|
curr,
|
|
can_overflow_correct != 0,
|
|
current_max,
|
|
overflow_correct_frequently != 0,
|
|
) as u32
|
|
}
|
|
|
|
type OverflowNeedCorrectionFn =
|
|
unsafe extern "C" fn(*mut c_void, *const c_void, *const c_void) -> c_int;
|
|
type OverflowCorrectFn = unsafe extern "C" fn(*mut c_void, *const c_void) -> c_uint;
|
|
type OverflowCallbackFn = unsafe extern "C" fn(*mut c_void);
|
|
type OverflowReduceIndexFn = unsafe extern "C" fn(*mut c_void, c_uint);
|
|
|
|
/// Projection for the overflow-correction ordering around C-owned match state.
|
|
///
|
|
/// Rust owns the correction branch and callback order. C retains the window,
|
|
/// workspace, match tables, and dictionary pointers behind these callbacks.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_overflowCorrectState {
|
|
callback_context: *mut c_void,
|
|
next_to_update: *mut c_uint,
|
|
need_correction: Option<OverflowNeedCorrectionFn>,
|
|
correct_overflow: Option<OverflowCorrectFn>,
|
|
mark_tables_dirty: Option<OverflowCallbackFn>,
|
|
reduce_index: Option<OverflowReduceIndexFn>,
|
|
mark_tables_clean: Option<OverflowCallbackFn>,
|
|
loaded_dict_end: *mut c_uint,
|
|
dict_match_state: *mut *const c_void,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(size_of::<OverflowNeedCorrectionFn>() == size_of::<usize>());
|
|
assert!(size_of::<OverflowCorrectFn>() == size_of::<usize>());
|
|
assert!(size_of::<OverflowCallbackFn>() == size_of::<usize>());
|
|
assert!(size_of::<OverflowReduceIndexFn>() == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_overflowCorrectState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_overflowCorrectState, next_to_update) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_overflowCorrectState, need_correction) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_overflowCorrectState, correct_overflow) == 3 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_overflowCorrectState, mark_tables_dirty) == 4 * size_of::<usize>()
|
|
);
|
|
assert!(offset_of!(ZSTD_rust_overflowCorrectState, reduce_index) == 5 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_overflowCorrectState, mark_tables_clean) == 6 * size_of::<usize>()
|
|
);
|
|
assert!(offset_of!(ZSTD_rust_overflowCorrectState, loaded_dict_end) == 7 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_overflowCorrectState, dict_match_state) == size_of::<[usize; 8]>()
|
|
);
|
|
assert!(size_of::<ZSTD_rust_overflowCorrectState>() == size_of::<[usize; 9]>());
|
|
};
|
|
|
|
/// Apply one overflow correction while keeping all private codec state in C.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_overflowCorrectIfNeeded(
|
|
state: *const ZSTD_rust_overflowCorrectState,
|
|
src: *const c_void,
|
|
src_end: *const c_void,
|
|
) {
|
|
if state.is_null() {
|
|
return;
|
|
}
|
|
let state = unsafe { &*state };
|
|
if state.callback_context.is_null()
|
|
|| state.next_to_update.is_null()
|
|
|| state.loaded_dict_end.is_null()
|
|
|| state.dict_match_state.is_null()
|
|
{
|
|
return;
|
|
}
|
|
let (
|
|
Some(need_correction),
|
|
Some(correct_overflow),
|
|
Some(mark_tables_dirty),
|
|
Some(reduce_index),
|
|
Some(mark_tables_clean),
|
|
) = (
|
|
state.need_correction,
|
|
state.correct_overflow,
|
|
state.mark_tables_dirty,
|
|
state.reduce_index,
|
|
state.mark_tables_clean,
|
|
)
|
|
else {
|
|
return;
|
|
};
|
|
|
|
unsafe {
|
|
if need_correction(state.callback_context, src, src_end) == 0 {
|
|
return;
|
|
}
|
|
let correction = correct_overflow(state.callback_context, src);
|
|
mark_tables_dirty(state.callback_context);
|
|
reduce_index(state.callback_context, correction);
|
|
mark_tables_clean(state.callback_context);
|
|
*state.next_to_update = (*state.next_to_update).saturating_sub(correction);
|
|
*state.loaded_dict_end = 0;
|
|
*state.dict_match_state = ptr::null();
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn limit_next_to_update(curr: u32, next_to_update: u32) -> u32 {
|
|
let const_gap = 384u32;
|
|
let const_step = 192u32;
|
|
if curr > next_to_update.wrapping_add(const_gap) {
|
|
curr.wrapping_sub(const_step.min(curr.wrapping_sub(next_to_update).wrapping_sub(const_gap)))
|
|
} else {
|
|
next_to_update
|
|
}
|
|
}
|
|
|
|
/// Limit a stale matchfinder update point after a very long match.
|
|
///
|
|
/// C computes the source index against its private window base; Rust owns the
|
|
/// wrapping U32 threshold and bounded catch-up arithmetic.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_limitNextToUpdate(curr: u32, next_to_update: u32) -> u32 {
|
|
limit_next_to_update(curr, next_to_update)
|
|
}
|
|
|
|
#[inline]
|
|
fn next_input_size_hint(
|
|
in_buffer_mode: c_int,
|
|
block_size_max: usize,
|
|
stable_in_not_consumed: usize,
|
|
in_buff_target: usize,
|
|
in_buff_pos: usize,
|
|
) -> usize {
|
|
if in_buffer_mode == ZSTD_BM_STABLE {
|
|
return block_size_max.wrapping_sub(stable_in_not_consumed);
|
|
}
|
|
|
|
let hint_in_size = in_buff_target.wrapping_sub(in_buff_pos);
|
|
if hint_in_size == 0 {
|
|
block_size_max
|
|
} else {
|
|
hint_in_size
|
|
}
|
|
}
|
|
|
|
/// Return the next input size required by the C streaming state machine.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_nextInputSizeHint(
|
|
in_buffer_mode: c_int,
|
|
block_size_max: usize,
|
|
stable_in_not_consumed: usize,
|
|
in_buff_target: usize,
|
|
in_buff_pos: usize,
|
|
) -> usize {
|
|
next_input_size_hint(
|
|
in_buffer_mode,
|
|
block_size_max,
|
|
stable_in_not_consumed,
|
|
in_buff_target,
|
|
in_buff_pos,
|
|
)
|
|
}
|
|
|
|
type NextInputSizeHintMTorSTFn = unsafe extern "C" fn(*mut c_void) -> usize;
|
|
|
|
/// Projection for the MT-versus-single-thread input-hint dispatch.
|
|
///
|
|
/// Rust owns only the worker-count branch. The single-thread hint inputs are
|
|
/// scalar, while the MT context remains opaque behind its C callback.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_nextInputSizeHintMTorSTState {
|
|
block_size_max: usize,
|
|
stable_in_not_consumed: usize,
|
|
in_buff_target: usize,
|
|
in_buff_pos: usize,
|
|
nb_workers: c_int,
|
|
in_buffer_mode: c_int,
|
|
mt_context: *mut c_void,
|
|
mt_next_input_size_hint: Option<NextInputSizeHintMTorSTFn>,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(size_of::<NextInputSizeHintMTorSTFn>() == size_of::<usize>());
|
|
assert!(size_of::<Option<NextInputSizeHintMTorSTFn>>() == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_nextInputSizeHintMTorSTState, block_size_max) == 0);
|
|
assert!(
|
|
offset_of!(
|
|
ZSTD_rust_nextInputSizeHintMTorSTState,
|
|
stable_in_not_consumed
|
|
) == size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_nextInputSizeHintMTorSTState, in_buff_target)
|
|
== 2 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_nextInputSizeHintMTorSTState, in_buff_pos) == 3 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_nextInputSizeHintMTorSTState, nb_workers) == 4 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_nextInputSizeHintMTorSTState, in_buffer_mode)
|
|
== 4 * size_of::<usize>() + size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_nextInputSizeHintMTorSTState, mt_context)
|
|
== 4 * size_of::<usize>() + 2 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(
|
|
ZSTD_rust_nextInputSizeHintMTorSTState,
|
|
mt_next_input_size_hint
|
|
) == 4 * size_of::<usize>() + 2 * size_of::<c_int>() + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
size_of::<ZSTD_rust_nextInputSizeHintMTorSTState>()
|
|
== 4 * size_of::<usize>() + 2 * size_of::<c_int>() + 2 * size_of::<usize>()
|
|
);
|
|
};
|
|
|
|
/// Select the MT or single-thread input-size hint without exposing the MT
|
|
/// context representation to Rust.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_nextInputSizeHintMTorST(
|
|
state: *const ZSTD_rust_nextInputSizeHintMTorSTState,
|
|
) -> usize {
|
|
let Some(state) = (unsafe { state.as_ref() }) else {
|
|
return 0;
|
|
};
|
|
if state.nb_workers >= 1 {
|
|
let Some(mt_next_input_size_hint) = state.mt_next_input_size_hint else {
|
|
return 0;
|
|
};
|
|
return unsafe { mt_next_input_size_hint(state.mt_context) };
|
|
}
|
|
next_input_size_hint(
|
|
state.in_buffer_mode,
|
|
state.block_size_max,
|
|
state.stable_in_not_consumed,
|
|
state.in_buff_target,
|
|
state.in_buff_pos,
|
|
)
|
|
}
|
|
|
|
#[inline]
|
|
fn mt_next_input_size_hint(target_section_size: usize, in_buff_filled: usize) -> usize {
|
|
let hint_in_size = target_section_size.wrapping_sub(in_buff_filled);
|
|
if hint_in_size == 0 {
|
|
target_section_size
|
|
} else {
|
|
hint_in_size
|
|
}
|
|
}
|
|
|
|
/// Return the next input size required by the C multithreaded streaming state.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTDMT_rust_nextInputSizeHint(
|
|
target_section_size: usize,
|
|
in_buff_filled: usize,
|
|
) -> usize {
|
|
mt_next_input_size_hint(target_section_size, in_buff_filled)
|
|
}
|
|
|
|
#[inline]
|
|
fn mt_sizeof_cctx(
|
|
mtctx_size: usize,
|
|
factory_size: usize,
|
|
buffer_pool_size: usize,
|
|
jobs_size: usize,
|
|
cctx_pool_size: usize,
|
|
seq_pool_size: usize,
|
|
cdict_size: usize,
|
|
round_buff_size: usize,
|
|
) -> usize {
|
|
mtctx_size
|
|
.wrapping_add(factory_size)
|
|
.wrapping_add(buffer_pool_size)
|
|
.wrapping_add(jobs_size)
|
|
.wrapping_add(cctx_pool_size)
|
|
.wrapping_add(seq_pool_size)
|
|
.wrapping_add(cdict_size)
|
|
.wrapping_add(round_buff_size)
|
|
}
|
|
|
|
/// Aggregate C-owned multithreaded context size components with C `size_t`
|
|
/// wrapping semantics.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTDMT_rust_sizeofCCtx(
|
|
mtctx_size: usize,
|
|
factory_size: usize,
|
|
buffer_pool_size: usize,
|
|
jobs_size: usize,
|
|
cctx_pool_size: usize,
|
|
seq_pool_size: usize,
|
|
cdict_size: usize,
|
|
round_buff_size: usize,
|
|
) -> usize {
|
|
mt_sizeof_cctx(
|
|
mtctx_size,
|
|
factory_size,
|
|
buffer_pool_size,
|
|
jobs_size,
|
|
cctx_pool_size,
|
|
seq_pool_size,
|
|
cdict_size,
|
|
round_buff_size,
|
|
)
|
|
}
|
|
|
|
#[inline]
|
|
fn bitmix(mut val: u64, len: u64) -> u64 {
|
|
val ^= val.rotate_right(49) ^ val.rotate_right(24);
|
|
val = val.wrapping_mul(0x9FB21C651E98DF25);
|
|
val ^= (val >> 35).wrapping_add(len);
|
|
val = val.wrapping_mul(0x9FB21C651E98DF25);
|
|
val ^ (val >> 28)
|
|
}
|
|
|
|
#[inline]
|
|
fn advance_hash_salt(hash_salt: u64, hash_salt_entropy: u64) -> u64 {
|
|
bitmix(hash_salt, 8) ^ bitmix(hash_salt_entropy, 4)
|
|
}
|
|
|
|
/// Advance the row-matchfinder salt without exposing C's private match state.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_advanceHashSalt(hash_salt: u64, hash_salt_entropy: u64) -> u64 {
|
|
advance_hash_salt(hash_salt, hash_salt_entropy)
|
|
}
|
|
|
|
/// Advance a C-owned row-matchfinder salt in place while C retains the
|
|
/// private match-state layout.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_advanceHashSaltInPlace(
|
|
hash_salt: *mut u64,
|
|
hash_salt_entropy: u64,
|
|
) {
|
|
debug_assert!(!hash_salt.is_null());
|
|
unsafe {
|
|
*hash_salt = advance_hash_salt(*hash_salt, hash_salt_entropy);
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn index_too_close_to_max(next_src_base_offset: usize) -> bool {
|
|
next_src_base_offset > ZSTD_CURRENT_MAX - ZSTD_INDEXOVERFLOW_MARGIN
|
|
}
|
|
|
|
/// Return whether a scalar C window offset is within the overflow margin.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_indexTooCloseToMax(next_src_base_offset: usize) -> c_int {
|
|
index_too_close_to_max(next_src_base_offset) as c_int
|
|
}
|
|
|
|
#[inline]
|
|
fn dict_too_big(loaded_dict_size: usize) -> bool {
|
|
loaded_dict_size > ZSTD_CHUNKSIZE_MAX
|
|
}
|
|
|
|
/// Return whether a dictionary exceeds the maximum loadable chunk size.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_dictTooBig(loaded_dict_size: usize) -> c_int {
|
|
dict_too_big(loaded_dict_size) as c_int
|
|
}
|
|
|
|
type ResetMatchStateCallback = unsafe extern "C" fn(*mut c_void);
|
|
type ResetMatchStateSetPointer = unsafe extern "C" fn(*mut c_void, c_int, *mut c_void);
|
|
type ResetMatchStateReserve = unsafe extern "C" fn(*mut c_void, c_int, usize) -> *mut c_void;
|
|
type ResetMatchStateReserveFailed = unsafe extern "C" fn(*mut c_void) -> c_int;
|
|
type ResetMatchStateZero = unsafe extern "C" fn(*mut c_void, *mut c_void, usize);
|
|
|
|
const RESET_MATCH_RESERVE_TABLE: c_int = 0;
|
|
const RESET_MATCH_RESERVE_ALIGNED_INIT_ONCE: c_int = 1;
|
|
const RESET_MATCH_RESERVE_ALIGNED64: c_int = 2;
|
|
|
|
const RESET_MATCH_POINTER_HASH_TABLE: c_int = 0;
|
|
const RESET_MATCH_POINTER_CHAIN_TABLE: c_int = 1;
|
|
const RESET_MATCH_POINTER_HASH_TABLE3: c_int = 2;
|
|
const RESET_MATCH_POINTER_TAG_TABLE: c_int = 3;
|
|
const RESET_MATCH_POINTER_LIT_FREQ: c_int = 4;
|
|
const RESET_MATCH_POINTER_LIT_LENGTH_FREQ: c_int = 5;
|
|
const RESET_MATCH_POINTER_MATCH_LENGTH_FREQ: c_int = 6;
|
|
const RESET_MATCH_POINTER_OFF_CODE_FREQ: c_int = 7;
|
|
const RESET_MATCH_POINTER_MATCH_TABLE: c_int = 8;
|
|
const RESET_MATCH_POINTER_PRICE_TABLE: c_int = 9;
|
|
|
|
/// Private C layout pointers and callback seams for match-state reset.
|
|
///
|
|
/// Rust owns the reset policy and allocation order. C retains the workspace,
|
|
/// window, and private match-state pointer representations behind callbacks.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_resetMatchStateState {
|
|
callback_context: *mut c_void,
|
|
c_params: ZSTD_compressionParameters,
|
|
dedicated_dict_search: c_int,
|
|
use_row_match_finder: c_int,
|
|
comp_reset_policy: c_int,
|
|
index_reset_policy: c_int,
|
|
reset_target: c_int,
|
|
hash_log3_max: c_uint,
|
|
lit_freq_size: usize,
|
|
lit_length_freq_size: usize,
|
|
match_length_freq_size: usize,
|
|
off_code_freq_size: usize,
|
|
match_size: usize,
|
|
optimal_size: usize,
|
|
hash_log3: *mut c_uint,
|
|
row_hash_log: *mut c_uint,
|
|
hash_salt: *mut u64,
|
|
lazy_skipping: *mut c_int,
|
|
c_params_out: *mut ZSTD_compressionParameters,
|
|
set_pointer: Option<ResetMatchStateSetPointer>,
|
|
window_init: Option<ResetMatchStateCallback>,
|
|
mark_tables_dirty: Option<ResetMatchStateCallback>,
|
|
invalidate_match_state: Option<ResetMatchStateCallback>,
|
|
clear_tables: Option<ResetMatchStateCallback>,
|
|
clean_tables: Option<ResetMatchStateCallback>,
|
|
advance_hash_salt: Option<ResetMatchStateCallback>,
|
|
reserve: Option<ResetMatchStateReserve>,
|
|
reserve_failed: Option<ResetMatchStateReserveFailed>,
|
|
zero: Option<ResetMatchStateZero>,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(size_of::<ResetMatchStateCallback>() == size_of::<usize>());
|
|
assert!(size_of::<ResetMatchStateSetPointer>() == size_of::<usize>());
|
|
assert!(size_of::<ResetMatchStateReserve>() == size_of::<usize>());
|
|
assert!(size_of::<ResetMatchStateReserveFailed>() == size_of::<usize>());
|
|
assert!(size_of::<ResetMatchStateZero>() == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_resetMatchStateState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_resetMatchStateState, c_params) == size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetMatchStateState, lit_freq_size)
|
|
> offset_of!(ZSTD_rust_resetMatchStateState, hash_log3_max)
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetMatchStateState, hash_log3)
|
|
== offset_of!(ZSTD_rust_resetMatchStateState, optimal_size) + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetMatchStateState, set_pointer)
|
|
== offset_of!(ZSTD_rust_resetMatchStateState, c_params_out) + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
size_of::<ZSTD_rust_resetMatchStateState>()
|
|
== offset_of!(ZSTD_rust_resetMatchStateState, zero) + size_of::<usize>()
|
|
);
|
|
};
|
|
|
|
#[inline]
|
|
fn reset_match_state_table_size(log: u32) -> Option<usize> {
|
|
1usize.checked_shl(log)
|
|
}
|
|
|
|
/// Reset and allocate one private match-state projection.
|
|
///
|
|
/// The callback context deliberately remains opaque here. This keeps C's
|
|
/// workspace phases and match-state layout out of the Rust ABI while making
|
|
/// the policy/order independently testable.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_resetMatchState(
|
|
state: *const ZSTD_rust_resetMatchStateState,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
let Some(set_pointer) = state.set_pointer else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(window_init) = state.window_init else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(mark_tables_dirty) = state.mark_tables_dirty else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(invalidate_match_state) = state.invalidate_match_state else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(clear_tables) = state.clear_tables else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(clean_tables) = state.clean_tables else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(advance_hash_salt) = state.advance_hash_salt else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(reserve) = state.reserve else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(reserve_failed) = state.reserve_failed else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(zero) = state.zero else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
if state.callback_context.is_null()
|
|
|| state.hash_log3.is_null()
|
|
|| state.row_hash_log.is_null()
|
|
|| state.hash_salt.is_null()
|
|
|| state.lazy_skipping.is_null()
|
|
|| state.c_params_out.is_null()
|
|
{
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
if state.use_row_match_finder == ZSTD_RUST_PS_AUTO
|
|
|| !matches!(state.comp_reset_policy, 0..=1)
|
|
|| !matches!(state.index_reset_policy, 0..=1)
|
|
|| !matches!(state.reset_target, 0..=1)
|
|
{
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
let chain_size = if ZSTD_rust_params_allocateChainTable(
|
|
state.c_params.strategy,
|
|
state.use_row_match_finder,
|
|
c_int::from(state.dedicated_dict_search != 0 && state.reset_target == 0),
|
|
) != 0
|
|
{
|
|
let Some(size) = reset_match_state_table_size(state.c_params.chainLog) else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
size
|
|
} else {
|
|
0
|
|
};
|
|
let Some(hash_size) = reset_match_state_table_size(state.c_params.hashLog) else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let hash_log3 = if state.reset_target == 1 && state.c_params.minMatch == 3 {
|
|
state.hash_log3_max.min(state.c_params.windowLog)
|
|
} else {
|
|
0
|
|
};
|
|
let hash3_size = if hash_log3 == 0 {
|
|
0
|
|
} else {
|
|
let Some(size) = reset_match_state_table_size(hash_log3) else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
size
|
|
};
|
|
|
|
if state.index_reset_policy == 1 {
|
|
unsafe {
|
|
window_init(state.callback_context);
|
|
mark_tables_dirty(state.callback_context);
|
|
}
|
|
}
|
|
|
|
unsafe {
|
|
*state.hash_log3 = hash_log3;
|
|
*state.lazy_skipping = 0;
|
|
invalidate_match_state(state.callback_context);
|
|
debug_assert_eq!(reserve_failed(state.callback_context), 0);
|
|
clear_tables(state.callback_context);
|
|
}
|
|
|
|
unsafe {
|
|
set_pointer(
|
|
state.callback_context,
|
|
RESET_MATCH_POINTER_HASH_TABLE,
|
|
reserve(
|
|
state.callback_context,
|
|
RESET_MATCH_RESERVE_TABLE,
|
|
hash_size.wrapping_mul(size_of::<c_uint>()),
|
|
),
|
|
);
|
|
set_pointer(
|
|
state.callback_context,
|
|
RESET_MATCH_POINTER_CHAIN_TABLE,
|
|
reserve(
|
|
state.callback_context,
|
|
RESET_MATCH_RESERVE_TABLE,
|
|
chain_size.wrapping_mul(size_of::<c_uint>()),
|
|
),
|
|
);
|
|
set_pointer(
|
|
state.callback_context,
|
|
RESET_MATCH_POINTER_HASH_TABLE3,
|
|
reserve(
|
|
state.callback_context,
|
|
RESET_MATCH_RESERVE_TABLE,
|
|
hash3_size.wrapping_mul(size_of::<c_uint>()),
|
|
),
|
|
);
|
|
}
|
|
if unsafe { reserve_failed(state.callback_context) } != 0 {
|
|
return ERROR(ZstdErrorCode::MemoryAllocation);
|
|
}
|
|
|
|
if state.comp_reset_policy != 1 {
|
|
unsafe { clean_tables(state.callback_context) };
|
|
}
|
|
|
|
if ZSTD_rust_params_rowMatchFinderUsed(state.c_params.strategy, state.use_row_match_finder) != 0
|
|
{
|
|
if state.reset_target == 1 {
|
|
let tag_table = unsafe {
|
|
reserve(
|
|
state.callback_context,
|
|
RESET_MATCH_RESERVE_ALIGNED_INIT_ONCE,
|
|
hash_size,
|
|
)
|
|
};
|
|
unsafe {
|
|
set_pointer(
|
|
state.callback_context,
|
|
RESET_MATCH_POINTER_TAG_TABLE,
|
|
tag_table,
|
|
);
|
|
advance_hash_salt(state.callback_context);
|
|
}
|
|
} else {
|
|
let tag_table = unsafe {
|
|
reserve(
|
|
state.callback_context,
|
|
RESET_MATCH_RESERVE_ALIGNED64,
|
|
hash_size,
|
|
)
|
|
};
|
|
unsafe {
|
|
set_pointer(
|
|
state.callback_context,
|
|
RESET_MATCH_POINTER_TAG_TABLE,
|
|
tag_table,
|
|
);
|
|
}
|
|
if unsafe { reserve_failed(state.callback_context) } != 0 {
|
|
return ERROR(ZstdErrorCode::MemoryAllocation);
|
|
}
|
|
unsafe {
|
|
zero(state.callback_context, tag_table, hash_size);
|
|
*state.hash_salt = 0;
|
|
}
|
|
}
|
|
if unsafe { reserve_failed(state.callback_context) } != 0 {
|
|
return ERROR(ZstdErrorCode::MemoryAllocation);
|
|
}
|
|
let row_log = state.c_params.searchLog.clamp(4, 6);
|
|
debug_assert!(state.c_params.hashLog >= row_log);
|
|
unsafe {
|
|
*state.row_hash_log = state.c_params.hashLog.wrapping_sub(row_log);
|
|
}
|
|
}
|
|
|
|
if state.reset_target == 1 && state.c_params.strategy >= 7 {
|
|
unsafe {
|
|
set_pointer(
|
|
state.callback_context,
|
|
RESET_MATCH_POINTER_LIT_FREQ,
|
|
reserve(
|
|
state.callback_context,
|
|
RESET_MATCH_RESERVE_ALIGNED64,
|
|
state.lit_freq_size,
|
|
),
|
|
);
|
|
set_pointer(
|
|
state.callback_context,
|
|
RESET_MATCH_POINTER_LIT_LENGTH_FREQ,
|
|
reserve(
|
|
state.callback_context,
|
|
RESET_MATCH_RESERVE_ALIGNED64,
|
|
state.lit_length_freq_size,
|
|
),
|
|
);
|
|
set_pointer(
|
|
state.callback_context,
|
|
RESET_MATCH_POINTER_MATCH_LENGTH_FREQ,
|
|
reserve(
|
|
state.callback_context,
|
|
RESET_MATCH_RESERVE_ALIGNED64,
|
|
state.match_length_freq_size,
|
|
),
|
|
);
|
|
set_pointer(
|
|
state.callback_context,
|
|
RESET_MATCH_POINTER_OFF_CODE_FREQ,
|
|
reserve(
|
|
state.callback_context,
|
|
RESET_MATCH_RESERVE_ALIGNED64,
|
|
state.off_code_freq_size,
|
|
),
|
|
);
|
|
set_pointer(
|
|
state.callback_context,
|
|
RESET_MATCH_POINTER_MATCH_TABLE,
|
|
reserve(
|
|
state.callback_context,
|
|
RESET_MATCH_RESERVE_ALIGNED64,
|
|
state.match_size,
|
|
),
|
|
);
|
|
set_pointer(
|
|
state.callback_context,
|
|
RESET_MATCH_POINTER_PRICE_TABLE,
|
|
reserve(
|
|
state.callback_context,
|
|
RESET_MATCH_RESERVE_ALIGNED64,
|
|
state.optimal_size,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
unsafe {
|
|
*state.c_params_out = state.c_params;
|
|
}
|
|
if unsafe { reserve_failed(state.callback_context) } != 0 {
|
|
return ERROR(ZstdErrorCode::MemoryAllocation);
|
|
}
|
|
0
|
|
}
|
|
|
|
#[inline]
|
|
fn sizeof_local_dict(dict_buffer_present: c_int, dict_size: usize, cdict_size: usize) -> usize {
|
|
let buffer_size = if dict_buffer_present != 0 {
|
|
dict_size
|
|
} else {
|
|
0
|
|
};
|
|
buffer_size.wrapping_add(cdict_size)
|
|
}
|
|
|
|
/// Add the C-owned local-dictionary sizes with C `size_t` wrapping semantics.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_sizeofLocalDict(
|
|
dict_buffer_present: c_int,
|
|
dict_size: usize,
|
|
cdict_size: usize,
|
|
) -> usize {
|
|
sizeof_local_dict(dict_buffer_present, dict_size, cdict_size)
|
|
}
|
|
|
|
#[inline]
|
|
fn sizeof_cdict(object_size: usize, workspace_size: usize) -> usize {
|
|
object_size.wrapping_add(workspace_size)
|
|
}
|
|
|
|
/// Aggregate C-owned dictionary size components with C `size_t` wrapping
|
|
/// semantics.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_sizeofCDict(objectSize: usize, workspaceSize: usize) -> usize {
|
|
sizeof_cdict(objectSize, workspaceSize)
|
|
}
|
|
|
|
#[inline]
|
|
fn sizeof_cctx(
|
|
object_size: usize,
|
|
workspace_size: usize,
|
|
local_dict_size: usize,
|
|
mtctx_size: usize,
|
|
) -> usize {
|
|
object_size
|
|
.wrapping_add(workspace_size)
|
|
.wrapping_add(local_dict_size)
|
|
.wrapping_add(mtctx_size)
|
|
}
|
|
|
|
/// Aggregate C-owned context size components with C `size_t` wrapping
|
|
/// semantics.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_sizeofCCtx(
|
|
object_size: usize,
|
|
workspace_size: usize,
|
|
local_dict_size: usize,
|
|
mtctx_size: usize,
|
|
) -> usize {
|
|
sizeof_cctx(object_size, workspace_size, local_dict_size, mtctx_size)
|
|
}
|
|
|
|
#[inline]
|
|
fn estimate_workspace_size(
|
|
cctx_space: usize,
|
|
tmp_work_space: usize,
|
|
block_state_space: usize,
|
|
ldm_space: usize,
|
|
ldm_seq_space: usize,
|
|
match_state_size: usize,
|
|
token_space: usize,
|
|
buffer_space: usize,
|
|
external_seq_space: usize,
|
|
) -> usize {
|
|
cctx_space
|
|
.wrapping_add(tmp_work_space)
|
|
.wrapping_add(block_state_space)
|
|
.wrapping_add(ldm_space)
|
|
.wrapping_add(ldm_seq_space)
|
|
.wrapping_add(match_state_size)
|
|
.wrapping_add(token_space)
|
|
.wrapping_add(buffer_space)
|
|
.wrapping_add(external_seq_space)
|
|
}
|
|
|
|
/// Aggregate workspace-size components with C `size_t` wrapping semantics.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_estimateWorkspaceSize(
|
|
cctx_space: usize,
|
|
tmp_work_space: usize,
|
|
block_state_space: usize,
|
|
ldm_space: usize,
|
|
ldm_seq_space: usize,
|
|
match_state_size: usize,
|
|
token_space: usize,
|
|
buffer_space: usize,
|
|
external_seq_space: usize,
|
|
) -> usize {
|
|
estimate_workspace_size(
|
|
cctx_space,
|
|
tmp_work_space,
|
|
block_state_space,
|
|
ldm_space,
|
|
ldm_seq_space,
|
|
match_state_size,
|
|
token_space,
|
|
buffer_space,
|
|
external_seq_space,
|
|
)
|
|
}
|
|
|
|
/// Private C layout sizes needed by the CCtx workspace estimator.
|
|
///
|
|
/// The scalar sizes keep `ZSTD_CCtx`, `ldmEntry_t`, and the other private C
|
|
/// objects opaque while allowing Rust to own the workspace-sizing formula.
|
|
#[repr(C)]
|
|
#[derive(Clone, Copy, Debug, Default)]
|
|
pub struct ZSTD_rustCCtxWorkspaceSizing {
|
|
pub cctxSize: usize,
|
|
pub compressedBlockStateSize: usize,
|
|
pub seqDefSize: usize,
|
|
pub rawSeqSize: usize,
|
|
pub externalSequenceSize: usize,
|
|
pub tmpWorkspaceSize: usize,
|
|
pub wildcopyOverlength: usize,
|
|
pub matchTSize: usize,
|
|
pub optimalTSize: usize,
|
|
pub asanRedzoneSize: usize,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(size_of::<ZSTD_rustCCtxWorkspaceSizing>() == size_of::<[usize; 10]>());
|
|
};
|
|
|
|
const ZSTD_HASHLOG3_MAX: u32 = 17;
|
|
|
|
#[inline]
|
|
fn cctx_cwksp_alloc_size(size: usize, asan_redzone_size: usize) -> usize {
|
|
if size == 0 {
|
|
0
|
|
} else {
|
|
size.wrapping_add(asan_redzone_size.wrapping_mul(2))
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn cctx_cwksp_align(size: usize, alignment: usize) -> usize {
|
|
size.wrapping_add(alignment - 1) & !(alignment - 1)
|
|
}
|
|
|
|
#[inline]
|
|
fn cctx_cwksp_aligned64_alloc_size(size: usize, asan_redzone_size: usize) -> usize {
|
|
cctx_cwksp_alloc_size(cctx_cwksp_align(size, 64), asan_redzone_size)
|
|
}
|
|
|
|
#[inline]
|
|
fn cctx_ldm_table_size(
|
|
ldm_enable: c_int,
|
|
hash_log: u32,
|
|
bucket_size_log: u32,
|
|
asan_redzone_size: usize,
|
|
) -> usize {
|
|
if ldm_enable != ZSTD_RUST_PS_ENABLE {
|
|
return 0;
|
|
}
|
|
let hash_size = 1usize << hash_log;
|
|
let bucket_log = bucket_size_log.min(hash_log);
|
|
let bucket_size = 1usize << (hash_log - bucket_log);
|
|
cctx_cwksp_alloc_size(bucket_size, asan_redzone_size).wrapping_add(cctx_cwksp_alloc_size(
|
|
hash_size.wrapping_mul(2 * size_of::<u32>()),
|
|
asan_redzone_size,
|
|
))
|
|
}
|
|
|
|
/// Estimate the CCtx workspace using the private C layout sizes supplied by
|
|
/// the adapter. This owns the orchestration formerly kept in
|
|
/// `ZSTD_estimateCCtxSize_usingCCtxParams_internal()`.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_estimateCCtxWorkspaceSize(
|
|
cparams: ZSTD_compressionParameters,
|
|
ldm_enable: c_int,
|
|
ldm_hash_log: u32,
|
|
ldm_bucket_size_log: u32,
|
|
ldm_min_match_length: u32,
|
|
is_static: c_int,
|
|
use_row_match_finder: c_int,
|
|
buff_in_size: usize,
|
|
buff_out_size: usize,
|
|
pledged_src_size: u64,
|
|
use_sequence_producer: c_int,
|
|
max_block_size: usize,
|
|
sizing: *const ZSTD_rustCCtxWorkspaceSizing,
|
|
) -> usize {
|
|
if sizing.is_null() {
|
|
return 0;
|
|
}
|
|
let sizing = unsafe { *sizing };
|
|
let window_limit = 1u64.checked_shl(cparams.windowLog).unwrap_or(u64::MAX);
|
|
let window_size = (window_limit.min(pledged_src_size).min(usize::MAX as u64) as usize).max(1);
|
|
let block_size = ZSTD_resolveMaxBlockSize(max_block_size).min(window_size);
|
|
let max_nb_seq = ZSTD_rust_params_maxNbSeq(block_size, cparams.minMatch, use_sequence_producer);
|
|
let token_space = cctx_cwksp_alloc_size(
|
|
sizing.wildcopyOverlength.wrapping_add(block_size),
|
|
sizing.asanRedzoneSize,
|
|
)
|
|
.wrapping_add(cctx_cwksp_aligned64_alloc_size(
|
|
max_nb_seq.wrapping_mul(sizing.seqDefSize),
|
|
sizing.asanRedzoneSize,
|
|
))
|
|
.wrapping_add(3usize.wrapping_mul(cctx_cwksp_alloc_size(
|
|
max_nb_seq.wrapping_mul(size_of::<u8>()),
|
|
sizing.asanRedzoneSize,
|
|
)));
|
|
let tmp_workspace = cctx_cwksp_alloc_size(sizing.tmpWorkspaceSize, sizing.asanRedzoneSize);
|
|
let block_state_space = 2usize.wrapping_mul(cctx_cwksp_alloc_size(
|
|
sizing.compressedBlockStateSize,
|
|
sizing.asanRedzoneSize,
|
|
));
|
|
let match_state_sizing = ZSTD_rustMatchStateSizing {
|
|
hashLog3Max: ZSTD_HASHLOG3_MAX,
|
|
matchTSize: sizing.matchTSize,
|
|
optimalTSize: sizing.optimalTSize,
|
|
asanRedzoneSize: sizing.asanRedzoneSize,
|
|
};
|
|
let match_state_size = unsafe {
|
|
ZSTD_rust_params_estimateMatchStateSize(
|
|
cparams,
|
|
use_row_match_finder,
|
|
0,
|
|
1,
|
|
&match_state_sizing,
|
|
)
|
|
};
|
|
let ldm_space = cctx_ldm_table_size(
|
|
ldm_enable,
|
|
ldm_hash_log,
|
|
ldm_bucket_size_log,
|
|
sizing.asanRedzoneSize,
|
|
);
|
|
let max_nb_ldm_seq = if ldm_enable == ZSTD_RUST_PS_ENABLE && ldm_min_match_length != 0 {
|
|
block_size / ldm_min_match_length as usize
|
|
} else {
|
|
0
|
|
};
|
|
let ldm_seq_space = if ldm_enable == ZSTD_RUST_PS_ENABLE {
|
|
cctx_cwksp_aligned64_alloc_size(
|
|
max_nb_ldm_seq.wrapping_mul(sizing.rawSeqSize),
|
|
sizing.asanRedzoneSize,
|
|
)
|
|
} else {
|
|
0
|
|
};
|
|
let buffer_space = cctx_cwksp_alloc_size(buff_in_size, sizing.asanRedzoneSize)
|
|
.wrapping_add(cctx_cwksp_alloc_size(buff_out_size, sizing.asanRedzoneSize));
|
|
let cctx_space = if is_static != 0 {
|
|
cctx_cwksp_alloc_size(sizing.cctxSize, sizing.asanRedzoneSize)
|
|
} else {
|
|
0
|
|
};
|
|
let external_seq_space = if use_sequence_producer != 0 {
|
|
cctx_cwksp_aligned64_alloc_size(
|
|
crate::zstd_compress_api::ZSTD_sequenceBound(block_size)
|
|
.wrapping_mul(sizing.externalSequenceSize),
|
|
sizing.asanRedzoneSize,
|
|
)
|
|
} else {
|
|
0
|
|
};
|
|
|
|
estimate_workspace_size(
|
|
cctx_space,
|
|
tmp_workspace,
|
|
block_state_space,
|
|
ldm_space,
|
|
ldm_seq_space,
|
|
match_state_size,
|
|
token_space,
|
|
buffer_space,
|
|
external_seq_space,
|
|
)
|
|
}
|
|
|
|
/// Scalar outputs computed while resetting a private C compression context.
|
|
///
|
|
/// C still owns workspace resizing and all private reservations. Rust owns
|
|
/// this policy projection so the reset formula is testable without exposing
|
|
/// `ZSTD_CCtx` or `ZSTD_cwksp` layouts across the ABI.
|
|
#[repr(C)]
|
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
pub struct ZSTD_rustCCtxResetPlan {
|
|
pub windowSize: usize,
|
|
pub blockSize: usize,
|
|
pub maxNbSeq: usize,
|
|
pub buffInSize: usize,
|
|
pub buffOutSize: usize,
|
|
pub maxNbLdmSeq: usize,
|
|
pub maxNbExternalSeq: usize,
|
|
pub neededSpace: usize,
|
|
pub needsIndexReset: c_int,
|
|
}
|
|
|
|
/// Scalar inputs needed to plan a private C compression-context reset.
|
|
///
|
|
/// The C adapter supplies resolved parameters, private object sizes, and the
|
|
/// already-reduced index/dictionary predicates. No private C pointer or
|
|
/// layout is passed through this record.
|
|
#[repr(C)]
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub struct ZSTD_rustCCtxResetState {
|
|
pub cParams: ZSTD_compressionParameters,
|
|
pub ldmEnable: c_int,
|
|
pub ldmHashLog: c_uint,
|
|
pub ldmBucketSizeLog: c_uint,
|
|
pub ldmMinMatchLength: c_uint,
|
|
pub isStatic: c_int,
|
|
pub useRowMatchFinder: c_int,
|
|
pub inBufferBuffered: c_int,
|
|
pub outBufferBuffered: c_int,
|
|
pub useSequenceProducer: c_int,
|
|
pub initialized: c_int,
|
|
pub indexTooClose: c_int,
|
|
pub dictTooBig: c_int,
|
|
pub pledgedSrcSize: u64,
|
|
pub maxBlockSize: usize,
|
|
pub sizing: *const ZSTD_rustCCtxWorkspaceSizing,
|
|
pub plan: *mut ZSTD_rustCCtxResetPlan,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(size_of::<ZSTD_rustCCtxResetPlan>() == 9 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rustCCtxResetPlan, windowSize) == 0);
|
|
assert!(offset_of!(ZSTD_rustCCtxResetPlan, blockSize) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rustCCtxResetPlan, maxNbSeq) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rustCCtxResetPlan, buffInSize) == 3 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rustCCtxResetPlan, buffOutSize) == 4 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rustCCtxResetPlan, maxNbLdmSeq) == 5 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rustCCtxResetPlan, maxNbExternalSeq) == 6 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rustCCtxResetPlan, neededSpace) == 7 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rustCCtxResetPlan, needsIndexReset) == size_of::<[usize; 8]>());
|
|
assert!(offset_of!(ZSTD_rustCCtxResetState, cParams) == 0);
|
|
assert!(
|
|
offset_of!(ZSTD_rustCCtxResetState, ldmEnable) == size_of::<ZSTD_compressionParameters>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rustCCtxResetState, pledgedSrcSize)
|
|
> offset_of!(ZSTD_rustCCtxResetState, dictTooBig)
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rustCCtxResetState, maxBlockSize)
|
|
== offset_of!(ZSTD_rustCCtxResetState, pledgedSrcSize) + size_of::<u64>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rustCCtxResetState, sizing)
|
|
== offset_of!(ZSTD_rustCCtxResetState, maxBlockSize) + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rustCCtxResetState, plan)
|
|
== offset_of!(ZSTD_rustCCtxResetState, sizing) + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
size_of::<ZSTD_rustCCtxResetState>()
|
|
== offset_of!(ZSTD_rustCCtxResetState, plan) + size_of::<usize>()
|
|
);
|
|
};
|
|
|
|
/// Compute the scalar portion of `ZSTD_resetCCtx_internal()`.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_planCCtxReset(state: *const ZSTD_rustCCtxResetState) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
if state.sizing.is_null() || state.plan.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
let ldm_enabled = state.ldmEnable == ZSTD_RUST_PS_ENABLE;
|
|
if ldm_enabled && state.ldmMinMatchLength == 0 {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
let window_limit = 1u64
|
|
.checked_shl(state.cParams.windowLog)
|
|
.unwrap_or(u64::MAX);
|
|
let window_size = (window_limit
|
|
.min(state.pledgedSrcSize)
|
|
.min(usize::MAX as u64) as usize)
|
|
.max(1);
|
|
let block_size = state.maxBlockSize.min(window_size);
|
|
let max_nb_seq = ZSTD_rust_params_maxNbSeq(
|
|
block_size,
|
|
state.cParams.minMatch,
|
|
state.useSequenceProducer,
|
|
);
|
|
let buff_out_size = if state.outBufferBuffered != 0 {
|
|
crate::zstd_compress_api::ZSTD_compressBound(block_size).wrapping_add(1)
|
|
} else {
|
|
0
|
|
};
|
|
let buff_in_size = if state.inBufferBuffered != 0 {
|
|
window_size.wrapping_add(block_size)
|
|
} else {
|
|
0
|
|
};
|
|
let max_nb_ldm_seq = if ldm_enabled {
|
|
block_size / state.ldmMinMatchLength as usize
|
|
} else {
|
|
0
|
|
};
|
|
let max_nb_external_seq = if state.useSequenceProducer != 0 {
|
|
crate::zstd_compress_api::ZSTD_sequenceBound(block_size)
|
|
} else {
|
|
0
|
|
};
|
|
let needed_space = unsafe {
|
|
ZSTD_rust_estimateCCtxWorkspaceSize(
|
|
state.cParams,
|
|
state.ldmEnable,
|
|
state.ldmHashLog,
|
|
state.ldmBucketSizeLog,
|
|
state.ldmMinMatchLength,
|
|
state.isStatic,
|
|
state.useRowMatchFinder,
|
|
buff_in_size,
|
|
buff_out_size,
|
|
state.pledgedSrcSize,
|
|
state.useSequenceProducer,
|
|
state.maxBlockSize,
|
|
state.sizing,
|
|
)
|
|
};
|
|
if ERR_isError(needed_space) {
|
|
return needed_space;
|
|
}
|
|
|
|
unsafe {
|
|
*state.plan = ZSTD_rustCCtxResetPlan {
|
|
windowSize: window_size,
|
|
blockSize: block_size,
|
|
maxNbSeq: max_nb_seq,
|
|
buffInSize: buff_in_size,
|
|
buffOutSize: buff_out_size,
|
|
maxNbLdmSeq: max_nb_ldm_seq,
|
|
maxNbExternalSeq: max_nb_external_seq,
|
|
neededSpace: needed_space,
|
|
needsIndexReset: c_int::from(
|
|
state.indexTooClose != 0 || state.dictTooBig != 0 || state.initialized == 0,
|
|
),
|
|
};
|
|
}
|
|
0
|
|
}
|
|
|
|
type ResetCCtxStorageSetPointer = unsafe extern "C" fn(*mut c_void, c_int, *mut c_void);
|
|
type ResetCCtxStorageSetSize = unsafe extern "C" fn(*mut c_void, c_int, usize);
|
|
type ResetCCtxStorageSetInt = unsafe extern "C" fn(*mut c_void, c_int, c_int);
|
|
type ResetCCtxStorageReserve = unsafe extern "C" fn(*mut c_void, c_int, usize) -> *mut c_void;
|
|
type ResetCCtxStorageReserveFailed = unsafe extern "C" fn(*mut c_void) -> c_int;
|
|
type ResetCCtxStorageZero = unsafe extern "C" fn(*mut c_void, *mut c_void, usize);
|
|
type ResetCCtxStorageCallback = unsafe extern "C" fn(*mut c_void);
|
|
|
|
const RESET_CCTX_RESERVE_ALIGNED64: c_int = 0;
|
|
const RESET_CCTX_RESERVE_BUFFER: c_int = 1;
|
|
|
|
const RESET_CCTX_POINTER_SEQ_START: c_int = 0;
|
|
const RESET_CCTX_POINTER_LDM_HASH: c_int = 1;
|
|
const RESET_CCTX_POINTER_LDM_SEQUENCES: c_int = 2;
|
|
const RESET_CCTX_POINTER_EXTERNAL_SEQUENCES: c_int = 3;
|
|
const RESET_CCTX_POINTER_LITERALS: c_int = 4;
|
|
const RESET_CCTX_POINTER_INPUT_BUFFER: c_int = 5;
|
|
const RESET_CCTX_POINTER_OUTPUT_BUFFER: c_int = 6;
|
|
const RESET_CCTX_POINTER_LL_CODE: c_int = 7;
|
|
const RESET_CCTX_POINTER_ML_CODE: c_int = 8;
|
|
const RESET_CCTX_POINTER_OF_CODE: c_int = 9;
|
|
const RESET_CCTX_POINTER_LDM_BUCKETS: c_int = 10;
|
|
const RESET_CCTX_POINTER_PREV_CBLOCK: c_int = 11;
|
|
const RESET_CCTX_POINTER_NEXT_CBLOCK: c_int = 12;
|
|
const RESET_CCTX_POINTER_TMP_WORKSPACE: c_int = 13;
|
|
|
|
const RESET_CCTX_SIZE_MAX_NB_SEQ: c_int = 0;
|
|
const RESET_CCTX_SIZE_MAX_NB_LIT: c_int = 1;
|
|
const RESET_CCTX_SIZE_MAX_NB_LDM_SEQ: c_int = 2;
|
|
const RESET_CCTX_SIZE_EXTERNAL_SEQ_CAPACITY: c_int = 3;
|
|
const RESET_CCTX_SIZE_INPUT_BUFFER: c_int = 4;
|
|
const RESET_CCTX_SIZE_OUTPUT_BUFFER: c_int = 5;
|
|
const RESET_CCTX_SIZE_TMP_WORKSPACE: c_int = 6;
|
|
|
|
const RESET_CCTX_INT_BUFFERED_POLICY: c_int = 0;
|
|
const RESET_CCTX_INT_INITIALIZED: c_int = 1;
|
|
const RESET_CCTX_INDEX_RESET: c_int = 1;
|
|
|
|
/// C-owned workspace/layout projection for the post-match-state reset tail.
|
|
///
|
|
/// Rust owns the reservation order and size policy. C retains the private
|
|
/// `ZSTD_CCtx`/`ldmState_t` layouts and implements the workspace callbacks.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_resetCCtxStorageState {
|
|
pub callbackContext: *mut c_void,
|
|
pub ldmEnable: c_int,
|
|
pub hasExtSeqProd: c_int,
|
|
pub hashLog: c_uint,
|
|
pub bucketSizeLog: c_uint,
|
|
pub blockSize: usize,
|
|
pub maxNbSeq: usize,
|
|
pub maxNbLdmSeq: usize,
|
|
pub maxNbExternalSeq: usize,
|
|
pub buffInSize: usize,
|
|
pub buffOutSize: usize,
|
|
pub seqDefSize: usize,
|
|
pub ldmEntrySize: usize,
|
|
pub rawSeqSize: usize,
|
|
pub externalSequenceSize: usize,
|
|
pub byteSize: usize,
|
|
pub wildcopyOverlength: usize,
|
|
pub bufferedPolicy: c_int,
|
|
pub setPointer: Option<ResetCCtxStorageSetPointer>,
|
|
pub setSize: Option<ResetCCtxStorageSetSize>,
|
|
pub setInt: Option<ResetCCtxStorageSetInt>,
|
|
pub reserve: Option<ResetCCtxStorageReserve>,
|
|
pub reserveFailed: Option<ResetCCtxStorageReserveFailed>,
|
|
pub zero: Option<ResetCCtxStorageZero>,
|
|
pub windowInit: Option<ResetCCtxStorageCallback>,
|
|
pub resetExternalSequences: Option<ResetCCtxStorageCallback>,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(size_of::<ResetCCtxStorageSetPointer>() == size_of::<usize>());
|
|
assert!(size_of::<ResetCCtxStorageSetSize>() == size_of::<usize>());
|
|
assert!(size_of::<ResetCCtxStorageSetInt>() == size_of::<usize>());
|
|
assert!(size_of::<ResetCCtxStorageReserve>() == size_of::<usize>());
|
|
assert!(size_of::<ResetCCtxStorageReserveFailed>() == size_of::<usize>());
|
|
assert!(size_of::<ResetCCtxStorageZero>() == size_of::<usize>());
|
|
assert!(size_of::<ResetCCtxStorageCallback>() == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_resetCCtxStorageState, callbackContext) == 0);
|
|
assert!(offset_of!(ZSTD_rust_resetCCtxStorageState, ldmEnable) == size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxStorageState, hasExtSeqProd)
|
|
== size_of::<usize>() + size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxStorageState, hashLog)
|
|
> offset_of!(ZSTD_rust_resetCCtxStorageState, hasExtSeqProd)
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxStorageState, bucketSizeLog)
|
|
== offset_of!(ZSTD_rust_resetCCtxStorageState, hashLog) + size_of::<c_uint>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxStorageState, blockSize)
|
|
> offset_of!(ZSTD_rust_resetCCtxStorageState, bucketSizeLog)
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxStorageState, maxNbSeq)
|
|
== offset_of!(ZSTD_rust_resetCCtxStorageState, blockSize) + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxStorageState, maxNbLdmSeq)
|
|
== offset_of!(ZSTD_rust_resetCCtxStorageState, maxNbSeq) + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxStorageState, maxNbExternalSeq)
|
|
== offset_of!(ZSTD_rust_resetCCtxStorageState, maxNbLdmSeq) + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxStorageState, buffInSize)
|
|
== offset_of!(ZSTD_rust_resetCCtxStorageState, maxNbExternalSeq) + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxStorageState, buffOutSize)
|
|
== offset_of!(ZSTD_rust_resetCCtxStorageState, buffInSize) + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxStorageState, seqDefSize)
|
|
== offset_of!(ZSTD_rust_resetCCtxStorageState, buffOutSize) + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxStorageState, ldmEntrySize)
|
|
== offset_of!(ZSTD_rust_resetCCtxStorageState, seqDefSize) + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxStorageState, rawSeqSize)
|
|
== offset_of!(ZSTD_rust_resetCCtxStorageState, ldmEntrySize) + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxStorageState, externalSequenceSize)
|
|
== offset_of!(ZSTD_rust_resetCCtxStorageState, rawSeqSize) + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxStorageState, byteSize)
|
|
== offset_of!(ZSTD_rust_resetCCtxStorageState, externalSequenceSize)
|
|
+ size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxStorageState, wildcopyOverlength)
|
|
== offset_of!(ZSTD_rust_resetCCtxStorageState, byteSize) + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxStorageState, bufferedPolicy)
|
|
> offset_of!(ZSTD_rust_resetCCtxStorageState, wildcopyOverlength)
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxStorageState, setSize)
|
|
== offset_of!(ZSTD_rust_resetCCtxStorageState, setPointer) + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxStorageState, setInt)
|
|
== offset_of!(ZSTD_rust_resetCCtxStorageState, setSize) + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxStorageState, reserve)
|
|
== offset_of!(ZSTD_rust_resetCCtxStorageState, setInt) + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
size_of::<ZSTD_rust_resetCCtxStorageState>()
|
|
== offset_of!(ZSTD_rust_resetCCtxStorageState, resetExternalSequences)
|
|
+ size_of::<usize>()
|
|
);
|
|
};
|
|
|
|
type ResetCCtxWorkspaceCreate = unsafe extern "C" fn(*mut c_void, usize) -> usize;
|
|
type ResetCCtxWorkspaceReserveObject = unsafe extern "C" fn(*mut c_void, usize) -> *mut c_void;
|
|
type ResetCCtxTailCallback = unsafe extern "C" fn(*mut c_void) -> usize;
|
|
|
|
/// C-owned workspace operations used by the CCtx reset policy.
|
|
///
|
|
/// Rust owns the resize decision, error ordering, object-reservation order,
|
|
/// and workspace-clear boundary. C retains the workspace representation,
|
|
/// allocator, and private CCtx publication callbacks.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_resetCCtxWorkspaceState {
|
|
callbackContext: *mut c_void,
|
|
isStatic: c_int,
|
|
workspaceTooSmall: c_int,
|
|
workspaceWasteful: c_int,
|
|
neededSpace: usize,
|
|
compressedBlockStateSize: usize,
|
|
tmpWorkspaceSize: usize,
|
|
needsIndexReset: *mut c_int,
|
|
bumpOversizedDuration: Option<ResetCCtxStorageCallback>,
|
|
freeWorkspace: Option<ResetCCtxStorageCallback>,
|
|
createWorkspace: Option<ResetCCtxWorkspaceCreate>,
|
|
reserveObject: Option<ResetCCtxWorkspaceReserveObject>,
|
|
setPointer: Option<ResetCCtxStorageSetPointer>,
|
|
setSize: Option<ResetCCtxStorageSetSize>,
|
|
clearWorkspace: Option<ResetCCtxStorageCallback>,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_resetCCtxWorkspaceState, callbackContext) == 0);
|
|
assert!(offset_of!(ZSTD_rust_resetCCtxWorkspaceState, isStatic) == size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxWorkspaceState, workspaceTooSmall)
|
|
== size_of::<usize>() + size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxWorkspaceState, workspaceWasteful)
|
|
== size_of::<usize>() + 2 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxWorkspaceState, neededSpace)
|
|
> offset_of!(ZSTD_rust_resetCCtxWorkspaceState, workspaceWasteful)
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxWorkspaceState, compressedBlockStateSize)
|
|
== offset_of!(ZSTD_rust_resetCCtxWorkspaceState, neededSpace) + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxWorkspaceState, tmpWorkspaceSize)
|
|
== offset_of!(ZSTD_rust_resetCCtxWorkspaceState, compressedBlockStateSize)
|
|
+ size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxWorkspaceState, needsIndexReset)
|
|
== offset_of!(ZSTD_rust_resetCCtxWorkspaceState, tmpWorkspaceSize) + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxWorkspaceState, freeWorkspace)
|
|
== offset_of!(ZSTD_rust_resetCCtxWorkspaceState, bumpOversizedDuration)
|
|
+ size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxWorkspaceState, createWorkspace)
|
|
== offset_of!(ZSTD_rust_resetCCtxWorkspaceState, freeWorkspace) + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxWorkspaceState, reserveObject)
|
|
== offset_of!(ZSTD_rust_resetCCtxWorkspaceState, createWorkspace) + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxWorkspaceState, setPointer)
|
|
== offset_of!(ZSTD_rust_resetCCtxWorkspaceState, reserveObject) + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxWorkspaceState, setSize)
|
|
== offset_of!(ZSTD_rust_resetCCtxWorkspaceState, setPointer) + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
size_of::<ZSTD_rust_resetCCtxWorkspaceState>()
|
|
== offset_of!(ZSTD_rust_resetCCtxWorkspaceState, clearWorkspace) + size_of::<usize>()
|
|
);
|
|
};
|
|
|
|
/// C-owned operations used by the post-workspace CCtx reset tail.
|
|
///
|
|
/// Rust owns the operation order, compressed-block reset, and error
|
|
/// propagation. C retains the private context initialization, match-state
|
|
/// reset, and storage publication operations.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_resetCCtxTailState {
|
|
callbackContext: *mut c_void,
|
|
initialize: Option<ResetCCtxStorageCallback>,
|
|
compressedBlockState: *mut ZSTD_compressedBlockState_t,
|
|
resetMatchState: Option<ResetCCtxTailCallback>,
|
|
resetStorage: Option<ResetCCtxTailCallback>,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_resetCCtxTailState, callbackContext) == 0);
|
|
assert!(offset_of!(ZSTD_rust_resetCCtxTailState, initialize) == size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxTailState, compressedBlockState) == 2 * size_of::<usize>()
|
|
);
|
|
assert!(offset_of!(ZSTD_rust_resetCCtxTailState, resetMatchState) == 3 * size_of::<usize>());
|
|
assert!(
|
|
size_of::<ZSTD_rust_resetCCtxTailState>()
|
|
== offset_of!(ZSTD_rust_resetCCtxTailState, resetStorage) + size_of::<usize>()
|
|
);
|
|
};
|
|
|
|
type ResetCCtxWorkspacePrepare =
|
|
unsafe extern "C" fn(*mut c_void, usize, usize, usize, *mut c_int, *mut c_int);
|
|
type ResetCCtxTailPrepare = unsafe extern "C" fn(*mut c_void, usize, c_int);
|
|
|
|
/// State for composing the already-projected CCtx reset policies.
|
|
///
|
|
/// Rust owns the plan/workspace/tail order and the scalar hand-off between
|
|
/// those helpers. C retains the workspace checks, context initialization,
|
|
/// match-state reset, and private pointer publication behind callbacks.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_resetCCtxInternalState {
|
|
pub callbackContext: *mut c_void,
|
|
pub resetState: *const ZSTD_rustCCtxResetState,
|
|
pub storageState: *mut ZSTD_rust_resetCCtxStorageState,
|
|
pub workspaceState: *mut ZSTD_rust_resetCCtxWorkspaceState,
|
|
pub tailState: *mut ZSTD_rust_resetCCtxTailState,
|
|
pub prepareWorkspace: Option<ResetCCtxWorkspacePrepare>,
|
|
pub prepareTail: Option<ResetCCtxTailPrepare>,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(size_of::<ResetCCtxWorkspacePrepare>() == size_of::<usize>());
|
|
assert!(size_of::<ResetCCtxTailPrepare>() == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_resetCCtxInternalState, callbackContext) == 0);
|
|
assert!(offset_of!(ZSTD_rust_resetCCtxInternalState, resetState) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_resetCCtxInternalState, storageState) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_resetCCtxInternalState, workspaceState) == 3 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_resetCCtxInternalState, tailState) == 4 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxInternalState, prepareWorkspace) == 5 * size_of::<usize>()
|
|
);
|
|
assert!(offset_of!(ZSTD_rust_resetCCtxInternalState, prepareTail) == 6 * size_of::<usize>());
|
|
assert!(size_of::<ZSTD_rust_resetCCtxInternalState>() == 7 * size_of::<usize>());
|
|
};
|
|
|
|
#[inline]
|
|
fn project_cctx_reset_plan(
|
|
plan: &ZSTD_rustCCtxResetPlan,
|
|
storage_state: &mut ZSTD_rust_resetCCtxStorageState,
|
|
workspace_state: &mut ZSTD_rust_resetCCtxWorkspaceState,
|
|
) {
|
|
storage_state.blockSize = plan.blockSize;
|
|
storage_state.maxNbSeq = plan.maxNbSeq;
|
|
storage_state.maxNbLdmSeq = plan.maxNbLdmSeq;
|
|
storage_state.maxNbExternalSeq = plan.maxNbExternalSeq;
|
|
storage_state.buffInSize = plan.buffInSize;
|
|
storage_state.buffOutSize = plan.buffOutSize;
|
|
workspace_state.neededSpace = plan.neededSpace;
|
|
}
|
|
|
|
#[inline]
|
|
fn run_cctx_reset_policy(
|
|
plan: impl FnOnce() -> usize,
|
|
prepare_workspace: impl FnOnce(),
|
|
reset_workspace: impl FnOnce() -> usize,
|
|
prepare_tail: impl FnOnce(),
|
|
reset_tail: impl FnOnce() -> usize,
|
|
) -> usize {
|
|
let result = plan();
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
prepare_workspace();
|
|
let result = reset_workspace();
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
prepare_tail();
|
|
reset_tail()
|
|
}
|
|
|
|
/// Compose the private CCtx reset without moving C-only layout operations.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_resetCCtxInternal(
|
|
state: *const ZSTD_rust_resetCCtxInternalState,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
let Some(prepare_workspace) = state.prepareWorkspace else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(prepare_tail) = state.prepareTail else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
if state.callbackContext.is_null()
|
|
|| state.resetState.is_null()
|
|
|| state.storageState.is_null()
|
|
|| state.workspaceState.is_null()
|
|
|| state.tailState.is_null()
|
|
{
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
let reset_state = unsafe { &*state.resetState };
|
|
if reset_state.plan.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let workspace_state = unsafe { &*state.workspaceState };
|
|
if workspace_state.needsIndexReset.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
let plan = reset_state.plan;
|
|
let reset_state_ptr = state.resetState;
|
|
let storage_state_ptr = state.storageState;
|
|
let workspace_state_ptr = state.workspaceState;
|
|
let tail_state_ptr = state.tailState;
|
|
let callback_context = state.callbackContext;
|
|
|
|
run_cctx_reset_policy(
|
|
|| unsafe {
|
|
let result = ZSTD_rust_planCCtxReset(reset_state_ptr);
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
let plan = &*plan;
|
|
let storage_state = &mut *storage_state_ptr;
|
|
let workspace_state = &mut *workspace_state_ptr;
|
|
project_cctx_reset_plan(plan, storage_state, workspace_state);
|
|
*workspace_state.needsIndexReset = plan.needsIndexReset;
|
|
0
|
|
},
|
|
|| unsafe {
|
|
let plan = &*plan;
|
|
let workspace_state = &mut *workspace_state_ptr;
|
|
prepare_workspace(
|
|
callback_context,
|
|
plan.neededSpace,
|
|
plan.windowSize,
|
|
plan.blockSize,
|
|
&mut workspace_state.workspaceTooSmall,
|
|
&mut workspace_state.workspaceWasteful,
|
|
);
|
|
},
|
|
|| unsafe { ZSTD_rust_resetCCtxWorkspace(workspace_state_ptr) },
|
|
|| unsafe {
|
|
let workspace_state = &*workspace_state_ptr;
|
|
prepare_tail(
|
|
callback_context,
|
|
(&*plan).blockSize,
|
|
*workspace_state.needsIndexReset,
|
|
);
|
|
},
|
|
|| unsafe { ZSTD_rust_resetCCtxTail(tail_state_ptr) },
|
|
)
|
|
}
|
|
|
|
/// Reserve and publish the private CCtx storage that follows match-state reset.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_resetCCtxStorage(
|
|
state: *const ZSTD_rust_resetCCtxStorageState,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
if state.callbackContext.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let Some(set_pointer) = state.setPointer else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(set_size) = state.setSize else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(set_int) = state.setInt else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(reserve_callback) = state.reserve else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(reserve_failed) = state.reserveFailed else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(zero) = state.zero else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(window_init) = state.windowInit else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(reset_external_sequences) = state.resetExternalSequences else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
|
|
let reserve = |kind: c_int, size: usize| -> Result<*mut c_void, usize> {
|
|
let pointer = unsafe { reserve_callback(state.callbackContext, kind, size) };
|
|
if unsafe { reserve_failed(state.callbackContext) } != 0 {
|
|
Err(ERROR(ZstdErrorCode::MemoryAllocation))
|
|
} else {
|
|
Ok(pointer)
|
|
}
|
|
};
|
|
let publish_pointer = |kind: c_int, pointer: *mut c_void| unsafe {
|
|
set_pointer(state.callbackContext, kind, pointer)
|
|
};
|
|
let publish_size =
|
|
|kind: c_int, value: usize| unsafe { set_size(state.callbackContext, kind, value) };
|
|
|
|
let sequence_start = match reserve(
|
|
RESET_CCTX_RESERVE_ALIGNED64,
|
|
state.maxNbSeq.wrapping_mul(state.seqDefSize),
|
|
) {
|
|
Ok(pointer) => pointer,
|
|
Err(error) => return error,
|
|
};
|
|
publish_pointer(RESET_CCTX_POINTER_SEQ_START, sequence_start);
|
|
|
|
if state.ldmEnable == ZSTD_RUST_PS_ENABLE {
|
|
if state.bucketSizeLog > state.hashLog {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let Some(hash_size) = 1usize.checked_shl(state.hashLog) else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let hash_table = match reserve(
|
|
RESET_CCTX_RESERVE_ALIGNED64,
|
|
hash_size.wrapping_mul(state.ldmEntrySize),
|
|
) {
|
|
Ok(pointer) => pointer,
|
|
Err(error) => return error,
|
|
};
|
|
publish_pointer(RESET_CCTX_POINTER_LDM_HASH, hash_table);
|
|
unsafe {
|
|
zero(
|
|
state.callbackContext,
|
|
hash_table,
|
|
hash_size.wrapping_mul(state.ldmEntrySize),
|
|
)
|
|
};
|
|
|
|
let ldm_sequences = match reserve(
|
|
RESET_CCTX_RESERVE_ALIGNED64,
|
|
state.maxNbLdmSeq.wrapping_mul(state.rawSeqSize),
|
|
) {
|
|
Ok(pointer) => pointer,
|
|
Err(error) => return error,
|
|
};
|
|
publish_pointer(RESET_CCTX_POINTER_LDM_SEQUENCES, ldm_sequences);
|
|
publish_size(RESET_CCTX_SIZE_MAX_NB_LDM_SEQ, state.maxNbLdmSeq);
|
|
unsafe { window_init(state.callbackContext) };
|
|
}
|
|
|
|
if state.hasExtSeqProd != 0 {
|
|
let external_sequences = match reserve(
|
|
RESET_CCTX_RESERVE_ALIGNED64,
|
|
state
|
|
.maxNbExternalSeq
|
|
.wrapping_mul(state.externalSequenceSize),
|
|
) {
|
|
Ok(pointer) => pointer,
|
|
Err(error) => return error,
|
|
};
|
|
publish_pointer(RESET_CCTX_POINTER_EXTERNAL_SEQUENCES, external_sequences);
|
|
publish_size(
|
|
RESET_CCTX_SIZE_EXTERNAL_SEQ_CAPACITY,
|
|
state.maxNbExternalSeq,
|
|
);
|
|
}
|
|
|
|
let literals = match reserve(
|
|
RESET_CCTX_RESERVE_BUFFER,
|
|
state.blockSize.wrapping_add(state.wildcopyOverlength),
|
|
) {
|
|
Ok(pointer) => pointer,
|
|
Err(error) => return error,
|
|
};
|
|
publish_pointer(RESET_CCTX_POINTER_LITERALS, literals);
|
|
publish_size(RESET_CCTX_SIZE_MAX_NB_LIT, state.blockSize);
|
|
unsafe {
|
|
set_int(
|
|
state.callbackContext,
|
|
RESET_CCTX_INT_BUFFERED_POLICY,
|
|
state.bufferedPolicy,
|
|
)
|
|
};
|
|
|
|
let input_buffer = match reserve(RESET_CCTX_RESERVE_BUFFER, state.buffInSize) {
|
|
Ok(pointer) => pointer,
|
|
Err(error) => return error,
|
|
};
|
|
publish_pointer(RESET_CCTX_POINTER_INPUT_BUFFER, input_buffer);
|
|
publish_size(RESET_CCTX_SIZE_INPUT_BUFFER, state.buffInSize);
|
|
|
|
let output_buffer = match reserve(RESET_CCTX_RESERVE_BUFFER, state.buffOutSize) {
|
|
Ok(pointer) => pointer,
|
|
Err(error) => return error,
|
|
};
|
|
publish_pointer(RESET_CCTX_POINTER_OUTPUT_BUFFER, output_buffer);
|
|
publish_size(RESET_CCTX_SIZE_OUTPUT_BUFFER, state.buffOutSize);
|
|
|
|
if state.ldmEnable == ZSTD_RUST_PS_ENABLE {
|
|
let bucket_count = match 1usize.checked_shl(state.hashLog - state.bucketSizeLog) {
|
|
Some(count) => count,
|
|
None => return ERROR(ZstdErrorCode::Generic),
|
|
};
|
|
let bucket_offsets = match reserve(
|
|
RESET_CCTX_RESERVE_BUFFER,
|
|
bucket_count.wrapping_mul(state.byteSize),
|
|
) {
|
|
Ok(pointer) => pointer,
|
|
Err(error) => return error,
|
|
};
|
|
publish_pointer(RESET_CCTX_POINTER_LDM_BUCKETS, bucket_offsets);
|
|
unsafe {
|
|
zero(
|
|
state.callbackContext,
|
|
bucket_offsets,
|
|
bucket_count.wrapping_mul(state.byteSize),
|
|
)
|
|
};
|
|
}
|
|
|
|
unsafe { reset_external_sequences(state.callbackContext) };
|
|
publish_size(RESET_CCTX_SIZE_MAX_NB_SEQ, state.maxNbSeq);
|
|
|
|
let ll_code = match reserve(
|
|
RESET_CCTX_RESERVE_BUFFER,
|
|
state.maxNbSeq.wrapping_mul(state.byteSize),
|
|
) {
|
|
Ok(pointer) => pointer,
|
|
Err(error) => return error,
|
|
};
|
|
publish_pointer(RESET_CCTX_POINTER_LL_CODE, ll_code);
|
|
let ml_code = match reserve(
|
|
RESET_CCTX_RESERVE_BUFFER,
|
|
state.maxNbSeq.wrapping_mul(state.byteSize),
|
|
) {
|
|
Ok(pointer) => pointer,
|
|
Err(error) => return error,
|
|
};
|
|
publish_pointer(RESET_CCTX_POINTER_ML_CODE, ml_code);
|
|
let of_code = match reserve(
|
|
RESET_CCTX_RESERVE_BUFFER,
|
|
state.maxNbSeq.wrapping_mul(state.byteSize),
|
|
) {
|
|
Ok(pointer) => pointer,
|
|
Err(error) => return error,
|
|
};
|
|
publish_pointer(RESET_CCTX_POINTER_OF_CODE, of_code);
|
|
unsafe { set_int(state.callbackContext, RESET_CCTX_INT_INITIALIZED, 1) };
|
|
0
|
|
}
|
|
|
|
/// Own the CCtx workspace resize, object reservation, and clear order.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_resetCCtxWorkspace(
|
|
state: *const ZSTD_rust_resetCCtxWorkspaceState,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
if state.callbackContext.is_null() || state.needsIndexReset.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let Some(bump_oversized_duration) = state.bumpOversizedDuration else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(free_workspace) = state.freeWorkspace else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(create_workspace) = state.createWorkspace else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(reserve_object) = state.reserveObject else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(set_pointer) = state.setPointer else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(set_size) = state.setSize else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(clear_workspace) = state.clearWorkspace else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
|
|
unsafe {
|
|
if state.isStatic == 0 {
|
|
bump_oversized_duration(state.callbackContext);
|
|
}
|
|
let resize_workspace = state.workspaceTooSmall != 0 || state.workspaceWasteful != 0;
|
|
if resize_workspace {
|
|
if state.isStatic != 0 {
|
|
return ERROR(ZstdErrorCode::MemoryAllocation);
|
|
}
|
|
*state.needsIndexReset = RESET_CCTX_INDEX_RESET;
|
|
free_workspace(state.callbackContext);
|
|
let result = create_workspace(state.callbackContext, state.neededSpace);
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
|
|
let previous = reserve_object(state.callbackContext, state.compressedBlockStateSize);
|
|
if previous.is_null() {
|
|
return ERROR(ZstdErrorCode::MemoryAllocation);
|
|
}
|
|
set_pointer(
|
|
state.callbackContext,
|
|
RESET_CCTX_POINTER_PREV_CBLOCK,
|
|
previous,
|
|
);
|
|
|
|
let next = reserve_object(state.callbackContext, state.compressedBlockStateSize);
|
|
if next.is_null() {
|
|
return ERROR(ZstdErrorCode::MemoryAllocation);
|
|
}
|
|
set_pointer(state.callbackContext, RESET_CCTX_POINTER_NEXT_CBLOCK, next);
|
|
|
|
let tmp_workspace = reserve_object(state.callbackContext, state.tmpWorkspaceSize);
|
|
if tmp_workspace.is_null() {
|
|
return ERROR(ZstdErrorCode::MemoryAllocation);
|
|
}
|
|
set_pointer(
|
|
state.callbackContext,
|
|
RESET_CCTX_POINTER_TMP_WORKSPACE,
|
|
tmp_workspace,
|
|
);
|
|
set_size(
|
|
state.callbackContext,
|
|
RESET_CCTX_SIZE_TMP_WORKSPACE,
|
|
state.tmpWorkspaceSize,
|
|
);
|
|
}
|
|
clear_workspace(state.callbackContext);
|
|
}
|
|
0
|
|
}
|
|
|
|
/// Own the callback order for the post-workspace CCtx reset tail.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_resetCCtxTail(
|
|
state: *const ZSTD_rust_resetCCtxTailState,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
if state.callbackContext.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let Some(initialize) = state.initialize else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(reset_match_state) = state.resetMatchState else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(reset_storage) = state.resetStorage else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
if state.compressedBlockState.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
unsafe {
|
|
initialize(state.callbackContext);
|
|
ZSTD_rust_resetCompressedBlockState(state.compressedBlockState);
|
|
let result = reset_match_state(state.callbackContext);
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
let result = reset_storage(state.callbackContext);
|
|
if ERR_isError(result) {
|
|
return result;
|
|
}
|
|
}
|
|
0
|
|
}
|
|
|
|
#[inline]
|
|
fn max_estimate_cctx_size(
|
|
estimate0: usize,
|
|
estimate1: usize,
|
|
estimate2: usize,
|
|
estimate3: usize,
|
|
) -> usize {
|
|
estimate0.max(estimate1).max(estimate2).max(estimate3)
|
|
}
|
|
|
|
type EstimateCCtxSizeGetCParamsFn =
|
|
unsafe extern "C" fn(*mut c_void, c_int, u64) -> ZSTD_compressionParameters;
|
|
type EstimateCCtxSizeEstimateFn =
|
|
unsafe extern "C" fn(*mut c_void, ZSTD_compressionParameters) -> usize;
|
|
|
|
/// Rust owns the fixed source-size tier policy for CCtx estimates. C keeps
|
|
/// the configuration-sensitive parameter lookup and workspace estimator behind
|
|
/// callbacks, so this seam does not expose any private context layout.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_estimateCCtxSizeState {
|
|
callback_context: *mut c_void,
|
|
compression_level: c_int,
|
|
get_c_params: EstimateCCtxSizeGetCParamsFn,
|
|
estimate: EstimateCCtxSizeEstimateFn,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_estimateCCtxSizeState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_estimateCCtxSizeState, compression_level) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_estimateCCtxSizeState, get_c_params) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_estimateCCtxSizeState, estimate) == 3 * size_of::<usize>());
|
|
assert!(size_of::<ZSTD_rust_estimateCCtxSizeState>() == 4 * size_of::<usize>());
|
|
};
|
|
|
|
/// Evaluate the four source-size tiers used by `ZSTD_estimateCCtxSize()` and
|
|
/// return the largest raw result, including size_t-encoded error values.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_estimateCCtxSizeInternal(
|
|
state: *const ZSTD_rust_estimateCCtxSizeState,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
let state = unsafe { &*state };
|
|
let source_size_tiers = [16 * 1024, 128 * 1024, 256 * 1024, ZSTD_CONTENTSIZE_UNKNOWN];
|
|
let mut estimates = [0usize; 4];
|
|
for (estimate, &source_size_hint) in estimates.iter_mut().zip(source_size_tiers.iter()) {
|
|
let c_params = unsafe {
|
|
(state.get_c_params)(
|
|
state.callback_context,
|
|
state.compression_level,
|
|
source_size_hint,
|
|
)
|
|
};
|
|
*estimate = unsafe { (state.estimate)(state.callback_context, c_params) };
|
|
}
|
|
|
|
max_estimate_cctx_size(estimates[0], estimates[1], estimates[2], estimates[3])
|
|
}
|
|
|
|
/// Return the largest raw estimate, including any C size_t error values.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_maxEstimateCCtxSize(
|
|
estimate0: usize,
|
|
estimate1: usize,
|
|
estimate2: usize,
|
|
estimate3: usize,
|
|
) -> usize {
|
|
max_estimate_cctx_size(estimate0, estimate1, estimate2, estimate3)
|
|
}
|
|
|
|
#[inline]
|
|
fn reduce_table_internal(table: &mut [u32], reducer_value: u32, preserve_mark: bool) {
|
|
debug_assert_eq!(table.len() % ZSTD_ROWSIZE, 0);
|
|
debug_assert!(table.len() < (1usize << 31));
|
|
|
|
/* Protect special index values < ZSTD_WINDOW_START_INDEX. */
|
|
let reducer_threshold = reducer_value.wrapping_add(ZSTD_WINDOW_START_INDEX);
|
|
let mut rows = table.chunks_exact_mut(ZSTD_ROWSIZE);
|
|
for row in &mut rows {
|
|
for cell in row {
|
|
let value = *cell;
|
|
*cell = if preserve_mark && value == ZSTD_DUBT_UNSORTED_MARK {
|
|
/* Keep the btlazy2 unsorted marker across table reduction. */
|
|
ZSTD_DUBT_UNSORTED_MARK
|
|
} else if value < reducer_threshold {
|
|
0
|
|
} else {
|
|
value.wrapping_sub(reducer_value)
|
|
};
|
|
}
|
|
}
|
|
debug_assert!(rows.into_remainder().is_empty());
|
|
}
|
|
|
|
/// Rust implementation of the C match-table reduction leaf.
|
|
///
|
|
/// The C caller supplies a clear zero/one `preserve_mark` value for the
|
|
/// strategy-specific btlazy2 policy.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_reduceTable(
|
|
table: *mut u32,
|
|
size: u32,
|
|
reducer_value: u32,
|
|
preserve_mark: c_int,
|
|
) {
|
|
debug_assert!(!table.is_null() || size == 0);
|
|
debug_assert_eq!(size % ZSTD_ROWSIZE as u32, 0);
|
|
debug_assert!(size < (1u32 << 31));
|
|
if size == 0 {
|
|
return;
|
|
}
|
|
|
|
let table = unsafe { std::slice::from_raw_parts_mut(table, size as usize) };
|
|
reduce_table_internal(table, reducer_value, preserve_mark != 0);
|
|
}
|
|
|
|
/// Reduce the match tables selected by C's stateful overflow-correction path.
|
|
///
|
|
/// C retains match-state access and table selection. Each non-zero size is
|
|
/// paired with a validated mutable table pointer; zero-sized optional tables
|
|
/// may use null pointers. Only the chain table receives the btlazy2 marker
|
|
/// policy.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_reduceIndex(
|
|
hash_table: *mut u32,
|
|
hash_size: u32,
|
|
chain_table: *mut u32,
|
|
chain_size: u32,
|
|
hash_table3: *mut u32,
|
|
hash_size3: u32,
|
|
reducer_value: u32,
|
|
preserve_chain_mark: c_int,
|
|
) {
|
|
debug_assert!(!hash_table.is_null() || hash_size == 0);
|
|
debug_assert!(!chain_table.is_null() || chain_size == 0);
|
|
debug_assert!(!hash_table3.is_null() || hash_size3 == 0);
|
|
debug_assert!(preserve_chain_mark == 0 || preserve_chain_mark == 1);
|
|
|
|
unsafe {
|
|
ZSTD_rust_reduceTable(hash_table, hash_size, reducer_value, 0);
|
|
ZSTD_rust_reduceTable(chain_table, chain_size, reducer_value, preserve_chain_mark);
|
|
ZSTD_rust_reduceTable(hash_table3, hash_size3, reducer_value, 0);
|
|
}
|
|
}
|
|
|
|
/// Select the match tables participating in overflow correction and delegate
|
|
/// their cell updates to the Rust reducer. C supplies private table pointers;
|
|
/// Rust owns the strategy, row-matchfinder, dedicated-dictionary, and marker
|
|
/// policy that determines the participating table set.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_reduceIndexForMatchState(
|
|
hash_table: *mut u32,
|
|
hash_log: c_uint,
|
|
chain_table: *mut u32,
|
|
chain_log: c_uint,
|
|
hash_table3: *mut u32,
|
|
hash_log3: c_uint,
|
|
strategy: c_int,
|
|
use_row_match_finder: c_int,
|
|
dedicated_dict_search: c_int,
|
|
reducer_value: c_uint,
|
|
) {
|
|
let hash_size = 1u32.wrapping_shl(hash_log);
|
|
let (chain_table, chain_size) = if ZSTD_rust_params_allocateChainTable(
|
|
strategy,
|
|
use_row_match_finder,
|
|
dedicated_dict_search,
|
|
) != 0
|
|
{
|
|
(chain_table, 1u32.wrapping_shl(chain_log))
|
|
} else {
|
|
(ptr::null_mut(), 0)
|
|
};
|
|
let (hash_table3, hash_size3) = if hash_log3 != 0 {
|
|
(hash_table3, 1u32.wrapping_shl(hash_log3))
|
|
} else {
|
|
(ptr::null_mut(), 0)
|
|
};
|
|
unsafe {
|
|
ZSTD_rust_reduceIndex(
|
|
hash_table,
|
|
hash_size,
|
|
chain_table,
|
|
chain_size,
|
|
hash_table3,
|
|
hash_size3,
|
|
reducer_value,
|
|
c_int::from(strategy == ZSTD_BTLAZY2),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Copies a CDict match table into a CCtx, removing short-cache tags when the
|
|
/// C-owned compression parameters say the source table is tagged.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_copyCDictTableIntoCCtx(
|
|
dst: *mut u32,
|
|
src: *const u32,
|
|
table_size: usize,
|
|
tagged: c_int,
|
|
) {
|
|
if tagged != 0 {
|
|
for i in 0..table_size {
|
|
let index = unsafe { *src.add(i) } >> ZSTD_SHORT_CACHE_TAG_BITS;
|
|
unsafe { *dst.add(i) = index };
|
|
}
|
|
} else if table_size != 0 {
|
|
unsafe { ptr::copy_nonoverlapping(src, dst, table_size) };
|
|
}
|
|
}
|
|
|
|
/// Clear the previous block's repcodes before the next regular compression.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_invalidateRepCodes(rep: *mut u32) {
|
|
debug_assert!(!rep.is_null());
|
|
let rep = unsafe { std::slice::from_raw_parts_mut(rep, ZSTD_REP_NUM) };
|
|
rep.fill(0);
|
|
}
|
|
|
|
/// Clear a C-owned compression window using the original `size_t`-to-`U32`
|
|
/// conversion. The caller computes the pointer difference while the private
|
|
/// `ZSTD_window_t` layout remains entirely on the C side.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_windowClear(
|
|
end_t: usize,
|
|
low_limit: *mut u32,
|
|
dict_limit: *mut u32,
|
|
) {
|
|
debug_assert!(!low_limit.is_null());
|
|
debug_assert!(!dict_limit.is_null());
|
|
let end = end_t as u32;
|
|
unsafe {
|
|
*low_limit = end;
|
|
*dict_limit = end;
|
|
}
|
|
}
|
|
|
|
/// Invalidate a C-owned match-state projection after the caller computes the
|
|
/// private window pointer difference. Rust owns the paired window-limit and
|
|
/// match-state field updates while C retains the match-state layout.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_invalidateMatchState(
|
|
end_t: usize,
|
|
low_limit: *mut u32,
|
|
dict_limit: *mut u32,
|
|
next_to_update: *mut u32,
|
|
loaded_dict_end: *mut u32,
|
|
lit_length_sum: *mut u32,
|
|
dict_match_state: *mut *const c_void,
|
|
) {
|
|
debug_assert!(!low_limit.is_null());
|
|
debug_assert!(!dict_limit.is_null());
|
|
debug_assert!(!next_to_update.is_null());
|
|
debug_assert!(!loaded_dict_end.is_null());
|
|
debug_assert!(!lit_length_sum.is_null());
|
|
debug_assert!(!dict_match_state.is_null());
|
|
unsafe {
|
|
ZSTD_rust_windowClear(end_t, low_limit, dict_limit);
|
|
*next_to_update = *dict_limit;
|
|
*loaded_dict_end = 0;
|
|
*lit_length_sum = 0;
|
|
*dict_match_state = ptr::null();
|
|
}
|
|
}
|
|
|
|
const HASH_READ_SIZE: u32 = 8;
|
|
|
|
/// Private C window fields projected for the shared window-update policy.
|
|
/// Pointer arithmetic is intentionally performed as wrapping address math to
|
|
/// mirror the original C implementation's pointer-overflow contract.
|
|
#[repr(C)]
|
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
pub struct ZSTD_rust_windowUpdateState {
|
|
pub nextSrc: *const c_void,
|
|
pub base: *const c_void,
|
|
pub dictBase: *const c_void,
|
|
pub dictLimit: u32,
|
|
pub lowLimit: u32,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_windowUpdateState, nextSrc) == 0);
|
|
assert!(offset_of!(ZSTD_rust_windowUpdateState, base) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_windowUpdateState, dictBase) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_windowUpdateState, dictLimit) == 3 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_windowUpdateState, lowLimit)
|
|
== 3 * size_of::<usize>() + size_of::<u32>()
|
|
);
|
|
assert!(
|
|
size_of::<ZSTD_rust_windowUpdateState>() == 3 * size_of::<usize>() + 2 * size_of::<u32>()
|
|
);
|
|
};
|
|
|
|
static WINDOW_INIT_SENTINEL: [u8; 2] = [b' ', 0];
|
|
|
|
/// Initialize the projected window fields. C retains the containing object
|
|
/// and its overflow-correction counter.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_windowInit(state: *mut ZSTD_rust_windowUpdateState) {
|
|
if state.is_null() {
|
|
return;
|
|
}
|
|
let base = WINDOW_INIT_SENTINEL.as_ptr();
|
|
unsafe {
|
|
*state = ZSTD_rust_windowUpdateState {
|
|
nextSrc: base.wrapping_add(2).cast(),
|
|
base: base.cast(),
|
|
dictBase: base.cast(),
|
|
dictLimit: ZSTD_WINDOW_START_INDEX,
|
|
lowLimit: ZSTD_WINDOW_START_INDEX,
|
|
};
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_windowIsEmpty(
|
|
next_src: *const c_void,
|
|
base: *const c_void,
|
|
dict_limit: u32,
|
|
low_limit: u32,
|
|
) -> c_uint {
|
|
(dict_limit == ZSTD_WINDOW_START_INDEX
|
|
&& low_limit == ZSTD_WINDOW_START_INDEX
|
|
&& (next_src as usize).wrapping_sub(base as usize) == ZSTD_WINDOW_START_INDEX as usize)
|
|
as c_uint
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_windowHasExtDict(dict_limit: u32, low_limit: u32) -> c_uint {
|
|
(low_limit < dict_limit) as c_uint
|
|
}
|
|
|
|
/// Project the dictionary/window limit policy while C retains pointer-owned
|
|
/// match-state invalidation and the private window layout.
|
|
#[repr(C)]
|
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
pub struct ZSTD_rust_windowDictState {
|
|
pub lowLimit: u32,
|
|
pub dictLimit: u32,
|
|
pub loadedDictEnd: u32,
|
|
pub invalidate: u32,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_windowDictState, lowLimit) == 0);
|
|
assert!(offset_of!(ZSTD_rust_windowDictState, dictLimit) == size_of::<u32>());
|
|
assert!(offset_of!(ZSTD_rust_windowDictState, loadedDictEnd) == 2 * size_of::<u32>());
|
|
assert!(offset_of!(ZSTD_rust_windowDictState, invalidate) == 3 * size_of::<u32>());
|
|
assert!(size_of::<ZSTD_rust_windowDictState>() == 4 * size_of::<u32>());
|
|
};
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_windowEnforceMaxDist(
|
|
block_end_idx: u32,
|
|
max_dist: u32,
|
|
mut state: ZSTD_rust_windowDictState,
|
|
) -> ZSTD_rust_windowDictState {
|
|
state.invalidate = 0;
|
|
if block_end_idx > max_dist.wrapping_add(state.loadedDictEnd) {
|
|
let new_low_limit = block_end_idx.wrapping_sub(max_dist);
|
|
if state.lowLimit < new_low_limit {
|
|
state.lowLimit = new_low_limit;
|
|
}
|
|
if state.dictLimit < state.lowLimit {
|
|
state.dictLimit = state.lowLimit;
|
|
}
|
|
state.loadedDictEnd = 0;
|
|
state.invalidate = 1;
|
|
}
|
|
state
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_windowCheckDictValidity(
|
|
block_end_idx: u32,
|
|
max_dist: u32,
|
|
loaded_dict_end: u32,
|
|
dict_limit: u32,
|
|
) -> c_uint {
|
|
(block_end_idx > loaded_dict_end.wrapping_add(max_dist) || loaded_dict_end != dict_limit)
|
|
as c_uint
|
|
}
|
|
|
|
/// Update the rolling prefix/ext-dictionary window for one input segment.
|
|
/// C owns the containing match state; Rust owns this five-field transition.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_windowUpdate(
|
|
state: *mut ZSTD_rust_windowUpdateState,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
force_non_contiguous: c_int,
|
|
) -> c_uint {
|
|
if state.is_null() {
|
|
return 1;
|
|
}
|
|
let state = unsafe { &mut *state };
|
|
if src_size == 0 {
|
|
return 1;
|
|
}
|
|
|
|
debug_assert!(!state.base.is_null());
|
|
debug_assert!(!state.dictBase.is_null());
|
|
|
|
let ip = src as usize;
|
|
let input_end = ip.wrapping_add(src_size);
|
|
let mut contiguous = 1;
|
|
if ip != state.nextSrc as usize || force_non_contiguous != 0 {
|
|
let distance_from_base = (state.nextSrc as usize).wrapping_sub(state.base as usize);
|
|
state.lowLimit = state.dictLimit;
|
|
debug_assert!(distance_from_base <= u32::MAX as usize);
|
|
state.dictLimit = distance_from_base as u32;
|
|
state.dictBase = state.base;
|
|
state.base = ip.wrapping_sub(distance_from_base) as *const c_void;
|
|
if state.dictLimit.wrapping_sub(state.lowLimit) < HASH_READ_SIZE {
|
|
state.lowLimit = state.dictLimit;
|
|
}
|
|
contiguous = 0;
|
|
}
|
|
|
|
state.nextSrc = input_end as *const c_void;
|
|
let dict_base = state.dictBase as usize;
|
|
if input_end > dict_base.wrapping_add(state.lowLimit as usize)
|
|
&& ip < dict_base.wrapping_add(state.dictLimit as usize)
|
|
{
|
|
let high_input_idx = input_end.wrapping_sub(dict_base);
|
|
debug_assert!(high_input_idx < u32::MAX as usize);
|
|
let low_limit_max = if high_input_idx > state.dictLimit as usize {
|
|
state.dictLimit
|
|
} else {
|
|
high_input_idx as u32
|
|
};
|
|
state.lowLimit = low_limit_max;
|
|
}
|
|
contiguous
|
|
}
|
|
|
|
#[inline]
|
|
fn set_pledged_src_size(
|
|
stream_stage: c_int,
|
|
pledged_src_size: u64,
|
|
pledged_src_size_plus_one: *mut u64,
|
|
) -> usize {
|
|
if stream_stage != ZSTD_CSTREAM_STAGE_INIT {
|
|
return ERROR(ZstdErrorCode::StageWrong);
|
|
}
|
|
debug_assert!(!pledged_src_size_plus_one.is_null());
|
|
unsafe {
|
|
*pledged_src_size_plus_one = pledged_src_size.wrapping_add(1);
|
|
}
|
|
0
|
|
}
|
|
|
|
/// Set the pledged source size through scalar stage state and a C-owned field.
|
|
/// The stage error remains the public `stage_wrong` encoding, while the
|
|
/// unsigned increment preserves C's defined wrap at `U64::MAX`.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_setPledgedSrcSize(
|
|
stream_stage: c_int,
|
|
pledged_src_size: u64,
|
|
pledged_src_size_plus_one: *mut u64,
|
|
) -> usize {
|
|
set_pledged_src_size(stream_stage, pledged_src_size, pledged_src_size_plus_one)
|
|
}
|
|
|
|
#[inline]
|
|
fn zeroed_state() -> ZSTD_compressedBlockState_t {
|
|
/* The state contains only integer arrays and enum fields. */
|
|
unsafe { MaybeUninit::<ZSTD_compressedBlockState_t>::zeroed().assume_init() }
|
|
}
|
|
|
|
#[inline]
|
|
fn checked_table_size(log: u32) -> Option<usize> {
|
|
1usize.checked_shl(log)
|
|
}
|
|
|
|
#[inline]
|
|
fn ceil_log2(size: usize) -> u32 {
|
|
if size <= 1 {
|
|
0
|
|
} else {
|
|
usize::BITS - (size - 1).leading_zeros()
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
unsafe fn write_empty_sequence_block(dst: *mut u8, dst_capacity: usize) -> usize {
|
|
/* Keep the original C helper's four-byte write and capacity check. */
|
|
if dst_capacity < 4 {
|
|
return ERROR(ZstdErrorCode::DstSizeTooSmall);
|
|
}
|
|
let header = 1u32.to_le_bytes();
|
|
unsafe { ptr::copy_nonoverlapping(header.as_ptr(), dst, header.len()) };
|
|
ZSTD_BLOCK_HEADER_SIZE
|
|
}
|
|
|
|
/// Rust implementation of the per-block loop from
|
|
/// `ZSTD_compressSequences_internal()`.
|
|
///
|
|
/// Public initialization, frame-header/checksum ordering, and validation are
|
|
/// driven by the Rust API orchestrator below. The projected state keeps the
|
|
/// ABI explicit while allowing this loop to reuse the existing Rust
|
|
/// sequence-transfer, entropy, and block-serialization leaves.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_compressSequencesInternal(
|
|
state: *const ZSTD_rust_sequenceCompressionState,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
in_seqs: *const ZSTD_Sequence,
|
|
in_seqs_size: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
if state.seq_store.is_null()
|
|
|| state.prev_c_block.is_null()
|
|
|| state.next_c_block.is_null()
|
|
|| state.is_first_block.is_null()
|
|
{
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
if unsafe { (*state.prev_c_block).is_null() || (*state.next_c_block).is_null() } {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
let mut c_size = 0usize;
|
|
let mut remaining = src_size;
|
|
let mut seq_pos = ZSTD_SequencePosition::default();
|
|
let mut ip = src.cast::<u8>();
|
|
let mut op = dst.cast::<u8>();
|
|
let mut dst_capacity = dst_capacity;
|
|
let explicit_delimiters =
|
|
ZSTD_rust_selectSequenceCopier(state.block_delimiters) == ZSTD_SF_EXPLICIT_BLOCK_DELIMITERS;
|
|
|
|
/* Special case: empty frame. */
|
|
if remaining == 0 {
|
|
return unsafe { write_empty_sequence_block(op, dst_capacity) };
|
|
}
|
|
|
|
while remaining != 0 {
|
|
let mut block_size = unsafe {
|
|
ZSTD_rust_determineBlockSize(
|
|
state.block_delimiters,
|
|
state.block_size_max,
|
|
remaining,
|
|
in_seqs,
|
|
in_seqs_size,
|
|
seq_pos.idx,
|
|
)
|
|
};
|
|
let last_block = u32::from(block_size == remaining);
|
|
if ERR_isError(block_size) {
|
|
return block_size;
|
|
}
|
|
debug_assert!(block_size <= remaining);
|
|
|
|
unsafe { ZSTD_rust_resetSeqStore(state.seq_store) };
|
|
|
|
let prev_block = unsafe { *state.prev_c_block };
|
|
let next_block = unsafe { *state.next_c_block };
|
|
block_size = if explicit_delimiters {
|
|
unsafe {
|
|
ZSTD_rust_transferSequencesWBlockDelim(
|
|
state.seq_store,
|
|
&mut seq_pos,
|
|
in_seqs,
|
|
in_seqs_size,
|
|
ip,
|
|
block_size,
|
|
state.search_for_external_repcodes,
|
|
(*prev_block).rep.as_ptr(),
|
|
(*next_block).rep.as_mut_ptr(),
|
|
state.dict_size,
|
|
state.validate_sequences,
|
|
state.min_match,
|
|
state.window_log,
|
|
state.use_sequence_producer,
|
|
)
|
|
}
|
|
} else {
|
|
unsafe {
|
|
ZSTD_rust_transferSequencesNoDelim(
|
|
state.seq_store,
|
|
&mut seq_pos,
|
|
in_seqs,
|
|
in_seqs_size,
|
|
ip,
|
|
block_size,
|
|
(*prev_block).rep.as_ptr(),
|
|
(*next_block).rep.as_mut_ptr(),
|
|
state.dict_size,
|
|
state.validate_sequences,
|
|
state.min_match,
|
|
state.window_log,
|
|
state.use_sequence_producer,
|
|
)
|
|
}
|
|
};
|
|
if ERR_isError(block_size) {
|
|
return block_size;
|
|
}
|
|
|
|
/* If blocks are too small, emit as a nocompress block. */
|
|
if block_size < MIN_COMPRESSIBLE_BLOCK_SIZE {
|
|
let c_block_size = unsafe {
|
|
ZSTD_rust_noCompressBlock(
|
|
op.cast(),
|
|
dst_capacity,
|
|
ip.cast(),
|
|
block_size,
|
|
last_block,
|
|
)
|
|
};
|
|
if ERR_isError(c_block_size) {
|
|
return c_block_size;
|
|
}
|
|
c_size = c_size.wrapping_add(c_block_size);
|
|
unsafe {
|
|
ip = ip.add(block_size);
|
|
op = op.add(c_block_size);
|
|
}
|
|
remaining -= block_size;
|
|
dst_capacity -= c_block_size;
|
|
continue;
|
|
}
|
|
|
|
if dst_capacity < ZSTD_BLOCK_HEADER_SIZE {
|
|
return ERROR(ZstdErrorCode::DstSizeTooSmall);
|
|
}
|
|
|
|
let compressed_size = unsafe {
|
|
ZSTD_rust_entropyCompressSeqStore(
|
|
state.seq_store,
|
|
ptr::addr_of!((*prev_block).entropy),
|
|
ptr::addr_of_mut!((*next_block).entropy),
|
|
state.strategy,
|
|
state.disable_literal_compression,
|
|
op.add(ZSTD_BLOCK_HEADER_SIZE).cast(),
|
|
dst_capacity - ZSTD_BLOCK_HEADER_SIZE,
|
|
block_size,
|
|
state.tmp_workspace,
|
|
state.tmp_wksp_size,
|
|
state.bmi2,
|
|
)
|
|
};
|
|
if ERR_isError(compressed_size) {
|
|
return compressed_size;
|
|
}
|
|
|
|
let is_first_block = unsafe { *state.is_first_block };
|
|
let (maybe_rle, is_rle) = if is_first_block == 0 {
|
|
let maybe_rle = unsafe { ZSTD_rust_maybeRLE(state.seq_store) };
|
|
let is_rle = if maybe_rle != 0 {
|
|
unsafe { ZSTD_rust_isRLE(ip, block_size) }
|
|
} else {
|
|
0
|
|
};
|
|
(maybe_rle, is_rle)
|
|
} else {
|
|
(0, 0)
|
|
};
|
|
let action = sequence_block_action(is_first_block, maybe_rle, is_rle, compressed_size);
|
|
|
|
let c_block_size = match action {
|
|
SequenceBlockAction::Raw => unsafe {
|
|
/* ZSTD_noCompressBlock writes the block header as well. */
|
|
ZSTD_rust_noCompressBlock(
|
|
op.cast(),
|
|
dst_capacity,
|
|
ip.cast(),
|
|
block_size,
|
|
last_block,
|
|
)
|
|
},
|
|
SequenceBlockAction::Rle => unsafe {
|
|
ZSTD_rust_rleCompressBlock(op.cast(), dst_capacity, *ip, block_size, last_block)
|
|
},
|
|
SequenceBlockAction::Compressed => {
|
|
/* Error checking and repcodes update. */
|
|
unsafe {
|
|
ZSTD_rust_confirmRepcodesAndEntropyTables(
|
|
state.prev_c_block,
|
|
state.next_c_block,
|
|
);
|
|
}
|
|
let prev_block = unsafe { *state.prev_c_block };
|
|
if unsafe { (*prev_block).entropy.fse.offcode_repeatMode } == 2 {
|
|
unsafe { (*prev_block).entropy.fse.offcode_repeatMode = 1 };
|
|
}
|
|
|
|
unsafe {
|
|
ZSTD_rust_writeBlockHeader(op.cast(), compressed_size, block_size, last_block)
|
|
};
|
|
ZSTD_BLOCK_HEADER_SIZE + compressed_size
|
|
}
|
|
};
|
|
if ERR_isError(c_block_size) {
|
|
return c_block_size;
|
|
}
|
|
|
|
c_size = c_size.wrapping_add(c_block_size);
|
|
if last_block != 0 {
|
|
break;
|
|
}
|
|
|
|
unsafe {
|
|
ip = ip.add(block_size);
|
|
op = op.add(c_block_size);
|
|
*state.is_first_block = 0;
|
|
}
|
|
remaining -= block_size;
|
|
dst_capacity -= c_block_size;
|
|
}
|
|
|
|
c_size
|
|
}
|
|
|
|
/// Compress one frame using the already migrated block leaves.
|
|
///
|
|
/// This path owns the match tables and carries their history across the
|
|
/// 128 KiB block boundaries, while keeping the private C
|
|
/// `ZSTD_MatchState_t` window out of the Rust ABI.
|
|
/// Writes the three-byte raw header used by the external
|
|
/// sequences-and-literals empty-frame special case.
|
|
#[inline]
|
|
unsafe fn write_empty_sequence_literals_block(dst: *mut u8, dst_capacity: usize) -> usize {
|
|
if dst_capacity < ZSTD_BLOCK_HEADER_SIZE {
|
|
return ERROR(ZstdErrorCode::DstSizeTooSmall);
|
|
}
|
|
let header = [1u8, 0, 0];
|
|
unsafe { ptr::copy_nonoverlapping(header.as_ptr(), dst, header.len()) };
|
|
ZSTD_BLOCK_HEADER_SIZE
|
|
}
|
|
|
|
/// Rust implementation of the block loop from
|
|
/// ZSTD_compressSequencesAndLiterals_internal.
|
|
///
|
|
/// The C wrapper retains the private CCtx-dependent state and sequence
|
|
/// conversion callback. Rust owns the public validation/ordering layer, the
|
|
/// external literal cursor, per-block entropy pass, compressed-block framing,
|
|
/// and completion checks.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_compressSequencesAndLiteralsInternal(
|
|
state: *const ZSTD_rust_sequenceLiteralsState,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
in_seqs: *const ZSTD_Sequence,
|
|
nb_sequences: usize,
|
|
literals: *const c_void,
|
|
lit_size: usize,
|
|
src_size: usize,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
if state.seq_store.is_null()
|
|
|| state.prev_c_block.is_null()
|
|
|| state.next_c_block.is_null()
|
|
|| state.is_first_block.is_null()
|
|
|| unsafe { (*state.prev_c_block).is_null() || (*state.next_c_block).is_null() }
|
|
{
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
if nb_sequences == 0 {
|
|
return ERROR(ZstdErrorCode::ExternalSequencesInvalid);
|
|
}
|
|
|
|
let mut remaining = src_size;
|
|
let mut nb_sequences = nb_sequences;
|
|
let mut c_size = 0usize;
|
|
let mut op = dst.cast::<u8>();
|
|
let mut dst_capacity = dst_capacity;
|
|
let mut in_seqs = in_seqs;
|
|
let mut literals = literals;
|
|
let mut lit_size = lit_size;
|
|
|
|
/* Special case: empty frame. Keep the C ordering: this header is emitted
|
|
* before the ordinary sequence loop. */
|
|
if nb_sequences == 1 && unsafe { (*in_seqs).litLength } == 0 {
|
|
let written = unsafe { write_empty_sequence_literals_block(op, dst_capacity) };
|
|
if ERR_isError(written) {
|
|
return written;
|
|
}
|
|
unsafe {
|
|
op = op.add(written);
|
|
}
|
|
dst_capacity -= written;
|
|
c_size += written;
|
|
}
|
|
|
|
while nb_sequences != 0 {
|
|
let block = unsafe { ZSTD_rust_get1BlockSummary(in_seqs, nb_sequences) };
|
|
let last_block = c_uint::from(block.nbSequences == nb_sequences);
|
|
if ERR_isError(block.nbSequences) {
|
|
return block.nbSequences;
|
|
}
|
|
debug_assert!(block.nbSequences <= nb_sequences);
|
|
if block.litSize > lit_size {
|
|
return ERROR(ZstdErrorCode::ExternalSequencesInvalid);
|
|
}
|
|
|
|
unsafe { ZSTD_rust_resetSeqStore(state.seq_store) };
|
|
let conversion_status = unsafe {
|
|
(state.convert_block_sequences)(
|
|
state.callback_context,
|
|
in_seqs,
|
|
block.nbSequences,
|
|
state.repcode_resolution,
|
|
)
|
|
};
|
|
if ERR_isError(conversion_status) {
|
|
return conversion_status;
|
|
}
|
|
|
|
unsafe {
|
|
in_seqs = in_seqs.add(block.nbSequences);
|
|
}
|
|
nb_sequences -= block.nbSequences;
|
|
remaining = remaining.wrapping_sub(block.blockSize);
|
|
|
|
if dst_capacity < ZSTD_BLOCK_HEADER_SIZE {
|
|
return ERROR(ZstdErrorCode::DstSizeTooSmall);
|
|
}
|
|
|
|
let prev_block = unsafe { *state.prev_c_block };
|
|
let next_block = unsafe { *state.next_c_block };
|
|
if prev_block.is_null() || next_block.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let compressed_size = unsafe {
|
|
ZSTD_rust_entropyCompressSeqStore_internal(
|
|
op.add(ZSTD_BLOCK_HEADER_SIZE).cast(),
|
|
dst_capacity - ZSTD_BLOCK_HEADER_SIZE,
|
|
literals,
|
|
block.litSize,
|
|
state.seq_store,
|
|
ptr::addr_of!((*prev_block).entropy),
|
|
ptr::addr_of_mut!((*next_block).entropy),
|
|
state.strategy,
|
|
state.disable_literal_compression,
|
|
state.tmp_workspace,
|
|
state.tmp_wksp_size,
|
|
state.bmi2,
|
|
)
|
|
};
|
|
if ERR_isError(compressed_size) {
|
|
return compressed_size;
|
|
}
|
|
|
|
let compressed_size = if compressed_size > state.block_size_max {
|
|
0
|
|
} else {
|
|
compressed_size
|
|
};
|
|
unsafe {
|
|
literals = literals.cast::<u8>().add(block.litSize).cast();
|
|
}
|
|
lit_size = lit_size.wrapping_sub(block.litSize);
|
|
|
|
if compressed_size == 0 {
|
|
return ERROR(ZstdErrorCode::CannotProduceUncompressedBlock);
|
|
}
|
|
|
|
debug_assert!(compressed_size > 1);
|
|
unsafe {
|
|
ZSTD_rust_confirmRepcodesAndEntropyTables(state.prev_c_block, state.next_c_block);
|
|
}
|
|
let prev_block = unsafe { *state.prev_c_block };
|
|
if prev_block.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
if unsafe { (*prev_block).entropy.fse.offcode_repeatMode } == 2 {
|
|
unsafe { (*prev_block).entropy.fse.offcode_repeatMode = 1 };
|
|
}
|
|
|
|
unsafe {
|
|
ZSTD_rust_writeBlockHeader(op.cast(), compressed_size, block.blockSize, last_block);
|
|
}
|
|
let c_block_size = ZSTD_BLOCK_HEADER_SIZE + compressed_size;
|
|
c_size = c_size.wrapping_add(c_block_size);
|
|
unsafe {
|
|
op = op.add(c_block_size);
|
|
*state.is_first_block = 0;
|
|
}
|
|
dst_capacity = dst_capacity.wrapping_sub(c_block_size);
|
|
|
|
if last_block != 0 {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if lit_size != 0 || remaining != 0 {
|
|
return ERROR(ZstdErrorCode::ExternalSequencesInvalid);
|
|
}
|
|
c_size
|
|
}
|
|
|
|
unsafe fn sequence_api_prepare(
|
|
state: &mut ZSTD_rust_sequenceApiState,
|
|
pledged_src_size: usize,
|
|
with_literals: bool,
|
|
lit_size: usize,
|
|
lit_capacity: usize,
|
|
) -> Result<SequenceApiPlan, usize> {
|
|
if with_literals {
|
|
let capacity_result = sequence_api_validate_literal_capacity(lit_size, lit_capacity);
|
|
if ERR_isError(capacity_result) {
|
|
return Err(capacity_result);
|
|
}
|
|
}
|
|
|
|
if state.callback_context.is_null()
|
|
|| state.sequence_state.is_null()
|
|
|| state.sequence_literals_state.is_null()
|
|
|| state.init as usize == 0
|
|
|| state.checksum_state.is_null()
|
|
{
|
|
return Err(ERROR(ZstdErrorCode::Generic));
|
|
}
|
|
|
|
let init_result = unsafe {
|
|
(state.init)(
|
|
state.callback_context,
|
|
pledged_src_size,
|
|
state as *mut ZSTD_rust_sequenceApiState,
|
|
)
|
|
};
|
|
if ERR_isError(init_result) {
|
|
return Err(init_result);
|
|
}
|
|
|
|
sequence_api_plan(
|
|
with_literals,
|
|
state.block_delimiters,
|
|
state.validate_sequences,
|
|
state.checksum_flag,
|
|
)
|
|
}
|
|
|
|
unsafe fn sequence_api_write_frame_header(
|
|
state: &ZSTD_rust_sequenceApiState,
|
|
op: &mut *mut u8,
|
|
dst_capacity: &mut usize,
|
|
c_size: &mut usize,
|
|
pledged_src_size: usize,
|
|
) -> usize {
|
|
let frame_header_size = unsafe {
|
|
ZSTD_rust_writeFrameHeader(
|
|
(*op).cast(),
|
|
*dst_capacity,
|
|
state.no_dict_id_flag,
|
|
state.checksum_flag,
|
|
state.content_size_flag,
|
|
state.format,
|
|
state.window_log,
|
|
pledged_src_size as u64,
|
|
state.dict_id,
|
|
)
|
|
};
|
|
if ERR_isError(frame_header_size) {
|
|
return frame_header_size;
|
|
}
|
|
if frame_header_size > *dst_capacity {
|
|
return ERROR(ZstdErrorCode::DstSizeTooSmall);
|
|
}
|
|
|
|
unsafe {
|
|
*op = (*op).add(frame_header_size);
|
|
}
|
|
*dst_capacity -= frame_header_size;
|
|
*c_size = c_size.wrapping_add(frame_header_size);
|
|
0
|
|
}
|
|
|
|
unsafe fn sequence_api_append_frame_checksum(
|
|
state: &ZSTD_rust_sequenceApiState,
|
|
plan: SequenceApiPlan,
|
|
op: *mut u8,
|
|
dst_capacity: usize,
|
|
c_size: &mut usize,
|
|
) -> usize {
|
|
if !plan.append_frame_checksum {
|
|
return 0;
|
|
}
|
|
|
|
/* Keep the original ordering: digest the checksum before checking the
|
|
* remaining destination capacity. */
|
|
let checksum = unsafe { XXH64_digest(state.checksum_state) as c_uint };
|
|
if dst_capacity < 4 {
|
|
return ERROR(ZstdErrorCode::DstSizeTooSmall);
|
|
}
|
|
unsafe { MEM_writeLE32(op.cast(), checksum) };
|
|
*c_size = c_size.wrapping_add(4);
|
|
0
|
|
}
|
|
|
|
/// Rust-owned orchestration for `ZSTD_compressSequences`.
|
|
///
|
|
/// The C wrapper provides the post-initialization block-state projection and
|
|
/// callback for private CCtx initialization; frame-header fields and the
|
|
/// checksum state are projected directly. Rust preserves the public ordering:
|
|
/// initialize, write the frame header, update the input checksum, emit blocks,
|
|
/// then append the frame checksum.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_compressSequences(
|
|
state: *mut ZSTD_rust_sequenceApiState,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
in_seqs: *const ZSTD_Sequence,
|
|
in_seqs_size: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &mut *state };
|
|
let plan = match unsafe { sequence_api_prepare(state, src_size, false, 0, 0) } {
|
|
Ok(plan) => plan,
|
|
Err(result) => return result,
|
|
};
|
|
|
|
let mut op = dst.cast::<u8>();
|
|
let mut dst_capacity = dst_capacity;
|
|
let mut c_size = 0usize;
|
|
let header_result = unsafe {
|
|
sequence_api_write_frame_header(state, &mut op, &mut dst_capacity, &mut c_size, src_size)
|
|
};
|
|
if ERR_isError(header_result) {
|
|
return header_result;
|
|
}
|
|
|
|
if plan.update_input_checksum && src_size != 0 {
|
|
unsafe {
|
|
let _ = XXH64_update(state.checksum_state, src, src_size);
|
|
}
|
|
}
|
|
|
|
let block_size = unsafe {
|
|
ZSTD_rust_compressSequencesInternal(
|
|
&*state.sequence_state,
|
|
op.cast(),
|
|
dst_capacity,
|
|
in_seqs,
|
|
in_seqs_size,
|
|
src,
|
|
src_size,
|
|
)
|
|
};
|
|
if ERR_isError(block_size) {
|
|
return block_size;
|
|
}
|
|
if block_size > dst_capacity {
|
|
return ERROR(ZstdErrorCode::DstSizeTooSmall);
|
|
}
|
|
unsafe {
|
|
op = op.add(block_size);
|
|
}
|
|
dst_capacity -= block_size;
|
|
c_size = c_size.wrapping_add(block_size);
|
|
|
|
let checksum_result =
|
|
unsafe { sequence_api_append_frame_checksum(state, plan, op, dst_capacity, &mut c_size) };
|
|
if ERR_isError(checksum_result) {
|
|
return checksum_result;
|
|
}
|
|
|
|
c_size
|
|
}
|
|
|
|
/// Rust-owned orchestration for `ZSTD_compressSequencesAndLiterals`.
|
|
///
|
|
/// The literal-capacity check intentionally precedes CCtx initialization, and
|
|
/// the post-initialization incompatibility checks retain their original
|
|
/// precedence. The existing Rust block loop remains responsible for literal
|
|
/// accounting, sequence conversion, and block codec operations.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_compressSequencesAndLiterals(
|
|
state: *mut ZSTD_rust_sequenceApiState,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
in_seqs: *const ZSTD_Sequence,
|
|
in_seqs_size: usize,
|
|
literals: *const c_void,
|
|
lit_size: usize,
|
|
lit_capacity: usize,
|
|
decompressed_size: usize,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &mut *state };
|
|
let plan = match unsafe {
|
|
sequence_api_prepare(state, decompressed_size, true, lit_size, lit_capacity)
|
|
} {
|
|
Ok(plan) => plan,
|
|
Err(result) => return result,
|
|
};
|
|
|
|
let mut op = dst.cast::<u8>();
|
|
let mut dst_capacity = dst_capacity;
|
|
let mut c_size = 0usize;
|
|
let header_result = unsafe {
|
|
sequence_api_write_frame_header(
|
|
state,
|
|
&mut op,
|
|
&mut dst_capacity,
|
|
&mut c_size,
|
|
decompressed_size,
|
|
)
|
|
};
|
|
if ERR_isError(header_result) {
|
|
return header_result;
|
|
}
|
|
|
|
let block_size = unsafe {
|
|
ZSTD_rust_compressSequencesAndLiteralsInternal(
|
|
&*state.sequence_literals_state,
|
|
op.cast(),
|
|
dst_capacity,
|
|
in_seqs,
|
|
in_seqs_size,
|
|
literals,
|
|
lit_size,
|
|
decompressed_size,
|
|
)
|
|
};
|
|
if ERR_isError(block_size) {
|
|
return block_size;
|
|
}
|
|
if block_size > dst_capacity {
|
|
return ERROR(ZstdErrorCode::DstSizeTooSmall);
|
|
}
|
|
dst_capacity -= block_size;
|
|
c_size = c_size.wrapping_add(block_size);
|
|
|
|
let checksum_result =
|
|
unsafe { sequence_api_append_frame_checksum(state, plan, op, dst_capacity, &mut c_size) };
|
|
if ERR_isError(checksum_result) {
|
|
return checksum_result;
|
|
}
|
|
|
|
c_size
|
|
}
|
|
|
|
unsafe fn compress_frame(
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
compression_level: c_int,
|
|
) -> usize {
|
|
if dst.is_null() {
|
|
return ERROR(if dst_capacity == 0 {
|
|
ZstdErrorCode::DstSizeTooSmall
|
|
} else {
|
|
ZstdErrorCode::DstBufferNull
|
|
});
|
|
}
|
|
if src_size != 0 && src.is_null() {
|
|
return ERROR(ZstdErrorCode::SrcSizeWrong);
|
|
}
|
|
if src_size as u64 == ZSTD_CONTENTSIZE_UNKNOWN {
|
|
return ERROR(ZstdErrorCode::SrcSizeWrong);
|
|
}
|
|
|
|
let mut cparams = ZSTD_rust_params_selectCParams(
|
|
compression_level,
|
|
src_size as u64,
|
|
0,
|
|
ZSTD_RUST_CPM_NO_ATTACH_DICT,
|
|
);
|
|
cparams = ZSTD_rust_params_adjustCParams(
|
|
cparams,
|
|
src_size as u64,
|
|
0,
|
|
ZSTD_RUST_CPM_NO_ATTACH_DICT,
|
|
ZSTD_RUST_PS_DISABLE,
|
|
);
|
|
|
|
let header_size = unsafe {
|
|
ZSTD_rust_writeFrameHeader(
|
|
dst,
|
|
dst_capacity,
|
|
0, /* noDictIDFlag */
|
|
0, /* checksumFlag */
|
|
1, /* contentSizeFlag */
|
|
0, /* zstd frame */
|
|
cparams.windowLog,
|
|
src_size as u64,
|
|
0,
|
|
)
|
|
};
|
|
if ERR_isError(header_size) {
|
|
return header_size;
|
|
}
|
|
|
|
if src_size == 0 {
|
|
let empty_block = unsafe {
|
|
ZSTD_writeLastEmptyBlock(
|
|
dst.cast::<u8>().add(header_size).cast(),
|
|
dst_capacity - header_size,
|
|
)
|
|
};
|
|
return if ERR_isError(empty_block) {
|
|
empty_block
|
|
} else {
|
|
header_size + empty_block
|
|
};
|
|
}
|
|
|
|
let first_block_size = src_size.min(ZSTD_BLOCKSIZE_MAX);
|
|
let matcher_hash_log = cparams.hashLog;
|
|
let matcher_chain_log = cparams.chainLog.min(ceil_log2(first_block_size).max(6));
|
|
let hash_size = match checked_table_size(matcher_hash_log) {
|
|
Some(size) => size,
|
|
None => return ERROR(ZstdErrorCode::MemoryAllocation),
|
|
};
|
|
let chain_size = match checked_table_size(matcher_chain_log) {
|
|
Some(size) => size,
|
|
None => return ERROR(ZstdErrorCode::MemoryAllocation),
|
|
};
|
|
|
|
let max_nb_seq =
|
|
ZSTD_rust_params_maxNbSeq(ZSTD_BLOCKSIZE_MAX, cparams.minMatch, 0).saturating_add(1);
|
|
let disable_literal_compression =
|
|
c_int::from(cparams.strategy == ZSTD_FAST && cparams.targetLength > 0);
|
|
let mut sequences = vec![SeqDef::default(); max_nb_seq];
|
|
let mut literals = vec![0u8; ZSTD_BLOCKSIZE_MAX];
|
|
let mut ll_codes = vec![0u8; max_nb_seq];
|
|
let mut ml_codes = vec![0u8; max_nb_seq];
|
|
let mut of_codes = vec![0u8; max_nb_seq];
|
|
let mut hash_table = vec![0u32; hash_size];
|
|
let mut chain_table = vec![0u32; chain_size];
|
|
let mut workspace = vec![0u64; TMP_WORKSPACE_SIZE / size_of::<u64>()];
|
|
let mut prev_block = zeroed_state();
|
|
let mut next_block = zeroed_state();
|
|
prev_block.rep = [1, 4, 8];
|
|
next_block.rep = prev_block.rep;
|
|
|
|
let seq_store = &mut SeqStore_t {
|
|
sequencesStart: sequences.as_mut_ptr(),
|
|
sequences: sequences.as_mut_ptr(),
|
|
litStart: literals.as_mut_ptr(),
|
|
lit: literals.as_mut_ptr(),
|
|
llCode: ll_codes.as_mut_ptr(),
|
|
mlCode: ml_codes.as_mut_ptr(),
|
|
ofCode: of_codes.as_mut_ptr(),
|
|
maxNbSeq: max_nb_seq,
|
|
maxNbLit: ZSTD_BLOCKSIZE_MAX,
|
|
longLengthType: 0,
|
|
longLengthPos: 0,
|
|
};
|
|
|
|
let source = src.cast::<u8>();
|
|
let output = dst.cast::<u8>();
|
|
let matcher_base = source.wrapping_sub(ZSTD_WINDOW_START_INDEX as usize);
|
|
let mut input_offset = 0usize;
|
|
let mut output_offset = header_size;
|
|
while input_offset < src_size {
|
|
let block_size = (src_size - input_offset).min(ZSTD_BLOCKSIZE_MAX);
|
|
let block_src = unsafe { source.add(input_offset) };
|
|
let block_end = unsafe { block_src.add(block_size) };
|
|
let last_block = u32::from(input_offset + block_size == src_size);
|
|
|
|
if block_size < MIN_COMPRESSIBLE_BLOCK_SIZE {
|
|
let written = unsafe {
|
|
write_raw_block(
|
|
output.add(output_offset),
|
|
dst_capacity.saturating_sub(output_offset),
|
|
block_src,
|
|
block_size,
|
|
last_block,
|
|
)
|
|
};
|
|
if ERR_isError(written) {
|
|
return written;
|
|
}
|
|
output_offset += written;
|
|
input_offset += block_size;
|
|
continue;
|
|
}
|
|
|
|
seq_store.sequences = seq_store.sequencesStart;
|
|
seq_store.lit = seq_store.litStart;
|
|
seq_store.longLengthType = 0;
|
|
seq_store.longLengthPos = 0;
|
|
next_block.rep = prev_block.rep;
|
|
|
|
let mut reps = prev_block.rep;
|
|
let last_literals = if block_size < 8 {
|
|
block_size
|
|
} else if cparams.strategy == ZSTD_DFAST {
|
|
unsafe {
|
|
crate::zstd_double_fast::ZSTD_rust_compressBlock_doubleFast(
|
|
hash_table.as_mut_ptr(),
|
|
chain_table.as_mut_ptr(),
|
|
matcher_base,
|
|
ZSTD_WINDOW_START_INDEX,
|
|
0,
|
|
matcher_hash_log,
|
|
matcher_chain_log,
|
|
cparams.minMatch,
|
|
cparams.windowLog,
|
|
(seq_store as *mut SeqStore_t).cast(),
|
|
reps.as_mut_ptr(),
|
|
block_src.cast(),
|
|
block_size,
|
|
)
|
|
}
|
|
} else {
|
|
unsafe {
|
|
crate::zstd_fast::ZSTD_rust_compressBlock_fast(
|
|
hash_table.as_mut_ptr(),
|
|
matcher_base,
|
|
ZSTD_WINDOW_START_INDEX,
|
|
0,
|
|
matcher_hash_log,
|
|
cparams.minMatch,
|
|
cparams.targetLength,
|
|
cparams.windowLog,
|
|
(seq_store as *mut SeqStore_t).cast(),
|
|
reps.as_mut_ptr(),
|
|
block_src.cast(),
|
|
block_size,
|
|
)
|
|
}
|
|
};
|
|
|
|
if last_literals > block_size {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let last_literal_src = unsafe { block_end.sub(last_literals) };
|
|
if last_literals != 0 {
|
|
unsafe {
|
|
ptr::copy_nonoverlapping(last_literal_src, seq_store.lit, last_literals);
|
|
seq_store.lit = seq_store.lit.add(last_literals);
|
|
}
|
|
}
|
|
let remaining_capacity = dst_capacity.saturating_sub(output_offset);
|
|
if remaining_capacity < ZSTD_BLOCK_HEADER_SIZE {
|
|
return ERROR(ZstdErrorCode::DstSizeTooSmall);
|
|
}
|
|
let mut compressed_size = unsafe {
|
|
ZSTD_rust_entropyCompressSeqStore(
|
|
(seq_store as *const SeqStore_t).cast(),
|
|
ptr::addr_of!(prev_block.entropy),
|
|
ptr::addr_of_mut!(next_block.entropy),
|
|
cparams.strategy,
|
|
disable_literal_compression,
|
|
output.add(output_offset + ZSTD_BLOCK_HEADER_SIZE).cast(),
|
|
remaining_capacity - ZSTD_BLOCK_HEADER_SIZE,
|
|
block_size,
|
|
workspace.as_mut_ptr().cast(),
|
|
TMP_WORKSPACE_SIZE,
|
|
0, /* BMI2 is optional; portable Rust leaf path */
|
|
)
|
|
};
|
|
if ERR_isError(compressed_size) {
|
|
return compressed_size;
|
|
}
|
|
if input_offset != 0
|
|
&& compressed_size < 25
|
|
&& unsafe { ZSTD_rust_isRLE(block_src, block_size) } != 0
|
|
{
|
|
unsafe {
|
|
*output.add(output_offset + ZSTD_BLOCK_HEADER_SIZE) = *block_src;
|
|
}
|
|
compressed_size = 1;
|
|
}
|
|
|
|
let written = if compressed_size == 0 {
|
|
unsafe {
|
|
write_raw_block(
|
|
output.add(output_offset),
|
|
remaining_capacity,
|
|
block_src,
|
|
block_size,
|
|
last_block,
|
|
)
|
|
}
|
|
} else {
|
|
unsafe {
|
|
ZSTD_rust_writeBlockHeader(
|
|
output.add(output_offset).cast(),
|
|
compressed_size,
|
|
block_size,
|
|
last_block,
|
|
)
|
|
};
|
|
if compressed_size > 1 {
|
|
next_block.rep = reps;
|
|
}
|
|
compressed_size + ZSTD_BLOCK_HEADER_SIZE
|
|
};
|
|
if ERR_isError(written) {
|
|
return written;
|
|
}
|
|
output_offset += written;
|
|
input_offset += block_size;
|
|
if compressed_size > 1 {
|
|
std::mem::swap(&mut prev_block, &mut next_block);
|
|
}
|
|
}
|
|
|
|
output_offset
|
|
}
|
|
|
|
/// Select the complete-input streaming fast path before invoking the C-owned
|
|
/// simple-level predicate. Rust owns the stream-state branch; C keeps the
|
|
/// private context and level-selection leaf behind a callback.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_simpleCompressStream2Policy(
|
|
projection: *const ZSTD_rust_simpleCompressStream2Projection,
|
|
cctx: *const c_void,
|
|
src_size: usize,
|
|
simple_compress2_level: Option<ZSTD_rust_simpleCompress2LevelFn>,
|
|
) -> c_int {
|
|
let Some(projection) = (unsafe { projection.as_ref() }) else {
|
|
return c_int::MIN;
|
|
};
|
|
if cctx.is_null()
|
|
|| projection.stream_stage != ZSTD_CSTREAM_STAGE_INIT
|
|
|| projection.pledged_src_size_plus_one != 0
|
|
|| projection.rust_simple_compress2_completed != 0
|
|
{
|
|
return c_int::MIN;
|
|
}
|
|
let Some(simple_compress2_level) = simple_compress2_level else {
|
|
return c_int::MIN;
|
|
};
|
|
unsafe { simple_compress2_level(cctx, src_size) }
|
|
}
|
|
|
|
/// Simple one-shot compression entry point.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_compress(
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
compression_level: c_int,
|
|
) -> usize {
|
|
#[cfg(not(test))]
|
|
{
|
|
let strategy = ZSTD_rust_compressCCtxStrategy(src_size, compression_level);
|
|
if strategy != ZSTD_FAST && strategy != ZSTD_DFAST {
|
|
let cctx = unsafe { ZSTD_createCCtx() };
|
|
if cctx.is_null() {
|
|
return ERROR(ZstdErrorCode::MemoryAllocation);
|
|
}
|
|
let result = unsafe {
|
|
ZSTD_compress_usingDict(
|
|
cctx,
|
|
dst,
|
|
dst_capacity,
|
|
src,
|
|
src_size,
|
|
ptr::null(),
|
|
0,
|
|
compression_level,
|
|
)
|
|
};
|
|
unsafe { ZSTD_freeCCtx(cctx) };
|
|
return result;
|
|
}
|
|
}
|
|
unsafe { compress_frame(dst, dst_capacity, src, src_size, compression_level) }
|
|
}
|
|
|
|
/// Simple explicit-context compression entry point.
|
|
///
|
|
/// The public contract deliberately ignores all advanced context parameters.
|
|
/// C performs the context reset because the private `ZSTD_CCtx_s` layout is
|
|
/// still configuration-dependent. Strategies not yet implemented by the
|
|
/// Rust frame compressor use the original C simple API before that reset.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_compressCCtx(
|
|
cctx: *mut c_void,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
compression_level: c_int,
|
|
) -> usize {
|
|
if cctx.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
#[cfg(not(test))]
|
|
{
|
|
let strategy = ZSTD_rust_compressCCtxStrategy(src_size, compression_level);
|
|
if strategy != ZSTD_FAST && strategy != ZSTD_DFAST {
|
|
return unsafe {
|
|
ZSTD_compress_usingDict(
|
|
cctx,
|
|
dst,
|
|
dst_capacity,
|
|
src,
|
|
src_size,
|
|
ptr::null(),
|
|
0,
|
|
compression_level,
|
|
)
|
|
};
|
|
}
|
|
let reset = unsafe { ZSTD_rust_resetCCtxForSimpleCompression(cctx) };
|
|
if ERR_isError(reset) {
|
|
return reset;
|
|
}
|
|
let prepare =
|
|
unsafe { ZSTD_rust_prepareCCtxForSimpleCompression(cctx, src_size, compression_level) };
|
|
if ERR_isError(prepare) {
|
|
return prepare;
|
|
}
|
|
}
|
|
unsafe { compress_frame(dst, dst_capacity, src, src_size, compression_level) }
|
|
}
|
|
|
|
/// Stateful compression entry point during the context migration.
|
|
///
|
|
/// A context with only the ordinary frame settings is reset by the C shim and
|
|
/// compressed through the Rust frame path. Contexts using dictionaries,
|
|
/// checksums, target-sized blocks, sequence collection, or other advanced
|
|
/// state still use the renamed C implementation until their state is moved.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_compress2(
|
|
cctx: *mut c_void,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
) -> usize {
|
|
if cctx.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
#[cfg(test)]
|
|
{
|
|
unsafe { compress_frame(dst, dst_capacity, src, src_size, 3) }
|
|
}
|
|
|
|
#[cfg(not(test))]
|
|
{
|
|
let level = unsafe { ZSTD_rust_simpleCompress2Level(cctx.cast_const(), src_size) };
|
|
if level != c_int::MIN {
|
|
let reset = unsafe { ZSTD_rust_resetCCtxForSimpleCompressionSession(cctx) };
|
|
if ERR_isError(reset) {
|
|
return reset;
|
|
}
|
|
let prepare =
|
|
unsafe { ZSTD_rust_prepareCCtxForSimpleCompression(cctx, src_size, level) };
|
|
if ERR_isError(prepare) {
|
|
return prepare;
|
|
}
|
|
let result = unsafe { compress_frame(dst, dst_capacity, src, src_size, level) };
|
|
if !ERR_isError(result) {
|
|
unsafe { ZSTD_rust_markSimpleCompression2Complete(cctx) };
|
|
}
|
|
return result;
|
|
}
|
|
unsafe { ZSTD_compress2_c(cctx, dst, dst_capacity, src, src_size) }
|
|
}
|
|
}
|
|
|
|
/// Compress a streaming call when the complete input and a full output bound
|
|
/// are already available for the ordinary context configuration.
|
|
///
|
|
/// The C implementation remains the fallback for partial-output streaming,
|
|
/// `ZSTD_e_continue`/`ZSTD_e_flush`, dictionaries, and every advanced context
|
|
/// configuration. The Rust path resets the C-owned session after emitting a
|
|
/// complete frame so the same context can immediately start another frame.
|
|
#[cfg(not(test))]
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_compressStream2(
|
|
cctx: *mut c_void,
|
|
output: *mut ZSTD_outBuffer,
|
|
input: *mut ZSTD_inBuffer,
|
|
end_op: c_int,
|
|
) -> usize {
|
|
if cctx.is_null() || output.is_null() || input.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
let output_ref = unsafe { &mut *output };
|
|
let input_ref = unsafe { &mut *input };
|
|
if output_ref.pos > output_ref.size {
|
|
return ERROR(ZstdErrorCode::DstSizeTooSmall);
|
|
}
|
|
if input_ref.pos > input_ref.size {
|
|
return ERROR(ZstdErrorCode::SrcSizeWrong);
|
|
}
|
|
|
|
let src_size = input_ref.size - input_ref.pos;
|
|
if end_op == ZSTD_E_END {
|
|
let level = unsafe { ZSTD_rust_simpleCompressStream2Level(cctx.cast_const(), src_size) };
|
|
if level != c_int::MIN {
|
|
let dst_capacity = output_ref.size - output_ref.pos;
|
|
let bound = ZSTD_compressBound(src_size);
|
|
if !ERR_isError(bound) && dst_capacity >= bound {
|
|
if dst_capacity != 0 && output_ref.dst.is_null() {
|
|
return ERROR(ZstdErrorCode::DstBufferNull);
|
|
}
|
|
if src_size != 0 && input_ref.src.is_null() {
|
|
return ERROR(ZstdErrorCode::SrcSizeWrong);
|
|
}
|
|
let dst = if output_ref.dst.is_null() {
|
|
ptr::null_mut()
|
|
} else {
|
|
unsafe { output_ref.dst.cast::<u8>().add(output_ref.pos).cast() }
|
|
};
|
|
let src = if input_ref.src.is_null() {
|
|
ptr::null()
|
|
} else {
|
|
unsafe { input_ref.src.cast::<u8>().add(input_ref.pos).cast() }
|
|
};
|
|
let result = unsafe { compress_frame(dst, dst_capacity, src, src_size, level) };
|
|
if !ERR_isError(result) {
|
|
output_ref.pos += result;
|
|
input_ref.pos = input_ref.size;
|
|
return unsafe { ZSTD_rust_resetCCtxForSimpleCompressionSession(cctx) };
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe { ZSTD_compressStream2_c(cctx, output, input, end_op) }
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::errors::ERR_getErrorCode;
|
|
use crate::zstd_compress_params_api::{
|
|
ZSTD_CCtxParams_getParameter, ZSTD_CCtxParams_setParameter,
|
|
};
|
|
use std::io::Write;
|
|
use std::process::{Command, Stdio};
|
|
|
|
const ZSTD_GREEDY: c_int = 3;
|
|
const ZSTD_LAZY: c_int = 4;
|
|
const ZSTD_BTULTRA: c_int = 8;
|
|
const ZSTD_BTOPT: c_int = 7;
|
|
const ZSTD_BTULTRA2: c_int = 9;
|
|
|
|
struct EstimateCCtxSizeTestContext {
|
|
compression_levels: Vec<c_int>,
|
|
source_size_hints: Vec<u64>,
|
|
parameter_tiers: Vec<u32>,
|
|
}
|
|
|
|
unsafe extern "C" fn estimate_cctx_size_test_get_c_params(
|
|
context: *mut c_void,
|
|
compression_level: c_int,
|
|
source_size_hint: u64,
|
|
) -> ZSTD_compressionParameters {
|
|
let context = unsafe { &mut *(context.cast::<EstimateCCtxSizeTestContext>()) };
|
|
context.compression_levels.push(compression_level);
|
|
context.source_size_hints.push(source_size_hint);
|
|
ZSTD_compressionParameters {
|
|
windowLog: context.source_size_hints.len() as u32,
|
|
..ZSTD_compressionParameters::default()
|
|
}
|
|
}
|
|
|
|
unsafe extern "C" fn estimate_cctx_size_test_estimate(
|
|
context: *mut c_void,
|
|
c_params: ZSTD_compressionParameters,
|
|
) -> usize {
|
|
let context = unsafe { &mut *(context.cast::<EstimateCCtxSizeTestContext>()) };
|
|
context.parameter_tiers.push(c_params.windowLog);
|
|
match c_params.windowLog {
|
|
1 => 17,
|
|
2 => 91,
|
|
3 => 23,
|
|
4 => 67,
|
|
_ => ERROR(ZstdErrorCode::Generic),
|
|
}
|
|
}
|
|
|
|
struct GenerateSequencesTestContext {
|
|
events: Vec<&'static str>,
|
|
target_c_block_size: c_int,
|
|
nb_workers: c_int,
|
|
allocation_fails: bool,
|
|
collector_result: usize,
|
|
compress_result: usize,
|
|
sequence_count: usize,
|
|
}
|
|
|
|
unsafe extern "C" fn generate_sequences_get_parameter_test(
|
|
context: *mut c_void,
|
|
parameter: c_int,
|
|
value: *mut c_int,
|
|
) -> usize {
|
|
let context = unsafe { &mut *context.cast::<GenerateSequencesTestContext>() };
|
|
let (event, result) = match parameter {
|
|
ZSTD_C_TARGET_C_BLOCK_SIZE => ("target", context.target_c_block_size),
|
|
ZSTD_C_NB_WORKERS => ("workers", context.nb_workers),
|
|
_ => ("other", 0),
|
|
};
|
|
context.events.push(event);
|
|
unsafe { *value = result };
|
|
0
|
|
}
|
|
|
|
unsafe extern "C" fn generate_sequences_allocate_test(
|
|
context: *mut c_void,
|
|
_size: usize,
|
|
) -> *mut c_void {
|
|
let context = unsafe { &mut *context.cast::<GenerateSequencesTestContext>() };
|
|
context.events.push("allocate");
|
|
if context.allocation_fails {
|
|
ptr::null_mut()
|
|
} else {
|
|
Box::into_raw(Box::new(0_u8)).cast()
|
|
}
|
|
}
|
|
|
|
unsafe extern "C" fn generate_sequences_free_test(context: *mut c_void, pointer: *mut c_void) {
|
|
let context = unsafe { &mut *context.cast::<GenerateSequencesTestContext>() };
|
|
context.events.push("free");
|
|
if !pointer.is_null() {
|
|
unsafe { drop(Box::from_raw(pointer.cast::<u8>())) };
|
|
}
|
|
}
|
|
|
|
unsafe extern "C" fn generate_sequences_set_collector_test(
|
|
context: *mut c_void,
|
|
_out_seqs: *mut ZSTD_Sequence,
|
|
_out_seqs_size: usize,
|
|
) -> usize {
|
|
let context = unsafe { &mut *context.cast::<GenerateSequencesTestContext>() };
|
|
context.events.push("collector");
|
|
context.collector_result
|
|
}
|
|
|
|
unsafe extern "C" fn generate_sequences_compress2_test(
|
|
context: *mut c_void,
|
|
_dst: *mut c_void,
|
|
_dst_capacity: usize,
|
|
_src: *const c_void,
|
|
_src_size: usize,
|
|
) -> usize {
|
|
let context = unsafe { &mut *context.cast::<GenerateSequencesTestContext>() };
|
|
context.events.push("compress2");
|
|
context.compress_result
|
|
}
|
|
|
|
unsafe extern "C" fn generate_sequences_get_count_test(context: *mut c_void) -> usize {
|
|
let context = unsafe { &mut *context.cast::<GenerateSequencesTestContext>() };
|
|
context.events.push("count");
|
|
context.sequence_count
|
|
}
|
|
|
|
fn generate_sequences_test_state(
|
|
context: &mut GenerateSequencesTestContext,
|
|
) -> ZSTD_rust_generateSequencesState {
|
|
ZSTD_rust_generateSequencesState {
|
|
callback_context: context as *mut GenerateSequencesTestContext as *mut c_void,
|
|
get_parameter: generate_sequences_get_parameter_test,
|
|
allocate: generate_sequences_allocate_test,
|
|
free: generate_sequences_free_test,
|
|
set_collector: generate_sequences_set_collector_test,
|
|
compress2: generate_sequences_compress2_test,
|
|
get_count: generate_sequences_get_count_test,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn generate_sequences_rejects_unsupported_parameters_before_allocation() {
|
|
let mut context = GenerateSequencesTestContext {
|
|
events: Vec::new(),
|
|
target_c_block_size: 1,
|
|
nb_workers: 0,
|
|
allocation_fails: false,
|
|
collector_result: 0,
|
|
compress_result: 0,
|
|
sequence_count: 0,
|
|
};
|
|
let state = generate_sequences_test_state(&mut context);
|
|
|
|
let result =
|
|
unsafe { ZSTD_rust_generateSequences(&state, ptr::null_mut(), 0, ptr::null(), 0) };
|
|
|
|
assert_eq!(
|
|
ERR_getErrorCode(result),
|
|
ZstdErrorCode::ParameterUnsupported as i32
|
|
);
|
|
assert_eq!(context.events, ["target"]);
|
|
}
|
|
|
|
#[test]
|
|
fn generate_sequences_frees_temporary_output_on_setup_failure() {
|
|
let mut context = GenerateSequencesTestContext {
|
|
events: Vec::new(),
|
|
target_c_block_size: 0,
|
|
nb_workers: 0,
|
|
allocation_fails: false,
|
|
collector_result: ERROR(ZstdErrorCode::Generic),
|
|
compress_result: 0,
|
|
sequence_count: 0,
|
|
};
|
|
let state = generate_sequences_test_state(&mut context);
|
|
|
|
let result =
|
|
unsafe { ZSTD_rust_generateSequences(&state, ptr::null_mut(), 0, ptr::null(), 0) };
|
|
|
|
assert_eq!(ERR_getErrorCode(result), ZstdErrorCode::Generic as i32);
|
|
assert_eq!(
|
|
context.events,
|
|
["target", "workers", "allocate", "collector", "free"]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn generate_sequences_runs_callbacks_in_order_and_returns_count() {
|
|
let mut context = GenerateSequencesTestContext {
|
|
events: Vec::new(),
|
|
target_c_block_size: 0,
|
|
nb_workers: 0,
|
|
allocation_fails: false,
|
|
collector_result: 0,
|
|
compress_result: 0,
|
|
sequence_count: 2,
|
|
};
|
|
let state = generate_sequences_test_state(&mut context);
|
|
|
|
let result =
|
|
unsafe { ZSTD_rust_generateSequences(&state, ptr::null_mut(), 0, ptr::null(), 0) };
|
|
|
|
assert_eq!(result, 2);
|
|
assert_eq!(
|
|
context.events,
|
|
[
|
|
"target",
|
|
"workers",
|
|
"allocate",
|
|
"collector",
|
|
"compress2",
|
|
"free",
|
|
"count"
|
|
]
|
|
);
|
|
}
|
|
|
|
unsafe extern "C" fn select_block_compressor_test_default(
|
|
_match_state: *mut c_void,
|
|
_seq_store: *mut SeqStore_t,
|
|
_rep: *mut u32,
|
|
_src: *const c_void,
|
|
_src_size: usize,
|
|
) -> usize {
|
|
7
|
|
}
|
|
|
|
unsafe extern "C" fn select_block_compressor_test_ordinary(
|
|
_match_state: *mut c_void,
|
|
_seq_store: *mut SeqStore_t,
|
|
_rep: *mut u32,
|
|
_src: *const c_void,
|
|
_src_size: usize,
|
|
) -> usize {
|
|
41
|
|
}
|
|
|
|
unsafe extern "C" fn select_block_compressor_test_row(
|
|
_match_state: *mut c_void,
|
|
_seq_store: *mut SeqStore_t,
|
|
_rep: *mut u32,
|
|
_src: *const c_void,
|
|
_src_size: usize,
|
|
) -> usize {
|
|
43
|
|
}
|
|
|
|
fn select_block_compressor_test_state(
|
|
block_compressors: &[Option<BlockCompressorFn>],
|
|
row_block_compressors: &[Option<BlockCompressorFn>],
|
|
selected_compressor: &mut Option<BlockCompressorFn>,
|
|
) -> ZSTD_rust_selectBlockCompressorState {
|
|
ZSTD_rust_selectBlockCompressorState {
|
|
block_compressors: block_compressors.as_ptr(),
|
|
row_block_compressors: row_block_compressors.as_ptr(),
|
|
selected_compressor,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn select_block_compressor_dispatches_ordinary_and_row_tables() {
|
|
let default = Some(select_block_compressor_test_default as BlockCompressorFn);
|
|
let mut block_compressors = [default; 4 * ZSTD_RUST_BLOCK_COMPRESSOR_TABLE_WIDTH];
|
|
let mut row_block_compressors = [default; 4 * ZSTD_RUST_ROW_BLOCK_COMPRESSOR_TABLE_WIDTH];
|
|
block_compressors[ZSTD_RUST_BLOCK_COMPRESSOR_TABLE_WIDTH + 2] =
|
|
Some(select_block_compressor_test_ordinary as BlockCompressorFn);
|
|
row_block_compressors[3 * ZSTD_RUST_ROW_BLOCK_COMPRESSOR_TABLE_WIDTH] =
|
|
Some(select_block_compressor_test_row as BlockCompressorFn);
|
|
let mut selected_compressor = None;
|
|
let state = select_block_compressor_test_state(
|
|
&block_compressors,
|
|
&row_block_compressors,
|
|
&mut selected_compressor,
|
|
);
|
|
|
|
assert_eq!(
|
|
unsafe { ZSTD_rust_selectBlockCompressor(&state, ZSTD_DFAST, ZSTD_RUST_PS_DISABLE, 1) },
|
|
0
|
|
);
|
|
let ordinary = selected_compressor.expect("ordinary compressor should be selected");
|
|
assert_eq!(
|
|
unsafe {
|
|
ordinary(
|
|
ptr::null_mut(),
|
|
ptr::null_mut(),
|
|
ptr::null_mut(),
|
|
ptr::null(),
|
|
0,
|
|
)
|
|
},
|
|
41
|
|
);
|
|
|
|
assert_eq!(
|
|
unsafe { ZSTD_rust_selectBlockCompressor(&state, ZSTD_GREEDY, ZSTD_RUST_PS_ENABLE, 3) },
|
|
0
|
|
);
|
|
let row = selected_compressor.expect("row compressor should be selected");
|
|
assert_eq!(
|
|
unsafe {
|
|
row(
|
|
ptr::null_mut(),
|
|
ptr::null_mut(),
|
|
ptr::null_mut(),
|
|
ptr::null(),
|
|
0,
|
|
)
|
|
},
|
|
43
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn select_block_compressor_rejects_an_excluded_dispatch_leaf() {
|
|
let default = Some(select_block_compressor_test_default as BlockCompressorFn);
|
|
let mut block_compressors = [default; 4 * ZSTD_RUST_BLOCK_COMPRESSOR_TABLE_WIDTH];
|
|
let row_block_compressors = [default; 4 * ZSTD_RUST_ROW_BLOCK_COMPRESSOR_TABLE_WIDTH];
|
|
block_compressors[1] = None;
|
|
let mut selected_compressor = None;
|
|
let state = select_block_compressor_test_state(
|
|
&block_compressors,
|
|
&row_block_compressors,
|
|
&mut selected_compressor,
|
|
);
|
|
|
|
let result =
|
|
unsafe { ZSTD_rust_selectBlockCompressor(&state, ZSTD_FAST, ZSTD_RUST_PS_DISABLE, 0) };
|
|
|
|
assert!(ERR_isError(result));
|
|
assert!(selected_compressor.is_none());
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct BuildSeqStoreSelectProbe {
|
|
events: Vec<&'static str>,
|
|
external_result: usize,
|
|
ldm_result: usize,
|
|
producer_result: usize,
|
|
producer_complete: c_int,
|
|
producer_allow_fallback: c_int,
|
|
internal_result: usize,
|
|
}
|
|
|
|
unsafe fn build_seq_store_select_probe(
|
|
context: *mut c_void,
|
|
) -> &'static mut BuildSeqStoreSelectProbe {
|
|
unsafe { &mut *context.cast::<BuildSeqStoreSelectProbe>() }
|
|
}
|
|
|
|
unsafe extern "C" fn build_seq_store_test_skip(_context: *mut c_void, _src_size: usize) {}
|
|
|
|
unsafe extern "C" fn build_seq_store_test_prepare(
|
|
_context: *mut c_void,
|
|
_src: *const c_void,
|
|
_src_size: usize,
|
|
) {
|
|
}
|
|
|
|
unsafe extern "C" fn build_seq_store_test_external(
|
|
context: *mut c_void,
|
|
_seq_store: *mut SeqStore_t,
|
|
_next_rep: *mut u32,
|
|
_src: *const c_void,
|
|
_src_size: usize,
|
|
) -> usize {
|
|
let probe = unsafe { build_seq_store_select_probe(context) };
|
|
probe.events.push("external");
|
|
probe.external_result
|
|
}
|
|
|
|
unsafe extern "C" fn build_seq_store_test_ldm(
|
|
context: *mut c_void,
|
|
_seq_store: *mut SeqStore_t,
|
|
_next_rep: *mut u32,
|
|
_src: *const c_void,
|
|
_src_size: usize,
|
|
) -> usize {
|
|
let probe = unsafe { build_seq_store_select_probe(context) };
|
|
probe.events.push("ldm");
|
|
probe.ldm_result
|
|
}
|
|
|
|
unsafe extern "C" fn build_seq_store_test_producer(
|
|
context: *mut c_void,
|
|
_src: *const c_void,
|
|
_src_size: usize,
|
|
seq_store_complete: *mut c_int,
|
|
allow_fallback: *mut c_int,
|
|
) -> usize {
|
|
let probe = unsafe { build_seq_store_select_probe(context) };
|
|
probe.events.push("producer");
|
|
unsafe {
|
|
*seq_store_complete = probe.producer_complete;
|
|
*allow_fallback = probe.producer_allow_fallback;
|
|
}
|
|
probe.producer_result
|
|
}
|
|
|
|
unsafe extern "C" fn build_seq_store_test_internal(
|
|
context: *mut c_void,
|
|
_seq_store: *mut SeqStore_t,
|
|
_next_rep: *mut u32,
|
|
_src: *const c_void,
|
|
_src_size: usize,
|
|
) -> usize {
|
|
let probe = unsafe { build_seq_store_select_probe(context) };
|
|
probe.events.push("internal");
|
|
probe.internal_result
|
|
}
|
|
|
|
unsafe extern "C" fn build_seq_store_test_clear(context: *mut c_void) {
|
|
let probe = unsafe { build_seq_store_select_probe(context) };
|
|
probe.events.push("clear");
|
|
}
|
|
|
|
fn build_seq_store_select_test_state(
|
|
probe: &mut BuildSeqStoreSelectProbe,
|
|
has_external_sequences: c_int,
|
|
ldm_enabled: c_int,
|
|
has_external_sequence_producer: c_int,
|
|
enable_match_finder_fallback: c_int,
|
|
) -> ZSTD_rust_buildSeqStoreState {
|
|
ZSTD_rust_buildSeqStoreState {
|
|
seq_store: ptr::null_mut(),
|
|
prev_c_block: ptr::null_mut(),
|
|
next_c_block: ptr::null_mut(),
|
|
callback_context: (probe as *mut BuildSeqStoreSelectProbe).cast(),
|
|
min_match: 3,
|
|
validate_seq_store: 0,
|
|
skip_small_block: build_seq_store_test_skip,
|
|
prepare_match_state: build_seq_store_test_prepare,
|
|
has_external_sequences,
|
|
ldm_enabled,
|
|
has_external_sequence_producer,
|
|
enable_match_finder_fallback,
|
|
compress_external_sequences: build_seq_store_test_external,
|
|
compress_ldm: build_seq_store_test_ldm,
|
|
try_external_sequence_producer: build_seq_store_test_producer,
|
|
compress_internal: build_seq_store_test_internal,
|
|
clear_ldm_seq_store: build_seq_store_test_clear,
|
|
}
|
|
}
|
|
|
|
fn run_build_seq_store_select_test(state: &ZSTD_rust_buildSeqStoreState) -> (usize, c_int) {
|
|
let mut seq_store = unsafe { MaybeUninit::<SeqStore_t>::zeroed().assume_init() };
|
|
let mut next_rep = [0u32; ZSTD_REP_NUM];
|
|
let source = [0u8; 1];
|
|
let mut seq_store_complete = 99;
|
|
let result = unsafe {
|
|
build_seq_store_select_sequences_body_with(
|
|
state,
|
|
&mut seq_store,
|
|
next_rep.as_mut_ptr(),
|
|
source.as_ptr().cast(),
|
|
source.len(),
|
|
&mut seq_store_complete,
|
|
)
|
|
};
|
|
(result, seq_store_complete)
|
|
}
|
|
|
|
#[test]
|
|
fn build_seq_store_selects_preloaded_external_sequences_first() {
|
|
let mut probe = BuildSeqStoreSelectProbe {
|
|
external_result: 11,
|
|
ldm_result: 12,
|
|
internal_result: 13,
|
|
..Default::default()
|
|
};
|
|
let state = build_seq_store_select_test_state(&mut probe, 1, 1, 0, 0);
|
|
|
|
let (result, seq_store_complete) = run_build_seq_store_select_test(&state);
|
|
|
|
assert_eq!(result, probe.external_result);
|
|
assert_eq!(seq_store_complete, 0);
|
|
assert_eq!(probe.events, ["external"]);
|
|
}
|
|
|
|
#[test]
|
|
fn build_seq_store_uses_ldm_when_no_external_sequences_are_pending() {
|
|
let mut probe = BuildSeqStoreSelectProbe {
|
|
ldm_result: 19,
|
|
internal_result: 23,
|
|
..Default::default()
|
|
};
|
|
let state = build_seq_store_select_test_state(&mut probe, 0, 1, 0, 0);
|
|
|
|
let (result, seq_store_complete) = run_build_seq_store_select_test(&state);
|
|
|
|
assert_eq!(result, probe.ldm_result);
|
|
assert_eq!(seq_store_complete, 0);
|
|
assert_eq!(probe.events, ["ldm"]);
|
|
}
|
|
|
|
#[test]
|
|
fn build_seq_store_rejects_external_producer_with_preloaded_sequences_or_ldm() {
|
|
for (has_external_sequences, ldm_enabled) in [(1, 0), (0, 1)] {
|
|
let mut probe = BuildSeqStoreSelectProbe::default();
|
|
let state = build_seq_store_select_test_state(
|
|
&mut probe,
|
|
has_external_sequences,
|
|
ldm_enabled,
|
|
1,
|
|
1,
|
|
);
|
|
|
|
let (result, seq_store_complete) = run_build_seq_store_select_test(&state);
|
|
|
|
assert_eq!(
|
|
result,
|
|
ERROR(ZstdErrorCode::ParameterCombinationUnsupported)
|
|
);
|
|
assert_eq!(seq_store_complete, 0);
|
|
assert!(probe.events.is_empty());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn build_seq_store_clears_ldm_after_external_producer_completion() {
|
|
let mut probe = BuildSeqStoreSelectProbe {
|
|
producer_complete: 1,
|
|
producer_allow_fallback: 1,
|
|
producer_result: 0,
|
|
..Default::default()
|
|
};
|
|
let state = build_seq_store_select_test_state(&mut probe, 0, 0, 1, 1);
|
|
|
|
let (result, seq_store_complete) = run_build_seq_store_select_test(&state);
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(seq_store_complete, 1);
|
|
assert_eq!(probe.events, ["producer", "clear"]);
|
|
}
|
|
|
|
#[test]
|
|
fn build_seq_store_does_not_fallback_when_producer_disallows_it() {
|
|
let producer_result = ERROR(ZstdErrorCode::SequenceProducerFailed);
|
|
let mut probe = BuildSeqStoreSelectProbe {
|
|
producer_result,
|
|
producer_allow_fallback: 1,
|
|
internal_result: 17,
|
|
..Default::default()
|
|
};
|
|
let state = build_seq_store_select_test_state(&mut probe, 0, 0, 1, 0);
|
|
|
|
let (result, seq_store_complete) = run_build_seq_store_select_test(&state);
|
|
|
|
assert_eq!(result, producer_result);
|
|
assert_eq!(seq_store_complete, 0);
|
|
assert_eq!(probe.events, ["producer"]);
|
|
}
|
|
|
|
#[test]
|
|
fn build_seq_store_falls_back_after_external_producer_error() {
|
|
let producer_result = ERROR(ZstdErrorCode::SequenceProducerFailed);
|
|
let mut probe = BuildSeqStoreSelectProbe {
|
|
producer_result,
|
|
producer_allow_fallback: 1,
|
|
internal_result: 23,
|
|
..Default::default()
|
|
};
|
|
let state = build_seq_store_select_test_state(&mut probe, 0, 0, 1, 1);
|
|
|
|
let (result, seq_store_complete) = run_build_seq_store_select_test(&state);
|
|
|
|
assert_eq!(result, probe.internal_result);
|
|
assert_eq!(seq_store_complete, 0);
|
|
assert_eq!(probe.events, ["producer", "clear", "internal"]);
|
|
}
|
|
|
|
#[test]
|
|
fn build_seq_store_clears_ldm_before_internal_compression() {
|
|
let mut probe = BuildSeqStoreSelectProbe {
|
|
internal_result: 29,
|
|
..Default::default()
|
|
};
|
|
let state = build_seq_store_select_test_state(&mut probe, 0, 0, 0, 0);
|
|
|
|
let (result, seq_store_complete) = run_build_seq_store_select_test(&state);
|
|
|
|
assert_eq!(result, probe.internal_result);
|
|
assert_eq!(seq_store_complete, 0);
|
|
assert_eq!(probe.events, ["clear", "internal"]);
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct ResetMatchStateTestContext {
|
|
events: Vec<c_int>,
|
|
reserves: Vec<(c_int, usize)>,
|
|
pointers: Vec<(c_int, *mut c_void)>,
|
|
zeroed: Vec<(usize, usize)>,
|
|
storage: [usize; 32],
|
|
fail_after: usize,
|
|
}
|
|
|
|
unsafe fn reset_match_state_test_context(
|
|
context: *mut c_void,
|
|
) -> &'static mut ResetMatchStateTestContext {
|
|
unsafe { &mut *context.cast::<ResetMatchStateTestContext>() }
|
|
}
|
|
|
|
unsafe extern "C" fn reset_match_state_test_callback(context: *mut c_void) {
|
|
let context = unsafe { reset_match_state_test_context(context) };
|
|
context.events.push(1);
|
|
}
|
|
|
|
unsafe extern "C" fn reset_match_state_test_mark_dirty(context: *mut c_void) {
|
|
let context = unsafe { reset_match_state_test_context(context) };
|
|
context.events.push(2);
|
|
}
|
|
|
|
unsafe extern "C" fn reset_match_state_test_invalidate(context: *mut c_void) {
|
|
let context = unsafe { reset_match_state_test_context(context) };
|
|
context.events.push(3);
|
|
}
|
|
|
|
unsafe extern "C" fn reset_match_state_test_clear(context: *mut c_void) {
|
|
let context = unsafe { reset_match_state_test_context(context) };
|
|
context.events.push(4);
|
|
}
|
|
|
|
unsafe extern "C" fn reset_match_state_test_clean(context: *mut c_void) {
|
|
let context = unsafe { reset_match_state_test_context(context) };
|
|
context.events.push(5);
|
|
}
|
|
|
|
unsafe extern "C" fn reset_match_state_test_advance(context: *mut c_void) {
|
|
let context = unsafe { reset_match_state_test_context(context) };
|
|
context.events.push(6);
|
|
}
|
|
|
|
unsafe extern "C" fn reset_match_state_test_reserve(
|
|
context: *mut c_void,
|
|
reserve_kind: c_int,
|
|
size: usize,
|
|
) -> *mut c_void {
|
|
let context = unsafe { reset_match_state_test_context(context) };
|
|
context.events.push(100 + reserve_kind);
|
|
let index = context.reserves.len();
|
|
context.reserves.push((reserve_kind, size));
|
|
if context.fail_after != 0 && index + 1 >= context.fail_after {
|
|
ptr::null_mut()
|
|
} else {
|
|
unsafe { context.storage.as_mut_ptr().add(index).cast() }
|
|
}
|
|
}
|
|
|
|
unsafe extern "C" fn reset_match_state_test_reserve_failed(context: *mut c_void) -> c_int {
|
|
let context = unsafe { reset_match_state_test_context(context) };
|
|
c_int::from(context.fail_after != 0 && context.reserves.len() >= context.fail_after)
|
|
}
|
|
|
|
unsafe extern "C" fn reset_match_state_test_set_pointer(
|
|
context: *mut c_void,
|
|
pointer_kind: c_int,
|
|
pointer: *mut c_void,
|
|
) {
|
|
let context = unsafe { reset_match_state_test_context(context) };
|
|
context.events.push(200 + pointer_kind);
|
|
context.pointers.push((pointer_kind, pointer));
|
|
}
|
|
|
|
unsafe extern "C" fn reset_match_state_test_zero(
|
|
context: *mut c_void,
|
|
pointer: *mut c_void,
|
|
size: usize,
|
|
) {
|
|
let context = unsafe { reset_match_state_test_context(context) };
|
|
context.events.push(7);
|
|
context.zeroed.push((pointer as usize, size));
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn reset_match_state_test_state(
|
|
context: &mut ResetMatchStateTestContext,
|
|
c_params: ZSTD_compressionParameters,
|
|
dedicated_dict_search: c_int,
|
|
use_row_match_finder: c_int,
|
|
comp_reset_policy: c_int,
|
|
index_reset_policy: c_int,
|
|
reset_target: c_int,
|
|
hash_log3: &mut c_uint,
|
|
row_hash_log: &mut c_uint,
|
|
hash_salt: &mut u64,
|
|
lazy_skipping: &mut c_int,
|
|
c_params_out: &mut ZSTD_compressionParameters,
|
|
) -> ZSTD_rust_resetMatchStateState {
|
|
ZSTD_rust_resetMatchStateState {
|
|
callback_context: (context as *mut ResetMatchStateTestContext).cast(),
|
|
c_params,
|
|
dedicated_dict_search,
|
|
use_row_match_finder,
|
|
comp_reset_policy,
|
|
index_reset_policy,
|
|
reset_target,
|
|
hash_log3_max: 17,
|
|
lit_freq_size: 256 * size_of::<c_uint>(),
|
|
lit_length_freq_size: 36 * size_of::<c_uint>(),
|
|
match_length_freq_size: 53 * size_of::<c_uint>(),
|
|
off_code_freq_size: 32 * size_of::<c_uint>(),
|
|
match_size: 4099 * 2 * size_of::<c_uint>(),
|
|
optimal_size: 4099 * 8 * size_of::<c_uint>(),
|
|
hash_log3,
|
|
row_hash_log,
|
|
hash_salt,
|
|
lazy_skipping,
|
|
c_params_out,
|
|
set_pointer: Some(reset_match_state_test_set_pointer),
|
|
window_init: Some(reset_match_state_test_callback),
|
|
mark_tables_dirty: Some(reset_match_state_test_mark_dirty),
|
|
invalidate_match_state: Some(reset_match_state_test_invalidate),
|
|
clear_tables: Some(reset_match_state_test_clear),
|
|
clean_tables: Some(reset_match_state_test_clean),
|
|
advance_hash_salt: Some(reset_match_state_test_advance),
|
|
reserve: Some(reset_match_state_test_reserve),
|
|
reserve_failed: Some(reset_match_state_test_reserve_failed),
|
|
zero: Some(reset_match_state_test_zero),
|
|
}
|
|
}
|
|
|
|
fn reset_match_state_test_params(
|
|
strategy: c_int,
|
|
search_log: u32,
|
|
min_match: u32,
|
|
) -> ZSTD_compressionParameters {
|
|
ZSTD_compressionParameters {
|
|
windowLog: 20,
|
|
chainLog: 11,
|
|
hashLog: 12,
|
|
searchLog: search_log,
|
|
minMatch: min_match,
|
|
targetLength: 16,
|
|
strategy,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn reset_match_state_orders_cctx_opt_allocations() {
|
|
let c_params = reset_match_state_test_params(ZSTD_BTOPT, 5, 3);
|
|
let mut context = ResetMatchStateTestContext::default();
|
|
let mut hash_log3 = 99;
|
|
let mut row_hash_log = 99;
|
|
let mut hash_salt = 7;
|
|
let mut lazy_skipping = 9;
|
|
let mut c_params_out = ZSTD_compressionParameters::default();
|
|
let state = reset_match_state_test_state(
|
|
&mut context,
|
|
c_params,
|
|
0,
|
|
ZSTD_RUST_PS_DISABLE,
|
|
0,
|
|
1,
|
|
1,
|
|
&mut hash_log3,
|
|
&mut row_hash_log,
|
|
&mut hash_salt,
|
|
&mut lazy_skipping,
|
|
&mut c_params_out,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_resetMatchState(&state) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(hash_log3, 17);
|
|
assert_eq!(row_hash_log, 99);
|
|
assert_eq!(hash_salt, 7);
|
|
assert_eq!(lazy_skipping, 0);
|
|
assert_eq!(c_params_out, c_params);
|
|
assert_eq!(
|
|
context.events,
|
|
[
|
|
1, 2, 3, 4, 100, 200, 100, 201, 100, 202, 5, 102, 204, 102, 205, 102, 206, 102,
|
|
207, 102, 208, 102, 209,
|
|
]
|
|
);
|
|
assert_eq!(
|
|
context.reserves,
|
|
[
|
|
(0, (1 << 12) * size_of::<c_uint>()),
|
|
(0, (1 << 11) * size_of::<c_uint>()),
|
|
(0, (1 << 17) * size_of::<c_uint>()),
|
|
(2, 256 * size_of::<c_uint>()),
|
|
(2, 36 * size_of::<c_uint>()),
|
|
(2, 53 * size_of::<c_uint>()),
|
|
(2, 32 * size_of::<c_uint>()),
|
|
(2, 4099 * 2 * size_of::<c_uint>()),
|
|
(2, 4099 * 8 * size_of::<c_uint>()),
|
|
]
|
|
);
|
|
assert_eq!(context.pointers.len(), 9);
|
|
assert!(context.zeroed.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn reset_match_state_salts_cctx_row_tables_without_zeroing() {
|
|
let c_params = reset_match_state_test_params(ZSTD_GREEDY, 6, 4);
|
|
let mut context = ResetMatchStateTestContext::default();
|
|
let mut hash_log3 = 99;
|
|
let mut row_hash_log = 99;
|
|
let mut hash_salt = 7;
|
|
let mut lazy_skipping = 9;
|
|
let mut c_params_out = ZSTD_compressionParameters::default();
|
|
let state = reset_match_state_test_state(
|
|
&mut context,
|
|
c_params,
|
|
0,
|
|
ZSTD_RUST_PS_ENABLE,
|
|
0,
|
|
0,
|
|
1,
|
|
&mut hash_log3,
|
|
&mut row_hash_log,
|
|
&mut hash_salt,
|
|
&mut lazy_skipping,
|
|
&mut c_params_out,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_resetMatchState(&state) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(hash_log3, 0);
|
|
assert_eq!(row_hash_log, 6);
|
|
assert_eq!(hash_salt, 7);
|
|
assert_eq!(c_params_out, c_params);
|
|
assert_eq!(
|
|
context.events,
|
|
[3, 4, 100, 200, 100, 201, 100, 202, 5, 101, 203, 6]
|
|
);
|
|
assert_eq!(
|
|
context.reserves,
|
|
[
|
|
(0, (1 << 12) * size_of::<c_uint>()),
|
|
(0, 0),
|
|
(0, 0),
|
|
(1, 1 << 12),
|
|
]
|
|
);
|
|
assert_eq!(context.pointers.len(), 4);
|
|
assert!(context.zeroed.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn reset_match_state_uses_cdict_row_policy_without_opt_storage() {
|
|
let c_params = reset_match_state_test_params(ZSTD_GREEDY, 6, 4);
|
|
let mut context = ResetMatchStateTestContext::default();
|
|
let mut hash_log3 = 99;
|
|
let mut row_hash_log = 99;
|
|
let mut hash_salt = 7;
|
|
let mut lazy_skipping = 9;
|
|
let mut c_params_out = ZSTD_compressionParameters::default();
|
|
let state = reset_match_state_test_state(
|
|
&mut context,
|
|
c_params,
|
|
1,
|
|
ZSTD_RUST_PS_ENABLE,
|
|
1,
|
|
0,
|
|
0,
|
|
&mut hash_log3,
|
|
&mut row_hash_log,
|
|
&mut hash_salt,
|
|
&mut lazy_skipping,
|
|
&mut c_params_out,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_resetMatchState(&state) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(hash_log3, 0);
|
|
assert_eq!(row_hash_log, 6);
|
|
assert_eq!(hash_salt, 0);
|
|
assert_eq!(c_params_out, c_params);
|
|
assert_eq!(
|
|
context.events,
|
|
[3, 4, 100, 200, 100, 201, 100, 202, 102, 203, 7]
|
|
);
|
|
assert_eq!(
|
|
context.reserves,
|
|
[
|
|
(0, (1 << 12) * size_of::<c_uint>()),
|
|
(0, (1 << 11) * size_of::<c_uint>()),
|
|
(0, 0),
|
|
(2, 1 << 12),
|
|
]
|
|
);
|
|
assert_eq!(context.pointers.len(), 4);
|
|
assert_eq!(context.zeroed.len(), 1);
|
|
assert_eq!(context.zeroed[0].1, 1 << 12);
|
|
}
|
|
|
|
#[test]
|
|
fn reset_match_state_propagates_table_allocation_failure() {
|
|
let c_params = reset_match_state_test_params(ZSTD_GREEDY, 4, 4);
|
|
let mut context = ResetMatchStateTestContext {
|
|
fail_after: 1,
|
|
..ResetMatchStateTestContext::default()
|
|
};
|
|
let mut hash_log3 = 99;
|
|
let mut row_hash_log = 99;
|
|
let mut hash_salt = 7;
|
|
let mut lazy_skipping = 9;
|
|
let mut c_params_out = ZSTD_compressionParameters::default();
|
|
let state = reset_match_state_test_state(
|
|
&mut context,
|
|
c_params,
|
|
0,
|
|
ZSTD_RUST_PS_DISABLE,
|
|
0,
|
|
1,
|
|
1,
|
|
&mut hash_log3,
|
|
&mut row_hash_log,
|
|
&mut hash_salt,
|
|
&mut lazy_skipping,
|
|
&mut c_params_out,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_resetMatchState(&state) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::MemoryAllocation));
|
|
assert_eq!(context.events, [1, 2, 3, 4, 100, 200, 100, 201, 100, 202]);
|
|
assert_eq!(context.pointers.len(), 3);
|
|
assert_eq!(hash_log3, 0);
|
|
assert_eq!(lazy_skipping, 0);
|
|
assert_eq!(c_params_out, ZSTD_compressionParameters::default());
|
|
}
|
|
|
|
#[test]
|
|
fn reference_external_sequences_resets_store_state() {
|
|
let mut source = [0u8; 3];
|
|
let sequence = source.as_mut_ptr().cast::<c_void>();
|
|
let mut stored_sequence = ptr::null_mut();
|
|
let mut pos = 7;
|
|
let mut pos_in_sequence = 8;
|
|
let mut size = 9;
|
|
let mut capacity = 10;
|
|
let state = ZSTD_rust_externalSequenceStoreState {
|
|
seq: &mut stored_sequence,
|
|
pos: &mut pos,
|
|
pos_in_sequence: &mut pos_in_sequence,
|
|
size: &mut size,
|
|
capacity: &mut capacity,
|
|
};
|
|
|
|
unsafe { ZSTD_rust_referenceExternalSequences(&state, sequence, 11) };
|
|
|
|
assert_eq!(stored_sequence, sequence);
|
|
assert_eq!(pos, 0);
|
|
assert_eq!(pos_in_sequence, 0);
|
|
assert_eq!(size, 11);
|
|
assert_eq!(capacity, 11);
|
|
}
|
|
|
|
#[test]
|
|
fn reference_external_sequences_rejects_incomplete_state() {
|
|
let mut pos = 7;
|
|
let mut pos_in_sequence = 8;
|
|
let mut size = 9;
|
|
let mut capacity = 10;
|
|
let state = ZSTD_rust_externalSequenceStoreState {
|
|
seq: ptr::null_mut(),
|
|
pos: &mut pos,
|
|
pos_in_sequence: &mut pos_in_sequence,
|
|
size: &mut size,
|
|
capacity: &mut capacity,
|
|
};
|
|
|
|
unsafe { ZSTD_rust_referenceExternalSequences(&state, ptr::null_mut(), 11) };
|
|
|
|
assert_eq!(pos, 7);
|
|
assert_eq!(pos_in_sequence, 8);
|
|
assert_eq!(size, 9);
|
|
assert_eq!(capacity, 10);
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct ExternalSequenceProducerProbe {
|
|
events: Vec<&'static str>,
|
|
sequences: Vec<ZSTD_Sequence>,
|
|
producer_result: usize,
|
|
producer_dict: *const c_void,
|
|
producer_dict_size: usize,
|
|
producer_level: c_int,
|
|
producer_window_size: usize,
|
|
transfer_sequence_count: usize,
|
|
transfer_src_size: usize,
|
|
transfer_result: usize,
|
|
}
|
|
|
|
unsafe fn external_sequence_producer_probe(
|
|
context: *mut c_void,
|
|
) -> &'static mut ExternalSequenceProducerProbe {
|
|
unsafe { &mut *context.cast::<ExternalSequenceProducerProbe>() }
|
|
}
|
|
|
|
unsafe extern "C" fn external_sequence_producer_test_producer(
|
|
context: *mut c_void,
|
|
out_seqs: *mut ZSTD_Sequence,
|
|
out_seqs_capacity: usize,
|
|
_src: *const c_void,
|
|
_src_size: usize,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
compression_level: c_int,
|
|
window_size: usize,
|
|
) -> usize {
|
|
let probe = unsafe { external_sequence_producer_probe(context) };
|
|
probe.events.push("produce");
|
|
probe.producer_dict = dict;
|
|
probe.producer_dict_size = dict_size;
|
|
probe.producer_level = compression_level;
|
|
probe.producer_window_size = window_size;
|
|
assert!(probe.sequences.len() <= out_seqs_capacity);
|
|
unsafe {
|
|
ptr::copy_nonoverlapping(probe.sequences.as_ptr(), out_seqs, probe.sequences.len());
|
|
}
|
|
probe.producer_result
|
|
}
|
|
|
|
unsafe extern "C" fn external_sequence_producer_test_transfer(
|
|
context: *mut c_void,
|
|
_seq_pos: *mut ZSTD_SequencePosition,
|
|
_in_seqs: *const ZSTD_Sequence,
|
|
in_seqs_size: usize,
|
|
_src: *const c_void,
|
|
block_size: usize,
|
|
) -> usize {
|
|
let probe = unsafe { external_sequence_producer_probe(context) };
|
|
probe.events.push("transfer");
|
|
probe.transfer_sequence_count = in_seqs_size;
|
|
probe.transfer_src_size = block_size;
|
|
probe.transfer_result
|
|
}
|
|
|
|
fn external_sequence_producer_test_state(
|
|
probe: &mut ExternalSequenceProducerProbe,
|
|
ext_seq_buf: &mut [ZSTD_Sequence],
|
|
ext_seq_buf_capacity: &usize,
|
|
src: &[u8],
|
|
src_size: &usize,
|
|
compression_level: &c_int,
|
|
window_size: &usize,
|
|
external_seq_count: &mut usize,
|
|
seq_store_complete: &mut c_int,
|
|
allow_fallback: &mut c_int,
|
|
) -> ZSTD_rust_externalSequenceProducerState {
|
|
let callback_context = (probe as *mut ExternalSequenceProducerProbe).cast();
|
|
ZSTD_rust_externalSequenceProducerState {
|
|
callback_context,
|
|
producer_state: callback_context,
|
|
producer: Some(external_sequence_producer_test_producer),
|
|
ext_seq_buf: ext_seq_buf.as_mut_ptr(),
|
|
ext_seq_buf_capacity,
|
|
src: src.as_ptr().cast(),
|
|
src_size,
|
|
compression_level,
|
|
window_size,
|
|
transfer: Some(external_sequence_producer_test_transfer),
|
|
external_seq_count,
|
|
seq_store_complete,
|
|
allow_fallback,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn external_sequence_producer_success_transfers_after_post_processing() {
|
|
let source = [1u8, 2, 3, 4, 5];
|
|
let mut probe = ExternalSequenceProducerProbe {
|
|
sequences: vec![ZSTD_Sequence {
|
|
offset: 0,
|
|
litLength: source.len() as u32,
|
|
matchLength: 0,
|
|
rep: 0,
|
|
}],
|
|
producer_result: 1,
|
|
..Default::default()
|
|
};
|
|
let mut ext_seq_buf = [ZSTD_Sequence {
|
|
offset: 0,
|
|
litLength: 0,
|
|
matchLength: 0,
|
|
rep: 0,
|
|
}; 2];
|
|
let ext_seq_buf_capacity = ext_seq_buf.len();
|
|
let src_size = source.len();
|
|
let compression_level = 7;
|
|
let window_size = 1 << 20;
|
|
let mut external_seq_count = 0;
|
|
let mut seq_store_complete = 0;
|
|
let mut allow_fallback = 0;
|
|
let state = external_sequence_producer_test_state(
|
|
&mut probe,
|
|
&mut ext_seq_buf,
|
|
&ext_seq_buf_capacity,
|
|
&source,
|
|
&src_size,
|
|
&compression_level,
|
|
&window_size,
|
|
&mut external_seq_count,
|
|
&mut seq_store_complete,
|
|
&mut allow_fallback,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_tryExternalSequenceProducer(&state) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(probe.events, ["produce", "transfer"]);
|
|
assert!(probe.producer_dict.is_null());
|
|
assert_eq!(probe.producer_dict_size, 0);
|
|
assert_eq!(probe.producer_level, compression_level);
|
|
assert_eq!(probe.producer_window_size, window_size);
|
|
assert_eq!(probe.transfer_sequence_count, 1);
|
|
assert_eq!(probe.transfer_src_size, source.len());
|
|
assert_eq!(external_seq_count, 1);
|
|
assert_eq!(seq_store_complete, 1);
|
|
assert_eq!(allow_fallback, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn external_sequence_producer_errors_enable_fallback_before_transfer() {
|
|
let source = [1u8, 2, 3];
|
|
let mut probe = ExternalSequenceProducerProbe {
|
|
producer_result: ERROR(ZstdErrorCode::SequenceProducerFailed),
|
|
..Default::default()
|
|
};
|
|
let mut ext_seq_buf = [ZSTD_Sequence {
|
|
offset: 0,
|
|
litLength: 0,
|
|
matchLength: 0,
|
|
rep: 0,
|
|
}; 2];
|
|
let ext_seq_buf_capacity = ext_seq_buf.len();
|
|
let src_size = source.len();
|
|
let compression_level = 3;
|
|
let window_size = 1 << 20;
|
|
let mut external_seq_count = 0;
|
|
let mut seq_store_complete = 0;
|
|
let mut allow_fallback = 0;
|
|
let state = external_sequence_producer_test_state(
|
|
&mut probe,
|
|
&mut ext_seq_buf,
|
|
&ext_seq_buf_capacity,
|
|
&source,
|
|
&src_size,
|
|
&compression_level,
|
|
&window_size,
|
|
&mut external_seq_count,
|
|
&mut seq_store_complete,
|
|
&mut allow_fallback,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_tryExternalSequenceProducer(&state) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::SequenceProducerFailed));
|
|
assert_eq!(probe.events, ["produce"]);
|
|
assert_eq!(external_seq_count, result);
|
|
assert_eq!(seq_store_complete, 0);
|
|
assert_eq!(allow_fallback, 1);
|
|
}
|
|
|
|
#[test]
|
|
fn external_sequence_producer_invalid_length_does_not_enable_fallback() {
|
|
let source = [1u8, 2, 3];
|
|
let mut probe = ExternalSequenceProducerProbe {
|
|
sequences: vec![ZSTD_Sequence {
|
|
offset: 0,
|
|
litLength: (source.len() + 1) as u32,
|
|
matchLength: 0,
|
|
rep: 0,
|
|
}],
|
|
producer_result: 1,
|
|
..Default::default()
|
|
};
|
|
let mut ext_seq_buf = [ZSTD_Sequence {
|
|
offset: 0,
|
|
litLength: 0,
|
|
matchLength: 0,
|
|
rep: 0,
|
|
}; 2];
|
|
let ext_seq_buf_capacity = ext_seq_buf.len();
|
|
let src_size = source.len();
|
|
let compression_level = 3;
|
|
let window_size = 1 << 20;
|
|
let mut external_seq_count = 0;
|
|
let mut seq_store_complete = 0;
|
|
let mut allow_fallback = 0;
|
|
let state = external_sequence_producer_test_state(
|
|
&mut probe,
|
|
&mut ext_seq_buf,
|
|
&ext_seq_buf_capacity,
|
|
&source,
|
|
&src_size,
|
|
&compression_level,
|
|
&window_size,
|
|
&mut external_seq_count,
|
|
&mut seq_store_complete,
|
|
&mut allow_fallback,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_tryExternalSequenceProducer(&state) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::ExternalSequencesInvalid));
|
|
assert_eq!(probe.events, ["produce"]);
|
|
assert_eq!(seq_store_complete, 0);
|
|
assert_eq!(allow_fallback, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn external_sequence_producer_transfer_errors_do_not_enable_fallback() {
|
|
let source = [1u8, 2, 3];
|
|
let mut probe = ExternalSequenceProducerProbe {
|
|
sequences: vec![ZSTD_Sequence {
|
|
offset: 0,
|
|
litLength: source.len() as u32,
|
|
matchLength: 0,
|
|
rep: 0,
|
|
}],
|
|
producer_result: 1,
|
|
transfer_result: ERROR(ZstdErrorCode::ExternalSequencesInvalid),
|
|
..Default::default()
|
|
};
|
|
let mut ext_seq_buf = [ZSTD_Sequence {
|
|
offset: 0,
|
|
litLength: 0,
|
|
matchLength: 0,
|
|
rep: 0,
|
|
}; 2];
|
|
let ext_seq_buf_capacity = ext_seq_buf.len();
|
|
let src_size = source.len();
|
|
let compression_level = 3;
|
|
let window_size = 1 << 20;
|
|
let mut external_seq_count = 0;
|
|
let mut seq_store_complete = 0;
|
|
let mut allow_fallback = 0;
|
|
let state = external_sequence_producer_test_state(
|
|
&mut probe,
|
|
&mut ext_seq_buf,
|
|
&ext_seq_buf_capacity,
|
|
&source,
|
|
&src_size,
|
|
&compression_level,
|
|
&window_size,
|
|
&mut external_seq_count,
|
|
&mut seq_store_complete,
|
|
&mut allow_fallback,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_tryExternalSequenceProducer(&state) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::ExternalSequencesInvalid));
|
|
assert_eq!(probe.events, ["produce", "transfer"]);
|
|
assert_eq!(seq_store_complete, 0);
|
|
assert_eq!(allow_fallback, 0);
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct Compress2TestContext {
|
|
events: Vec<&'static str>,
|
|
in_buffer_mode: c_int,
|
|
out_buffer_mode: c_int,
|
|
reset_result: usize,
|
|
stream_result: usize,
|
|
output_pos: usize,
|
|
input_pos: usize,
|
|
}
|
|
|
|
unsafe fn compress2_test_context(context: *mut c_void) -> &'static mut Compress2TestContext {
|
|
unsafe { &mut *context.cast::<Compress2TestContext>() }
|
|
}
|
|
|
|
unsafe extern "C" fn compress2_test_reset(context: *mut c_void) -> usize {
|
|
let context = unsafe { compress2_test_context(context) };
|
|
context.events.push("reset");
|
|
context.reset_result
|
|
}
|
|
|
|
unsafe extern "C" fn compress2_test_set_buffer_modes(
|
|
context: *mut c_void,
|
|
in_buffer_mode: c_int,
|
|
out_buffer_mode: c_int,
|
|
) {
|
|
let context = unsafe { compress2_test_context(context) };
|
|
context.events.push(
|
|
if in_buffer_mode == ZSTD_BM_STABLE && out_buffer_mode == ZSTD_BM_STABLE {
|
|
"stable"
|
|
} else {
|
|
"restore"
|
|
},
|
|
);
|
|
context.in_buffer_mode = in_buffer_mode;
|
|
context.out_buffer_mode = out_buffer_mode;
|
|
}
|
|
|
|
unsafe extern "C" fn compress2_test_stream_end(
|
|
context: *mut c_void,
|
|
_dst: *mut c_void,
|
|
_dst_capacity: usize,
|
|
dst_pos: *mut usize,
|
|
_src: *const c_void,
|
|
_src_size: usize,
|
|
src_pos: *mut usize,
|
|
) -> usize {
|
|
let context = unsafe { compress2_test_context(context) };
|
|
context.events.push("stream-end");
|
|
unsafe {
|
|
*dst_pos = context.output_pos;
|
|
*src_pos = context.input_pos;
|
|
}
|
|
context.stream_result
|
|
}
|
|
|
|
fn compress2_test_state(
|
|
context: &mut Compress2TestContext,
|
|
original_in_buffer_mode: c_int,
|
|
original_out_buffer_mode: c_int,
|
|
) -> ZSTD_rust_compress2State {
|
|
ZSTD_rust_compress2State {
|
|
callback_context: (context as *mut Compress2TestContext).cast(),
|
|
reset_session: compress2_test_reset,
|
|
set_buffer_modes: compress2_test_set_buffer_modes,
|
|
compress_stream_end: compress2_test_stream_end,
|
|
original_in_buffer_mode,
|
|
original_out_buffer_mode,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn compress2_fallback_restores_modes_after_codec_error() {
|
|
let mut context = Compress2TestContext {
|
|
stream_result: ERROR(ZstdErrorCode::MemoryAllocation),
|
|
output_pos: 4,
|
|
input_pos: 3,
|
|
in_buffer_mode: 7,
|
|
out_buffer_mode: 8,
|
|
..Compress2TestContext::default()
|
|
};
|
|
let state = compress2_test_state(&mut context, 7, 8);
|
|
let src = [0u8; 3];
|
|
let mut dst = [0u8; 4];
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_compress2(
|
|
&state,
|
|
dst.as_mut_ptr().cast(),
|
|
dst.len(),
|
|
src.as_ptr().cast(),
|
|
src.len(),
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::MemoryAllocation));
|
|
assert_eq!(context.events, ["reset", "stable", "stream-end", "restore"]);
|
|
assert_eq!((context.in_buffer_mode, context.out_buffer_mode), (7, 8));
|
|
}
|
|
|
|
#[test]
|
|
fn compress2_fallback_maps_remaining_output_to_dst_size_too_small() {
|
|
let mut context = Compress2TestContext {
|
|
stream_result: 1,
|
|
output_pos: 4,
|
|
input_pos: 3,
|
|
in_buffer_mode: 7,
|
|
out_buffer_mode: 8,
|
|
..Compress2TestContext::default()
|
|
};
|
|
let state = compress2_test_state(&mut context, 7, 8);
|
|
let src = [0u8; 3];
|
|
let mut dst = [0u8; 4];
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_compress2(
|
|
&state,
|
|
dst.as_mut_ptr().cast(),
|
|
dst.len(),
|
|
src.as_ptr().cast(),
|
|
src.len(),
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::DstSizeTooSmall));
|
|
assert_eq!(context.events, ["reset", "stable", "stream-end", "restore"]);
|
|
assert_eq!((context.in_buffer_mode, context.out_buffer_mode), (7, 8));
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct CompressEndTestContext {
|
|
events: Vec<&'static str>,
|
|
continue_result: usize,
|
|
trace_extra: usize,
|
|
continue_frame: c_uint,
|
|
continue_last_frame_chunk: c_uint,
|
|
stage: c_int,
|
|
window: ZSTD_rust_windowUpdateState,
|
|
force_non_contiguous: c_int,
|
|
next_to_update: c_uint,
|
|
window_projection: ZSTD_rust_compressContinueWindowProjection,
|
|
frame_chunk_clamp_state: Option<ZSTD_rust_frameChunkClampState>,
|
|
frame_chunk_prepare_state: Option<ZSTD_rust_frameChunkPrepareState>,
|
|
frame_chunk_state: Option<ZSTD_rust_frameChunkState>,
|
|
continue_state: Option<ZSTD_rust_compressContinueState>,
|
|
is_first_block: c_int,
|
|
produced_c_size: u64,
|
|
}
|
|
|
|
static COMPRESS_END_TEST_CHECKSUM_STATE: XXH64_state_t = XXH64_state_t {
|
|
total_len: 0,
|
|
v: [0; 4],
|
|
mem64: [0; 4],
|
|
memsize: 0,
|
|
reserved32: 0,
|
|
reserved64: 0,
|
|
};
|
|
|
|
unsafe fn compress_end_test_context(
|
|
context: *mut c_void,
|
|
) -> &'static mut CompressEndTestContext {
|
|
unsafe { &mut *context.cast::<CompressEndTestContext>() }
|
|
}
|
|
|
|
unsafe extern "C" fn compress_end_test_frame_prepare_overflow(
|
|
_context: *mut c_void,
|
|
_src: *const c_void,
|
|
_block_size: usize,
|
|
) {
|
|
}
|
|
|
|
unsafe extern "C" fn compress_end_test_frame_prepare_window(
|
|
_context: *mut c_void,
|
|
_src: *const c_void,
|
|
_block_size: usize,
|
|
_max_dist: c_uint,
|
|
) {
|
|
}
|
|
|
|
unsafe extern "C" fn compress_end_test_frame_target(
|
|
context: *mut c_void,
|
|
_dst: *mut c_void,
|
|
_dst_capacity: usize,
|
|
_src: *const c_void,
|
|
_src_size: usize,
|
|
last_block: c_uint,
|
|
) -> usize {
|
|
let context = unsafe { compress_end_test_context(context) };
|
|
context.events.push("continue");
|
|
context.continue_frame = 1;
|
|
context.continue_last_frame_chunk = last_block;
|
|
context.continue_result
|
|
}
|
|
|
|
unsafe extern "C" fn compress_end_test_trace(context: *mut c_void, extra_c_size: usize) {
|
|
let context = unsafe { compress_end_test_context(context) };
|
|
context.events.push("trace");
|
|
context.trace_extra = extra_c_size;
|
|
}
|
|
|
|
fn compress_end_test_state(
|
|
context: &mut CompressEndTestContext,
|
|
consumed_src_size: &mut u64,
|
|
pledged_src_size_plus_one: u64,
|
|
content_size_flag: c_int,
|
|
) -> ZSTD_rust_compressEndState {
|
|
let base = WINDOW_INIT_SENTINEL.as_ptr().cast::<c_void>();
|
|
context.stage = ZSTD_COMPRESSION_STAGE_ONGOING;
|
|
context.window = ZSTD_rust_windowUpdateState {
|
|
nextSrc: base,
|
|
base,
|
|
dictBase: base,
|
|
dictLimit: ZSTD_WINDOW_START_INDEX,
|
|
lowLimit: ZSTD_WINDOW_START_INDEX,
|
|
};
|
|
context.force_non_contiguous = 0;
|
|
context.next_to_update = ZSTD_WINDOW_START_INDEX;
|
|
context.is_first_block = 1;
|
|
context.produced_c_size = 0;
|
|
context.window_projection = ZSTD_rust_compressContinueWindowProjection {
|
|
next_src: ptr::addr_of_mut!(context.window.nextSrc),
|
|
base: ptr::addr_of_mut!(context.window.base),
|
|
dict_base: ptr::addr_of_mut!(context.window.dictBase),
|
|
dict_limit: ptr::addr_of_mut!(context.window.dictLimit),
|
|
low_limit: ptr::addr_of_mut!(context.window.lowLimit),
|
|
force_non_contiguous: ptr::addr_of_mut!(context.force_non_contiguous),
|
|
next_to_update: ptr::addr_of_mut!(context.next_to_update),
|
|
};
|
|
let callback_context = (context as *mut CompressEndTestContext).cast();
|
|
context.frame_chunk_clamp_state = Some(ZSTD_rust_frameChunkClampState {
|
|
next_to_update: ptr::addr_of_mut!(context.next_to_update),
|
|
low_limit: ptr::addr_of!(context.window.lowLimit),
|
|
});
|
|
let frame_chunk_clamp_state = context
|
|
.frame_chunk_clamp_state
|
|
.as_ref()
|
|
.map_or(ptr::null(), |state| state as *const _);
|
|
context.frame_chunk_prepare_state = Some(ZSTD_rust_frameChunkPrepareState {
|
|
callback_context,
|
|
max_dist: 64,
|
|
correct_overflow: compress_end_test_frame_prepare_overflow,
|
|
check_dict_validity: compress_end_test_frame_prepare_window,
|
|
enforce_max_dist: compress_end_test_frame_prepare_window,
|
|
clamp_state: frame_chunk_clamp_state,
|
|
});
|
|
let frame_chunk_prepare_state = context
|
|
.frame_chunk_prepare_state
|
|
.as_ref()
|
|
.map_or(ptr::null(), |state| state as *const _);
|
|
context.frame_chunk_state = Some(ZSTD_rust_frameChunkState {
|
|
callback_context,
|
|
tmp_workspace: ptr::null_mut(),
|
|
checksum_state: ptr::null_mut(),
|
|
is_first_block: ptr::addr_of_mut!(context.is_first_block),
|
|
stage: ptr::addr_of_mut!(context.stage),
|
|
tmp_wksp_size: 0,
|
|
block_size_max: 1,
|
|
savings: 0,
|
|
pre_block_splitter_level: 1,
|
|
strategy: ZSTD_FAST,
|
|
use_target_c_block_size: 1,
|
|
block_splitter_enabled: 0,
|
|
checksum_flag: 0,
|
|
ending_stage: ZSTD_COMPRESSION_STAGE_ENDING,
|
|
prepare_state: frame_chunk_prepare_state,
|
|
compress_target: Some(compress_end_test_frame_target),
|
|
compress_split: Some(compress_end_test_frame_target),
|
|
compress_internal: Some(compress_end_test_frame_target),
|
|
compress_internal_state: ptr::null(),
|
|
compress_target_state: ptr::null(),
|
|
compress_split_state: ptr::null(),
|
|
derive_block_splits_state: ptr::null(),
|
|
});
|
|
let frame_chunk_state = context
|
|
.frame_chunk_state
|
|
.as_ref()
|
|
.map_or(ptr::null(), |state| state as *const _);
|
|
context.continue_state = Some(ZSTD_rust_compressContinueState {
|
|
callback_context,
|
|
window_state: &context.window_projection,
|
|
ldm_window_state: ptr::null(),
|
|
overflow_state: ptr::null(),
|
|
frame_chunk_state,
|
|
compress_block: compress_end_test_frame_target,
|
|
stage: ptr::addr_of_mut!(context.stage),
|
|
consumed_src_size: consumed_src_size as *mut u64,
|
|
produced_c_size: ptr::addr_of_mut!(context.produced_c_size),
|
|
pledged_src_size_plus_one,
|
|
block_size_max: 1,
|
|
check_block_size: 0,
|
|
no_dict_id_flag: 0,
|
|
checksum_flag: 0,
|
|
content_size_flag: 0,
|
|
format: 0,
|
|
window_log: 20,
|
|
dict_id: 0,
|
|
ldm_enabled: 0,
|
|
});
|
|
let continue_state = context
|
|
.continue_state
|
|
.as_ref()
|
|
.map_or(ptr::null(), |state| state as *const _);
|
|
ZSTD_rust_compressEndState {
|
|
callback_context,
|
|
compress_continue_state: continue_state,
|
|
trace: compress_end_test_trace,
|
|
consumed_src_size: consumed_src_size as *const u64,
|
|
pledged_src_size_plus_one,
|
|
content_size_flag,
|
|
stage: &mut context.stage,
|
|
no_dict_id_flag: 0,
|
|
checksum_flag: 1,
|
|
format: 0,
|
|
window_log: 20,
|
|
checksum_state: &COMPRESS_END_TEST_CHECKSUM_STATE,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn compress_end_preserves_callback_order_and_output_accounting() {
|
|
let mut dst = [0u8; 32];
|
|
let mut context = CompressEndTestContext {
|
|
continue_result: 3,
|
|
..CompressEndTestContext::default()
|
|
};
|
|
let mut consumed_src_size = 7;
|
|
let state = compress_end_test_state(&mut context, &mut consumed_src_size, 9, 1);
|
|
let source = [0u8; 1];
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_compressEnd(
|
|
&state,
|
|
dst.as_mut_ptr().cast(),
|
|
dst.len(),
|
|
source.as_ptr().cast(),
|
|
source.len(),
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, 7);
|
|
assert_eq!(context.events, ["continue", "trace"]);
|
|
assert_eq!(
|
|
(context.continue_frame, context.continue_last_frame_chunk),
|
|
(1, 1)
|
|
);
|
|
assert_eq!(context.trace_extra, 4);
|
|
assert_eq!((consumed_src_size, context.produced_c_size), (8, 3));
|
|
assert_eq!(context.stage, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn compress_end_stops_before_epilogue_when_continue_fails() {
|
|
let mut dst = [0u8; 16];
|
|
let mut context = CompressEndTestContext {
|
|
continue_result: ERROR(ZstdErrorCode::MemoryAllocation),
|
|
..CompressEndTestContext::default()
|
|
};
|
|
let mut consumed_src_size = 7;
|
|
let state = compress_end_test_state(&mut context, &mut consumed_src_size, 8, 0);
|
|
let source = [0u8; 1];
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_compressEnd(
|
|
&state,
|
|
dst.as_mut_ptr().cast(),
|
|
dst.len(),
|
|
source.as_ptr().cast(),
|
|
source.len(),
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::MemoryAllocation));
|
|
assert_eq!(context.events, ["continue"]);
|
|
}
|
|
|
|
#[test]
|
|
fn compress_end_stops_before_validation_and_trace_when_epilogue_fails() {
|
|
let mut dst = [0u8; 6];
|
|
let mut context = CompressEndTestContext {
|
|
continue_result: 3,
|
|
..CompressEndTestContext::default()
|
|
};
|
|
let mut consumed_src_size = 7;
|
|
let state = compress_end_test_state(&mut context, &mut consumed_src_size, 99, 1);
|
|
let source = [0u8; 1];
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_compressEnd(
|
|
&state,
|
|
dst.as_mut_ptr().cast(),
|
|
dst.len(),
|
|
source.as_ptr().cast(),
|
|
source.len(),
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::DstSizeTooSmall));
|
|
assert_eq!(context.events, ["continue"]);
|
|
}
|
|
|
|
#[test]
|
|
fn compress_end_rejects_pledged_size_before_trace() {
|
|
let mut dst = [0u8; 32];
|
|
let mut context = CompressEndTestContext {
|
|
continue_result: 3,
|
|
..CompressEndTestContext::default()
|
|
};
|
|
let mut consumed_src_size = 7;
|
|
let state = compress_end_test_state(&mut context, &mut consumed_src_size, 99, 1);
|
|
let source = [0u8; 1];
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_compressEnd(
|
|
&state,
|
|
dst.as_mut_ptr().cast(),
|
|
dst.len(),
|
|
source.as_ptr().cast(),
|
|
source.len(),
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::SrcSizeWrong));
|
|
assert_eq!(context.events, ["continue"]);
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct CompressStreamInitTestContext {
|
|
events: Vec<&'static str>,
|
|
local_create_result: usize,
|
|
create_result: usize,
|
|
mt_result: usize,
|
|
begin_result: usize,
|
|
pledged: u64,
|
|
local_dict: *const c_void,
|
|
local_dict_buffer: *mut c_void,
|
|
local_dict_size: usize,
|
|
local_dict_content_type: c_int,
|
|
local_cdict: *mut c_void,
|
|
context_cdict: *const c_void,
|
|
prefix_dict: *const c_void,
|
|
created_local_cdict: *mut c_void,
|
|
cdict: *const c_void,
|
|
cdict_is_local: c_int,
|
|
cdict_compression_level: c_int,
|
|
cdict_dict_content_size: usize,
|
|
expected_prefix: *const c_void,
|
|
nb_workers: c_uint,
|
|
has_ext_seq_prod: c_int,
|
|
mt_context: *mut c_void,
|
|
buffer_mode: c_int,
|
|
block_size: usize,
|
|
ordinary_target: usize,
|
|
compression_level: c_int,
|
|
}
|
|
|
|
unsafe fn compress_stream_init_test_context(
|
|
context: *mut c_void,
|
|
) -> &'static mut CompressStreamInitTestContext {
|
|
unsafe { &mut *context.cast::<CompressStreamInitTestContext>() }
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_init_test_get_local_dict(
|
|
context: *mut c_void,
|
|
local_dict: *mut ZSTD_rust_compressStreamInitLocalDictState,
|
|
) {
|
|
let context = unsafe { compress_stream_init_test_context(context) };
|
|
context.events.push("local-dict");
|
|
let local_dict = unsafe { &mut *local_dict };
|
|
local_dict.dict = context.local_dict;
|
|
local_dict.dict_buffer = context.local_dict_buffer;
|
|
local_dict.dict_size = context.local_dict_size;
|
|
local_dict.dict_content_type = context.local_dict_content_type;
|
|
local_dict.local_cdict = context.local_cdict;
|
|
local_dict.cdict = context.context_cdict;
|
|
local_dict.prefix_dict = context.prefix_dict;
|
|
local_dict.created_cdict = ptr::null_mut();
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_init_test_create_local_dict(
|
|
context: *mut c_void,
|
|
local_dict: *mut ZSTD_rust_compressStreamInitLocalDictState,
|
|
) -> usize {
|
|
let context = unsafe { compress_stream_init_test_context(context) };
|
|
context.events.push("create-local-dict");
|
|
unsafe { &mut *local_dict }.created_cdict = context.created_local_cdict;
|
|
context.local_create_result
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_init_test_publish_local_dict(
|
|
context: *mut c_void,
|
|
local_dict: *const ZSTD_rust_compressStreamInitLocalDictState,
|
|
) {
|
|
let context = unsafe { compress_stream_init_test_context(context) };
|
|
context.events.push("publish-local-dict");
|
|
let local_dict = unsafe { &*local_dict };
|
|
context.local_cdict = local_dict.created_cdict;
|
|
context.context_cdict = local_dict.created_cdict.cast_const();
|
|
context.cdict = local_dict.created_cdict.cast_const();
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_init_test_refresh_cdict(
|
|
context: *mut c_void,
|
|
dictionaries: *mut ZSTD_rust_compressStreamInitDictionaryState,
|
|
) {
|
|
let context = unsafe { compress_stream_init_test_context(context) };
|
|
context.events.push("refresh-cdict");
|
|
let dictionaries = unsafe { &mut *dictionaries };
|
|
dictionaries.cdict = context.cdict;
|
|
dictionaries.cdict_is_local = context.cdict_is_local;
|
|
dictionaries.cdict_compression_level = context.cdict_compression_level;
|
|
dictionaries.cdict_dict_content_size = context.cdict_dict_content_size;
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_init_test_clear_prefix(context: *mut c_void) {
|
|
unsafe { compress_stream_init_test_context(context) }
|
|
.events
|
|
.push("clear-prefix");
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_init_test_assert_dictionaries(
|
|
context: *mut c_void,
|
|
prefix: *const c_void,
|
|
) {
|
|
let context = unsafe { compress_stream_init_test_context(context) };
|
|
context.events.push("assert-dictionaries");
|
|
assert_eq!(prefix, context.expected_prefix);
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_init_test_set_level(params: *mut c_void, level: c_int) {
|
|
let context = unsafe { &mut *params.cast::<CompressStreamInitTestContext>() };
|
|
context.events.push("set-level");
|
|
context.compression_level = level;
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_init_test_debug(context: *mut c_void) {
|
|
unsafe { compress_stream_init_test_context(context) }
|
|
.events
|
|
.push("debug");
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_init_test_get_pledged(context: *mut c_void) -> u64 {
|
|
let context = unsafe { compress_stream_init_test_context(context) };
|
|
context.events.push("get-pledged");
|
|
context.pledged
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_init_test_set_pledged(
|
|
context: *mut c_void,
|
|
in_size: usize,
|
|
) {
|
|
let context = unsafe { compress_stream_init_test_context(context) };
|
|
context.events.push("set-pledged");
|
|
context.pledged = (in_size as u64).wrapping_add(1);
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_init_test_get_cparam_mode(
|
|
params: *mut c_void,
|
|
_cdict: *const c_void,
|
|
_pledged: u64,
|
|
) -> c_int {
|
|
unsafe { compress_stream_init_test_context(params) }
|
|
.events
|
|
.push("get-cparam-mode");
|
|
0
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_init_test_build_cparams(
|
|
params: *mut c_void,
|
|
_pledged: u64,
|
|
_dict_size: usize,
|
|
_mode: c_int,
|
|
) {
|
|
unsafe { compress_stream_init_test_context(params) }
|
|
.events
|
|
.push("build-cparams");
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_init_test_resolve_params(
|
|
params: *mut c_void,
|
|
operation: c_int,
|
|
) {
|
|
let context = unsafe { compress_stream_init_test_context(params) };
|
|
context.events.push(match operation {
|
|
ZSTD_RUST_INIT_RESOLVE_BLOCK_SPLITTER => "resolve:block-splitter",
|
|
ZSTD_RUST_INIT_RESOLVE_LDM => "resolve:ldm",
|
|
ZSTD_RUST_INIT_RESOLVE_ROW_MATCH_FINDER => "resolve:row-match-finder",
|
|
ZSTD_RUST_INIT_RESOLVE_VALIDATE_SEQUENCES => "resolve:validate-sequences",
|
|
ZSTD_RUST_INIT_RESOLVE_MAX_BLOCK_SIZE => "resolve:max-block-size",
|
|
ZSTD_RUST_INIT_RESOLVE_EXTERNAL_REPCODE_SEARCH => "resolve:external-repcodes",
|
|
_ => "resolve:unknown",
|
|
});
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_init_test_get_nb_workers(params: *mut c_void) -> c_uint {
|
|
let context = unsafe { compress_stream_init_test_context(params) };
|
|
context.events.push("get-workers");
|
|
context.nb_workers
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_init_test_set_nb_workers(
|
|
params: *mut c_void,
|
|
nb_workers: c_uint,
|
|
) {
|
|
let context = unsafe { compress_stream_init_test_context(params) };
|
|
context.events.push("set-workers");
|
|
context.nb_workers = nb_workers;
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_init_test_has_ext_seq_prod(params: *mut c_void) -> c_int {
|
|
let context = unsafe { compress_stream_init_test_context(params) };
|
|
context.events.push("has-ext-seq-prod");
|
|
context.has_ext_seq_prod
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_init_test_trace(context: *mut c_void) {
|
|
unsafe { compress_stream_init_test_context(context) }
|
|
.events
|
|
.push("trace");
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_init_test_get_mt_context(
|
|
context: *mut c_void,
|
|
) -> *mut c_void {
|
|
let context = unsafe { compress_stream_init_test_context(context) };
|
|
context.events.push("get-mt-context");
|
|
context.mt_context
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_init_test_create_mt_context(
|
|
context: *mut c_void,
|
|
_nb_workers: c_uint,
|
|
) -> usize {
|
|
let context = unsafe { compress_stream_init_test_context(context) };
|
|
context.events.push("create-mt-context");
|
|
if !ERR_isError(context.create_result) {
|
|
context.mt_context = ptr::dangling_mut::<c_void>();
|
|
}
|
|
context.create_result
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_init_test_mt(
|
|
context: *mut c_void,
|
|
_mt_context: *mut c_void,
|
|
_dictionaries: *const ZSTD_rust_compressStreamInitDictionaryState,
|
|
_params: *mut c_void,
|
|
_pledged: u64,
|
|
) -> usize {
|
|
let context = unsafe { compress_stream_init_test_context(context) };
|
|
context.events.push("init-mt");
|
|
context.mt_result
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_init_test_commit_mt(
|
|
context: *mut c_void,
|
|
_dictionaries: *const ZSTD_rust_compressStreamInitDictionaryState,
|
|
_params: *mut c_void,
|
|
) {
|
|
unsafe { compress_stream_init_test_context(context) }
|
|
.events
|
|
.push("commit-mt");
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_init_test_check_cparams(params: *mut c_void) {
|
|
unsafe { compress_stream_init_test_context(params) }
|
|
.events
|
|
.push("check-cparams");
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_init_test_begin(
|
|
context: *mut c_void,
|
|
_dictionaries: *const ZSTD_rust_compressStreamInitDictionaryState,
|
|
_params: *mut c_void,
|
|
_pledged: u64,
|
|
) -> usize {
|
|
let context = unsafe { compress_stream_init_test_context(context) };
|
|
context.events.push("compress-begin");
|
|
context.begin_result
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_init_test_assert_ordinary(context: *mut c_void) {
|
|
unsafe { compress_stream_init_test_context(context) }
|
|
.events
|
|
.push("assert-ordinary");
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_init_test_get_buffer_mode(context: *mut c_void) -> c_int {
|
|
let context = unsafe { compress_stream_init_test_context(context) };
|
|
context.events.push("get-buffer-mode");
|
|
context.buffer_mode
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_init_test_get_block_size(context: *mut c_void) -> usize {
|
|
let context = unsafe { compress_stream_init_test_context(context) };
|
|
context.events.push("get-block-size");
|
|
context.block_size
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_init_test_commit_ordinary(
|
|
context: *mut c_void,
|
|
in_buff_target: usize,
|
|
) {
|
|
let context = unsafe { compress_stream_init_test_context(context) };
|
|
context.events.push("commit-ordinary");
|
|
context.ordinary_target = in_buff_target;
|
|
}
|
|
|
|
fn compress_stream_init_test_state(
|
|
context: &mut CompressStreamInitTestContext,
|
|
dictionaries: &mut ZSTD_rust_compressStreamInitDictionaryState,
|
|
end_op: c_int,
|
|
in_size: usize,
|
|
multithreaded: c_int,
|
|
) -> ZSTD_rust_compressStreamInitState {
|
|
ZSTD_rust_compressStreamInitState {
|
|
callback_context: (context as *mut CompressStreamInitTestContext).cast(),
|
|
params: (context as *mut CompressStreamInitTestContext).cast(),
|
|
dictionaries,
|
|
end_op,
|
|
in_size,
|
|
multithreaded,
|
|
mt_job_size_min: 10,
|
|
get_local_dict: compress_stream_init_test_get_local_dict,
|
|
create_local_dict: compress_stream_init_test_create_local_dict,
|
|
publish_local_dict: compress_stream_init_test_publish_local_dict,
|
|
refresh_cdict: compress_stream_init_test_refresh_cdict,
|
|
clear_prefix: compress_stream_init_test_clear_prefix,
|
|
assert_dictionaries: compress_stream_init_test_assert_dictionaries,
|
|
set_compression_level: compress_stream_init_test_set_level,
|
|
debug_init: compress_stream_init_test_debug,
|
|
get_pledged_src_size_plus_one: compress_stream_init_test_get_pledged,
|
|
set_pledged_src_size: compress_stream_init_test_set_pledged,
|
|
get_cparam_mode: compress_stream_init_test_get_cparam_mode,
|
|
build_cparams: compress_stream_init_test_build_cparams,
|
|
resolve_params: compress_stream_init_test_resolve_params,
|
|
get_nb_workers: compress_stream_init_test_get_nb_workers,
|
|
set_nb_workers: compress_stream_init_test_set_nb_workers,
|
|
has_ext_seq_prod: compress_stream_init_test_has_ext_seq_prod,
|
|
trace_begin: compress_stream_init_test_trace,
|
|
get_mt_context: compress_stream_init_test_get_mt_context,
|
|
create_mt_context: compress_stream_init_test_create_mt_context,
|
|
init_mt: compress_stream_init_test_mt,
|
|
commit_mt: compress_stream_init_test_commit_mt,
|
|
check_cparams: compress_stream_init_test_check_cparams,
|
|
compress_begin: compress_stream_init_test_begin,
|
|
assert_ordinary: compress_stream_init_test_assert_ordinary,
|
|
get_buffer_mode: compress_stream_init_test_get_buffer_mode,
|
|
get_block_size: compress_stream_init_test_get_block_size,
|
|
commit_ordinary: compress_stream_init_test_commit_ordinary,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn compress_stream_init_preserves_ordinary_callback_order_and_policy() {
|
|
let mut context = CompressStreamInitTestContext {
|
|
pledged: 0,
|
|
cdict: ptr::dangling::<c_void>(),
|
|
cdict_compression_level: 17,
|
|
cdict_dict_content_size: 3,
|
|
nb_workers: 0,
|
|
buffer_mode: ZSTD_BM_BUFFERED,
|
|
block_size: 4,
|
|
..CompressStreamInitTestContext::default()
|
|
};
|
|
let mut dictionaries = ZSTD_rust_compressStreamInitDictionaryState {
|
|
prefix_dict: ptr::null(),
|
|
prefix_dict_size: 0,
|
|
prefix_dict_content_type: 0,
|
|
cdict: ptr::null(),
|
|
cdict_is_local: 0,
|
|
cdict_compression_level: 0,
|
|
cdict_dict_content_size: 0,
|
|
};
|
|
let state =
|
|
compress_stream_init_test_state(&mut context, &mut dictionaries, ZSTD_E_END, 4, 0);
|
|
|
|
let result = unsafe { ZSTD_rust_compressStreamInit(&state) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(context.compression_level, 17);
|
|
assert_eq!(context.pledged, 5);
|
|
assert_eq!(context.ordinary_target, 5);
|
|
assert_eq!(
|
|
context.events,
|
|
[
|
|
"local-dict",
|
|
"refresh-cdict",
|
|
"clear-prefix",
|
|
"assert-dictionaries",
|
|
"set-level",
|
|
"debug",
|
|
"set-pledged",
|
|
"get-pledged",
|
|
"get-cparam-mode",
|
|
"build-cparams",
|
|
"resolve:block-splitter",
|
|
"resolve:ldm",
|
|
"resolve:row-match-finder",
|
|
"resolve:validate-sequences",
|
|
"resolve:max-block-size",
|
|
"resolve:external-repcodes",
|
|
"check-cparams",
|
|
"compress-begin",
|
|
"assert-ordinary",
|
|
"get-buffer-mode",
|
|
"get-block-size",
|
|
"commit-ordinary",
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn compress_stream_init_orchestrates_local_dictionary_creation_and_publish() {
|
|
let mut context = CompressStreamInitTestContext {
|
|
local_dict: ptr::dangling::<c_void>(),
|
|
local_dict_size: 3,
|
|
created_local_cdict: ptr::dangling_mut::<c_void>(),
|
|
cdict_is_local: 1,
|
|
cdict_dict_content_size: 3,
|
|
buffer_mode: ZSTD_BM_BUFFERED,
|
|
block_size: 4,
|
|
..CompressStreamInitTestContext::default()
|
|
};
|
|
let mut dictionaries = ZSTD_rust_compressStreamInitDictionaryState {
|
|
prefix_dict: ptr::null(),
|
|
prefix_dict_size: 0,
|
|
prefix_dict_content_type: 0,
|
|
cdict: ptr::null(),
|
|
cdict_is_local: 0,
|
|
cdict_compression_level: 0,
|
|
cdict_dict_content_size: 0,
|
|
};
|
|
let state =
|
|
compress_stream_init_test_state(&mut context, &mut dictionaries, ZSTD_E_CONTINUE, 0, 0);
|
|
|
|
let result = unsafe { ZSTD_rust_compressStreamInit(&state) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(context.local_cdict, context.created_local_cdict);
|
|
assert_eq!(
|
|
context.context_cdict,
|
|
context.created_local_cdict.cast_const()
|
|
);
|
|
assert_eq!(
|
|
&context.events[..3],
|
|
["local-dict", "create-local-dict", "publish-local-dict"]
|
|
);
|
|
assert_eq!(context.events[3], "refresh-cdict");
|
|
assert_eq!(dictionaries.cdict, context.created_local_cdict.cast_const());
|
|
assert_eq!(dictionaries.cdict_is_local, 1);
|
|
}
|
|
|
|
#[test]
|
|
fn compress_stream_init_stops_before_later_callbacks_on_error() {
|
|
let mut context = CompressStreamInitTestContext {
|
|
local_dict: ptr::dangling::<c_void>(),
|
|
local_dict_size: 3,
|
|
local_create_result: ERROR(ZstdErrorCode::MemoryAllocation),
|
|
..CompressStreamInitTestContext::default()
|
|
};
|
|
let mut dictionaries = ZSTD_rust_compressStreamInitDictionaryState {
|
|
prefix_dict: ptr::null(),
|
|
prefix_dict_size: 0,
|
|
prefix_dict_content_type: 0,
|
|
cdict: ptr::null(),
|
|
cdict_is_local: 0,
|
|
cdict_compression_level: 0,
|
|
cdict_dict_content_size: 0,
|
|
};
|
|
let state =
|
|
compress_stream_init_test_state(&mut context, &mut dictionaries, ZSTD_E_CONTINUE, 0, 0);
|
|
|
|
let result = unsafe { ZSTD_rust_compressStreamInit(&state) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::MemoryAllocation));
|
|
assert_eq!(context.events, ["local-dict", "create-local-dict"]);
|
|
}
|
|
|
|
#[test]
|
|
fn compress_stream_init_preserves_mt_branch_and_init_error_order() {
|
|
let mut context = CompressStreamInitTestContext {
|
|
cdict: ptr::dangling::<c_void>(),
|
|
cdict_dict_content_size: 3,
|
|
nb_workers: 2,
|
|
mt_result: ERROR(ZstdErrorCode::MemoryAllocation),
|
|
..CompressStreamInitTestContext::default()
|
|
};
|
|
let mut dictionaries = ZSTD_rust_compressStreamInitDictionaryState {
|
|
prefix_dict: ptr::null(),
|
|
prefix_dict_size: 0,
|
|
prefix_dict_content_type: 0,
|
|
cdict: ptr::null(),
|
|
cdict_is_local: 0,
|
|
cdict_compression_level: 0,
|
|
cdict_dict_content_size: 0,
|
|
};
|
|
let state =
|
|
compress_stream_init_test_state(&mut context, &mut dictionaries, ZSTD_E_END, 99, 1);
|
|
|
|
let result = unsafe { ZSTD_rust_compressStreamInit(&state) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::MemoryAllocation));
|
|
assert_eq!(
|
|
context.events,
|
|
[
|
|
"local-dict",
|
|
"refresh-cdict",
|
|
"clear-prefix",
|
|
"assert-dictionaries",
|
|
"set-level",
|
|
"debug",
|
|
"set-pledged",
|
|
"get-pledged",
|
|
"get-cparam-mode",
|
|
"build-cparams",
|
|
"resolve:block-splitter",
|
|
"resolve:ldm",
|
|
"resolve:row-match-finder",
|
|
"resolve:validate-sequences",
|
|
"resolve:max-block-size",
|
|
"resolve:external-repcodes",
|
|
"has-ext-seq-prod",
|
|
"get-workers",
|
|
"trace",
|
|
"get-mt-context",
|
|
"create-mt-context",
|
|
"get-mt-context",
|
|
"init-mt",
|
|
]
|
|
);
|
|
}
|
|
|
|
fn system_round_trip(compressed: &[u8]) -> Option<Vec<u8>> {
|
|
let mut child = Command::new("zstd")
|
|
.args(["-q", "-d", "-c"])
|
|
.stdin(Stdio::piped())
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::piped())
|
|
.spawn()
|
|
.ok()?;
|
|
child.stdin.take()?.write_all(compressed).ok()?;
|
|
let output = child.wait_with_output().ok()?;
|
|
if !output.status.success() {
|
|
panic!(
|
|
"system zstd rejected Rust output: {}",
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
}
|
|
Some(output.stdout)
|
|
}
|
|
|
|
fn compress_input(input: &[u8], level: c_int) -> Vec<u8> {
|
|
let capacity = crate::zstd_compress_api::ZSTD_compressBound(input.len());
|
|
assert!(!ERR_isError(capacity));
|
|
let mut output = vec![0u8; capacity];
|
|
let written = unsafe {
|
|
ZSTD_compress(
|
|
output.as_mut_ptr().cast(),
|
|
output.len(),
|
|
input.as_ptr().cast(),
|
|
input.len(),
|
|
level,
|
|
)
|
|
};
|
|
assert!(!ERR_isError(written));
|
|
output.truncate(written);
|
|
output
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct FrameChunkTestContext {
|
|
prepare_calls: usize,
|
|
prepared_sizes: [usize; 4],
|
|
prepare_order: [u8; 16],
|
|
prepare_order_len: usize,
|
|
prepare_max_dist: c_uint,
|
|
prepare_state: Option<ZSTD_rust_frameChunkPrepareState>,
|
|
clamp_state: Option<ZSTD_rust_frameChunkClampState>,
|
|
clamp_next_to_update: c_uint,
|
|
clamp_low_limit: c_uint,
|
|
target_calls: usize,
|
|
split_calls: usize,
|
|
internal_calls: usize,
|
|
last_blocks: [c_uint; 4],
|
|
checksum_state: Option<XXH64_state_t>,
|
|
target_result: usize,
|
|
split_result: usize,
|
|
internal_result: usize,
|
|
}
|
|
|
|
unsafe fn frame_chunk_test_context(context: *mut c_void) -> &'static mut FrameChunkTestContext {
|
|
unsafe { &mut *context.cast::<FrameChunkTestContext>() }
|
|
}
|
|
|
|
unsafe fn frame_chunk_test_record_prepare(context: *mut c_void, step: u8, block_size: usize) {
|
|
let context = unsafe { frame_chunk_test_context(context) };
|
|
if context.prepare_order_len < context.prepare_order.len() {
|
|
context.prepare_order[context.prepare_order_len] = step;
|
|
}
|
|
context.prepare_order_len += 1;
|
|
if step == 1 {
|
|
if context.prepare_calls < context.prepared_sizes.len() {
|
|
context.prepared_sizes[context.prepare_calls] = block_size;
|
|
}
|
|
context.prepare_calls += 1;
|
|
}
|
|
}
|
|
|
|
unsafe extern "C" fn frame_chunk_test_correct_overflow(
|
|
context: *mut c_void,
|
|
_src: *const c_void,
|
|
block_size: usize,
|
|
) {
|
|
unsafe { frame_chunk_test_record_prepare(context, 1, block_size) };
|
|
}
|
|
|
|
unsafe extern "C" fn frame_chunk_test_check_dict_validity(
|
|
context: *mut c_void,
|
|
_src: *const c_void,
|
|
block_size: usize,
|
|
max_dist: c_uint,
|
|
) {
|
|
unsafe { frame_chunk_test_record_prepare(context, 2, block_size) };
|
|
unsafe { frame_chunk_test_context(context) }.prepare_max_dist = max_dist;
|
|
}
|
|
|
|
unsafe extern "C" fn frame_chunk_test_enforce_max_dist(
|
|
context: *mut c_void,
|
|
_src: *const c_void,
|
|
block_size: usize,
|
|
max_dist: c_uint,
|
|
) {
|
|
unsafe { frame_chunk_test_record_prepare(context, 3, block_size) };
|
|
unsafe { frame_chunk_test_context(context) }.prepare_max_dist = max_dist;
|
|
}
|
|
|
|
unsafe extern "C" fn frame_chunk_test_target(
|
|
context: *mut c_void,
|
|
_dst: *mut c_void,
|
|
_dst_capacity: usize,
|
|
_src: *const c_void,
|
|
_src_size: usize,
|
|
last_block: c_uint,
|
|
) -> usize {
|
|
let context = unsafe { frame_chunk_test_context(context) };
|
|
if context.target_calls < context.last_blocks.len() {
|
|
context.last_blocks[context.target_calls] = last_block;
|
|
}
|
|
context.target_calls += 1;
|
|
context.target_result
|
|
}
|
|
|
|
unsafe extern "C" fn frame_chunk_test_split(
|
|
context: *mut c_void,
|
|
_dst: *mut c_void,
|
|
_dst_capacity: usize,
|
|
_src: *const c_void,
|
|
_src_size: usize,
|
|
last_block: c_uint,
|
|
) -> usize {
|
|
let context = unsafe { frame_chunk_test_context(context) };
|
|
if context.split_calls < context.last_blocks.len() {
|
|
context.last_blocks[context.split_calls] = last_block;
|
|
}
|
|
context.split_calls += 1;
|
|
context.split_result
|
|
}
|
|
|
|
unsafe extern "C" fn frame_chunk_test_internal(
|
|
context: *mut c_void,
|
|
_dst: *mut c_void,
|
|
_dst_capacity: usize,
|
|
_src: *const c_void,
|
|
_src_size: usize,
|
|
last_block: c_uint,
|
|
) -> usize {
|
|
let context = unsafe { frame_chunk_test_context(context) };
|
|
if context.internal_calls < context.last_blocks.len() {
|
|
context.last_blocks[context.internal_calls] = last_block;
|
|
}
|
|
context.internal_calls += 1;
|
|
context.internal_result
|
|
}
|
|
|
|
fn frame_chunk_test_state(
|
|
context: &mut FrameChunkTestContext,
|
|
is_first_block: &mut c_int,
|
|
stage: &mut c_int,
|
|
use_target_c_block_size: c_int,
|
|
block_splitter_enabled: c_int,
|
|
checksum_flag: c_int,
|
|
) -> ZSTD_rust_frameChunkState {
|
|
let context_ptr = context as *mut FrameChunkTestContext;
|
|
let callback_context = context_ptr.cast::<c_void>();
|
|
context.checksum_state = Some(XXH64_state_t {
|
|
total_len: 0,
|
|
v: [0; 4],
|
|
mem64: [0; 4],
|
|
memsize: 0,
|
|
reserved32: 0,
|
|
reserved64: 0,
|
|
});
|
|
let checksum_state = context.checksum_state.as_mut().unwrap();
|
|
unsafe {
|
|
let _ = XXH64_reset(checksum_state, 0);
|
|
}
|
|
unsafe {
|
|
(*context_ptr).clamp_state = Some(ZSTD_rust_frameChunkClampState {
|
|
next_to_update: &mut (*context_ptr).clamp_next_to_update,
|
|
low_limit: &(*context_ptr).clamp_low_limit,
|
|
});
|
|
(*context_ptr).prepare_state = Some(ZSTD_rust_frameChunkPrepareState {
|
|
callback_context,
|
|
max_dist: 64,
|
|
correct_overflow: frame_chunk_test_correct_overflow,
|
|
check_dict_validity: frame_chunk_test_check_dict_validity,
|
|
enforce_max_dist: frame_chunk_test_enforce_max_dist,
|
|
clamp_state: (*context_ptr)
|
|
.clamp_state
|
|
.as_ref()
|
|
.map_or(ptr::null(), |state| state as *const _),
|
|
});
|
|
}
|
|
let prepare_state = unsafe {
|
|
(*context_ptr)
|
|
.prepare_state
|
|
.as_ref()
|
|
.map_or(ptr::null(), |state| state as *const _)
|
|
};
|
|
ZSTD_rust_frameChunkState {
|
|
callback_context,
|
|
tmp_workspace: ptr::null_mut(),
|
|
checksum_state,
|
|
is_first_block,
|
|
stage,
|
|
tmp_wksp_size: 0,
|
|
block_size_max: 4,
|
|
savings: 0,
|
|
pre_block_splitter_level: 1,
|
|
strategy: ZSTD_FAST,
|
|
use_target_c_block_size,
|
|
block_splitter_enabled,
|
|
checksum_flag,
|
|
ending_stage: 77,
|
|
prepare_state,
|
|
compress_target: Some(frame_chunk_test_target),
|
|
compress_split: Some(frame_chunk_test_split),
|
|
compress_internal: Some(frame_chunk_test_internal),
|
|
compress_internal_state: ptr::null(),
|
|
compress_target_state: ptr::null(),
|
|
compress_split_state: ptr::null(),
|
|
derive_block_splits_state: ptr::null(),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn frame_chunk_internal_path_emits_blocks_and_updates_state_once() {
|
|
let mut context = FrameChunkTestContext {
|
|
internal_result: 2,
|
|
clamp_next_to_update: 1,
|
|
clamp_low_limit: 4,
|
|
..FrameChunkTestContext::default()
|
|
};
|
|
let mut is_first_block = 1;
|
|
let mut stage = 0;
|
|
let state = frame_chunk_test_state(&mut context, &mut is_first_block, &mut stage, 0, 0, 1);
|
|
let source = [0x11u8; 8];
|
|
let mut output = [0xa5u8; 16];
|
|
|
|
let result = unsafe {
|
|
compress_frame_chunk_body_with(
|
|
&state,
|
|
output.as_mut_ptr().cast(),
|
|
output.len(),
|
|
source.as_ptr().cast(),
|
|
source.len(),
|
|
1,
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, 10);
|
|
assert_eq!(context.prepare_calls, 2);
|
|
assert_eq!(context.prepared_sizes[..2], [4, 4]);
|
|
assert_eq!(context.prepare_order[..6], [1, 2, 3, 1, 2, 3]);
|
|
assert_eq!(context.prepare_max_dist, 64);
|
|
assert_eq!(context.clamp_next_to_update, 4);
|
|
assert_eq!(context.internal_calls, 2);
|
|
assert_eq!(context.last_blocks[..2], [0, 1]);
|
|
assert_eq!(
|
|
context.checksum_state.as_ref().unwrap().total_len,
|
|
source.len() as u64
|
|
);
|
|
assert_eq!(is_first_block, 0);
|
|
assert_eq!(stage, 77);
|
|
assert_ne!(output[..3], [0xa5; 3]);
|
|
assert_ne!(output[5..8], [0xa5; 3]);
|
|
}
|
|
|
|
#[test]
|
|
fn frame_chunk_dispatches_target_and_split_paths_before_internal() {
|
|
let source = [0x22u8; 4];
|
|
|
|
let mut target_context = FrameChunkTestContext {
|
|
target_result: 4,
|
|
split_result: 5,
|
|
internal_result: 6,
|
|
..FrameChunkTestContext::default()
|
|
};
|
|
let mut target_first = 1;
|
|
let mut target_stage = 0;
|
|
let target_state = frame_chunk_test_state(
|
|
&mut target_context,
|
|
&mut target_first,
|
|
&mut target_stage,
|
|
1,
|
|
1,
|
|
0,
|
|
);
|
|
let mut target_output = [0xa5u8; 8];
|
|
let target_result = unsafe {
|
|
compress_frame_chunk_body_with(
|
|
&target_state,
|
|
target_output.as_mut_ptr().cast(),
|
|
target_output.len(),
|
|
source.as_ptr().cast(),
|
|
source.len(),
|
|
0,
|
|
)
|
|
};
|
|
assert_eq!(target_result, 4);
|
|
assert_eq!(target_context.target_calls, 1);
|
|
assert_eq!(target_context.split_calls, 0);
|
|
assert_eq!(target_context.internal_calls, 0);
|
|
|
|
let mut split_context = FrameChunkTestContext {
|
|
split_result: 5,
|
|
internal_result: 6,
|
|
..FrameChunkTestContext::default()
|
|
};
|
|
let mut split_first = 1;
|
|
let mut split_stage = 0;
|
|
let split_state = frame_chunk_test_state(
|
|
&mut split_context,
|
|
&mut split_first,
|
|
&mut split_stage,
|
|
0,
|
|
1,
|
|
0,
|
|
);
|
|
let mut split_output = [0xa5u8; 8];
|
|
let split_result = unsafe {
|
|
compress_frame_chunk_body_with(
|
|
&split_state,
|
|
split_output.as_mut_ptr().cast(),
|
|
split_output.len(),
|
|
source.as_ptr().cast(),
|
|
source.len(),
|
|
0,
|
|
)
|
|
};
|
|
assert_eq!(split_result, 5);
|
|
assert_eq!(split_context.target_calls, 0);
|
|
assert_eq!(split_context.split_calls, 1);
|
|
assert_eq!(split_context.internal_calls, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn frame_chunk_keeps_state_unchanged_on_early_capacity_error() {
|
|
let mut context = FrameChunkTestContext::default();
|
|
let mut is_first_block = 1;
|
|
let mut stage = 23;
|
|
let state = frame_chunk_test_state(&mut context, &mut is_first_block, &mut stage, 0, 0, 1);
|
|
let source = [0x33u8; 4];
|
|
let mut output = [0xa5u8; 5];
|
|
|
|
let result = unsafe {
|
|
compress_frame_chunk_body_with(
|
|
&state,
|
|
output.as_mut_ptr().cast(),
|
|
output.len(),
|
|
source.as_ptr().cast(),
|
|
source.len(),
|
|
1,
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::DstSizeTooSmall));
|
|
assert_eq!(
|
|
context.checksum_state.as_ref().unwrap().total_len,
|
|
source.len() as u64
|
|
);
|
|
assert_eq!(context.prepare_calls, 0);
|
|
assert_eq!(context.internal_calls, 0);
|
|
assert_eq!(is_first_block, 1);
|
|
assert_eq!(stage, 23);
|
|
assert_eq!(output, [0xa5; 5]);
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct CompressContinueTestContext {
|
|
overflow_calls: usize,
|
|
frame_calls: usize,
|
|
block_calls: usize,
|
|
last_frame_chunk: c_uint,
|
|
frame_result: usize,
|
|
block_result: usize,
|
|
window: ZSTD_rust_windowUpdateState,
|
|
ldm_window: ZSTD_rust_windowUpdateState,
|
|
force_non_contiguous: c_int,
|
|
next_to_update: c_uint,
|
|
loaded_dict_end: c_uint,
|
|
dict_match_state: *const c_void,
|
|
window_projection: ZSTD_rust_compressContinueWindowProjection,
|
|
ldm_window_projection: ZSTD_rust_compressContinueWindowProjection,
|
|
overflow_state: Option<ZSTD_rust_overflowCorrectState>,
|
|
is_first_block: c_int,
|
|
frame_chunk_clamp_state: Option<ZSTD_rust_frameChunkClampState>,
|
|
frame_chunk_prepare_state: Option<ZSTD_rust_frameChunkPrepareState>,
|
|
frame_chunk_state: Option<ZSTD_rust_frameChunkState>,
|
|
}
|
|
|
|
unsafe fn compress_continue_test_context(
|
|
context: *mut c_void,
|
|
) -> &'static mut CompressContinueTestContext {
|
|
unsafe { &mut *context.cast::<CompressContinueTestContext>() }
|
|
}
|
|
|
|
unsafe extern "C" fn compress_continue_test_overflow_need(
|
|
context: *mut c_void,
|
|
_src: *const c_void,
|
|
_src_end: *const c_void,
|
|
) -> c_int {
|
|
let context = unsafe { compress_continue_test_context(context) };
|
|
context.overflow_calls += 1;
|
|
0
|
|
}
|
|
|
|
unsafe extern "C" fn compress_continue_test_overflow_correct(
|
|
_context: *mut c_void,
|
|
_src: *const c_void,
|
|
) -> c_uint {
|
|
0
|
|
}
|
|
|
|
unsafe extern "C" fn compress_continue_test_overflow_callback(_context: *mut c_void) {}
|
|
|
|
unsafe extern "C" fn compress_continue_test_overflow_reduce(
|
|
_context: *mut c_void,
|
|
_correction: c_uint,
|
|
) {
|
|
}
|
|
|
|
unsafe extern "C" fn compress_continue_test_frame_prepare_overflow(
|
|
_context: *mut c_void,
|
|
_src: *const c_void,
|
|
_block_size: usize,
|
|
) {
|
|
}
|
|
|
|
unsafe extern "C" fn compress_continue_test_frame_prepare_window(
|
|
_context: *mut c_void,
|
|
_src: *const c_void,
|
|
_block_size: usize,
|
|
_max_dist: c_uint,
|
|
) {
|
|
}
|
|
|
|
unsafe extern "C" fn compress_continue_test_frame(
|
|
context: *mut c_void,
|
|
_dst: *mut c_void,
|
|
_dst_capacity: usize,
|
|
_src: *const c_void,
|
|
_src_size: usize,
|
|
last_frame_chunk: c_uint,
|
|
) -> usize {
|
|
let context = unsafe { compress_continue_test_context(context) };
|
|
context.frame_calls += 1;
|
|
context.last_frame_chunk = last_frame_chunk;
|
|
context.frame_result
|
|
}
|
|
|
|
unsafe extern "C" fn compress_continue_test_block(
|
|
context: *mut c_void,
|
|
_dst: *mut c_void,
|
|
_dst_capacity: usize,
|
|
_src: *const c_void,
|
|
_src_size: usize,
|
|
_last_frame_chunk: c_uint,
|
|
) -> usize {
|
|
let context = unsafe { compress_continue_test_context(context) };
|
|
context.block_calls += 1;
|
|
context.block_result
|
|
}
|
|
|
|
fn compress_continue_test_state(
|
|
context: &mut CompressContinueTestContext,
|
|
stage: &mut c_int,
|
|
consumed_src_size: &mut u64,
|
|
produced_c_size: &mut u64,
|
|
pledged_src_size_plus_one: u64,
|
|
block_size_max: usize,
|
|
check_block_size: c_int,
|
|
) -> ZSTD_rust_compressContinueState {
|
|
let base = WINDOW_INIT_SENTINEL.as_ptr().cast::<c_void>();
|
|
let next_src = unsafe { base.cast::<u8>().add(2).cast::<c_void>() };
|
|
context.window = ZSTD_rust_windowUpdateState {
|
|
nextSrc: next_src,
|
|
base,
|
|
dictBase: base,
|
|
dictLimit: ZSTD_WINDOW_START_INDEX,
|
|
lowLimit: ZSTD_WINDOW_START_INDEX,
|
|
};
|
|
context.ldm_window = context.window;
|
|
context.force_non_contiguous = 0;
|
|
context.next_to_update = ZSTD_WINDOW_START_INDEX;
|
|
context.loaded_dict_end = 0;
|
|
context.dict_match_state = ptr::null();
|
|
context.is_first_block = 1;
|
|
context.window_projection = ZSTD_rust_compressContinueWindowProjection {
|
|
next_src: ptr::addr_of_mut!(context.window.nextSrc),
|
|
base: ptr::addr_of_mut!(context.window.base),
|
|
dict_base: ptr::addr_of_mut!(context.window.dictBase),
|
|
dict_limit: ptr::addr_of_mut!(context.window.dictLimit),
|
|
low_limit: ptr::addr_of_mut!(context.window.lowLimit),
|
|
force_non_contiguous: ptr::addr_of_mut!(context.force_non_contiguous),
|
|
next_to_update: ptr::addr_of_mut!(context.next_to_update),
|
|
};
|
|
context.ldm_window_projection = ZSTD_rust_compressContinueWindowProjection {
|
|
next_src: ptr::addr_of_mut!(context.ldm_window.nextSrc),
|
|
base: ptr::addr_of_mut!(context.ldm_window.base),
|
|
dict_base: ptr::addr_of_mut!(context.ldm_window.dictBase),
|
|
dict_limit: ptr::addr_of_mut!(context.ldm_window.dictLimit),
|
|
low_limit: ptr::addr_of_mut!(context.ldm_window.lowLimit),
|
|
force_non_contiguous: ptr::null_mut(),
|
|
next_to_update: ptr::null_mut(),
|
|
};
|
|
let callback_context = (context as *mut CompressContinueTestContext).cast();
|
|
context.overflow_state = Some(ZSTD_rust_overflowCorrectState {
|
|
callback_context,
|
|
next_to_update: ptr::addr_of_mut!(context.next_to_update),
|
|
need_correction: Some(compress_continue_test_overflow_need),
|
|
correct_overflow: Some(compress_continue_test_overflow_correct),
|
|
mark_tables_dirty: Some(compress_continue_test_overflow_callback),
|
|
reduce_index: Some(compress_continue_test_overflow_reduce),
|
|
mark_tables_clean: Some(compress_continue_test_overflow_callback),
|
|
loaded_dict_end: ptr::addr_of_mut!(context.loaded_dict_end),
|
|
dict_match_state: ptr::addr_of_mut!(context.dict_match_state),
|
|
});
|
|
let overflow_state = context
|
|
.overflow_state
|
|
.as_ref()
|
|
.map_or(ptr::null(), |state| state as *const _);
|
|
context.frame_chunk_clamp_state = Some(ZSTD_rust_frameChunkClampState {
|
|
next_to_update: ptr::addr_of_mut!(context.next_to_update),
|
|
low_limit: ptr::addr_of!(context.window.lowLimit),
|
|
});
|
|
let frame_chunk_clamp_state = context
|
|
.frame_chunk_clamp_state
|
|
.as_ref()
|
|
.map_or(ptr::null(), |state| state as *const _);
|
|
context.frame_chunk_prepare_state = Some(ZSTD_rust_frameChunkPrepareState {
|
|
callback_context,
|
|
max_dist: 64,
|
|
correct_overflow: compress_continue_test_frame_prepare_overflow,
|
|
check_dict_validity: compress_continue_test_frame_prepare_window,
|
|
enforce_max_dist: compress_continue_test_frame_prepare_window,
|
|
clamp_state: frame_chunk_clamp_state,
|
|
});
|
|
let frame_chunk_prepare_state = context
|
|
.frame_chunk_prepare_state
|
|
.as_ref()
|
|
.map_or(ptr::null(), |state| state as *const _);
|
|
context.frame_chunk_state = Some(ZSTD_rust_frameChunkState {
|
|
callback_context,
|
|
tmp_workspace: ptr::null_mut(),
|
|
checksum_state: ptr::null_mut(),
|
|
is_first_block: ptr::addr_of_mut!(context.is_first_block),
|
|
stage,
|
|
tmp_wksp_size: 0,
|
|
block_size_max,
|
|
savings: 0,
|
|
pre_block_splitter_level: 1,
|
|
strategy: ZSTD_FAST,
|
|
use_target_c_block_size: 1,
|
|
block_splitter_enabled: 0,
|
|
checksum_flag: 0,
|
|
ending_stage: ZSTD_COMPRESSION_STAGE_ENDING,
|
|
prepare_state: frame_chunk_prepare_state,
|
|
compress_target: Some(compress_continue_test_frame),
|
|
compress_split: Some(compress_continue_test_frame),
|
|
compress_internal: Some(compress_continue_test_frame),
|
|
compress_internal_state: ptr::null(),
|
|
compress_target_state: ptr::null(),
|
|
compress_split_state: ptr::null(),
|
|
derive_block_splits_state: ptr::null(),
|
|
});
|
|
let frame_chunk_state = context
|
|
.frame_chunk_state
|
|
.as_ref()
|
|
.map_or(ptr::null(), |state| state as *const _);
|
|
ZSTD_rust_compressContinueState {
|
|
callback_context,
|
|
window_state: &context.window_projection,
|
|
ldm_window_state: &context.ldm_window_projection,
|
|
overflow_state,
|
|
frame_chunk_state,
|
|
compress_block: compress_continue_test_block,
|
|
stage,
|
|
consumed_src_size,
|
|
produced_c_size,
|
|
pledged_src_size_plus_one,
|
|
block_size_max,
|
|
check_block_size,
|
|
no_dict_id_flag: 0,
|
|
checksum_flag: 0,
|
|
content_size_flag: 0,
|
|
format: 0,
|
|
window_log: 10,
|
|
dict_id: 0,
|
|
ldm_enabled: 1,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn compress_continue_block_builds_small_block_through_projected_states() {
|
|
let mut probe = BuildSeqStoreSelectProbe::default();
|
|
let mut seq_store = unsafe { MaybeUninit::<SeqStore_t>::zeroed().assume_init() };
|
|
let mut build_state = build_seq_store_select_test_state(&mut probe, 0, 0, 0, 0);
|
|
build_state.seq_store = &mut seq_store;
|
|
|
|
let mut prev_block = zeroed_state();
|
|
let mut next_block = zeroed_state();
|
|
let mut prev_c_block = &mut prev_block as *mut _;
|
|
let mut next_c_block = &mut next_block as *mut _;
|
|
let mut seq_collector = SeqCollector {
|
|
collectSequences: 0,
|
|
seqStart: ptr::null_mut(),
|
|
seqIndex: 0,
|
|
maxSequences: 0,
|
|
};
|
|
let block_state = ZSTD_rust_blockInternalState {
|
|
seq_store: &seq_store,
|
|
prev_c_block: &mut prev_c_block,
|
|
next_c_block: &mut next_c_block,
|
|
tmp_workspace: ptr::null_mut(),
|
|
tmp_wksp_size: 0,
|
|
seq_collector: &mut seq_collector,
|
|
strategy: 0,
|
|
disable_literal_compression: 0,
|
|
bmi2: 0,
|
|
is_first_block: 1,
|
|
};
|
|
let state = ZSTD_rust_compressContinueBlockState {
|
|
build_seq_store_state: &build_state,
|
|
block_internal_state: &block_state,
|
|
};
|
|
let source = [0u8; 1];
|
|
let mut output = [0xa5u8; 8];
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_compressContinueBlock(
|
|
(&state as *const ZSTD_rust_compressContinueBlockState)
|
|
.cast_mut()
|
|
.cast(),
|
|
output.as_mut_ptr().cast(),
|
|
output.len(),
|
|
source.as_ptr().cast(),
|
|
source.len(),
|
|
0,
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(output, [0xa5; 8]);
|
|
}
|
|
|
|
#[test]
|
|
fn compress_continue_starts_frame_and_updates_progression() {
|
|
let mut context = CompressContinueTestContext {
|
|
frame_result: 6,
|
|
..CompressContinueTestContext::default()
|
|
};
|
|
let mut stage = ZSTD_COMPRESSION_STAGE_INIT;
|
|
let mut consumed = 7;
|
|
let mut produced = 11;
|
|
let state = compress_continue_test_state(
|
|
&mut context,
|
|
&mut stage,
|
|
&mut consumed,
|
|
&mut produced,
|
|
0,
|
|
16,
|
|
0,
|
|
);
|
|
let source = [0x11u8; 5];
|
|
let mut output = [0xa5u8; 32];
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_compressContinue(
|
|
&state,
|
|
output.as_mut_ptr().cast(),
|
|
output.len(),
|
|
source.as_ptr().cast(),
|
|
source.len(),
|
|
1,
|
|
1,
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, 12);
|
|
assert_eq!(stage, ZSTD_COMPRESSION_STAGE_ENDING);
|
|
assert_eq!(consumed, 12);
|
|
assert_eq!(produced, 23);
|
|
assert_eq!(context.window.nextSrc, unsafe {
|
|
source.as_ptr().add(source.len()).cast()
|
|
});
|
|
assert_eq!(context.ldm_window.nextSrc, context.window.nextSrc);
|
|
assert_eq!(context.force_non_contiguous, 0);
|
|
assert_eq!(context.next_to_update, context.window.dictLimit);
|
|
assert_eq!(context.overflow_calls, 0);
|
|
assert_eq!(context.frame_calls, 1);
|
|
assert_eq!(context.block_calls, 0);
|
|
assert_eq!(context.last_frame_chunk, 1);
|
|
assert_eq!(context.is_first_block, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn compress_continue_empty_frame_only_writes_header() {
|
|
let mut context = CompressContinueTestContext::default();
|
|
let mut stage = ZSTD_COMPRESSION_STAGE_INIT;
|
|
let mut consumed = 7;
|
|
let mut produced = 11;
|
|
let state = compress_continue_test_state(
|
|
&mut context,
|
|
&mut stage,
|
|
&mut consumed,
|
|
&mut produced,
|
|
0,
|
|
16,
|
|
0,
|
|
);
|
|
let mut output = [0xa5u8; 18];
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_compressContinue(
|
|
&state,
|
|
output.as_mut_ptr().cast(),
|
|
output.len(),
|
|
ptr::null(),
|
|
0,
|
|
1,
|
|
0,
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, 6);
|
|
assert_eq!(stage, ZSTD_COMPRESSION_STAGE_ONGOING);
|
|
assert_eq!((consumed, produced), (7, 11));
|
|
assert_eq!(context.frame_calls, 0);
|
|
assert_eq!(&output[..6], &[0x28, 0xb5, 0x2f, 0xfd, 0x00, 0x00]);
|
|
assert_eq!(&output[6..], &[0xa5; 12]);
|
|
}
|
|
|
|
#[test]
|
|
fn compress_continue_rejects_invalid_stage_and_oversized_block_before_callbacks() {
|
|
let mut context = CompressContinueTestContext::default();
|
|
let mut stage = ZSTD_COMPRESSION_STAGE_INIT;
|
|
let mut consumed = 4;
|
|
let mut produced = 9;
|
|
let mut state = compress_continue_test_state(
|
|
&mut context,
|
|
&mut stage,
|
|
&mut consumed,
|
|
&mut produced,
|
|
0,
|
|
4,
|
|
1,
|
|
);
|
|
let source = [0x22u8; 5];
|
|
let mut output = [0xa5u8; 16];
|
|
let initial_window = context.window;
|
|
let initial_ldm_window = context.ldm_window;
|
|
|
|
let oversized = unsafe {
|
|
ZSTD_rust_compressContinue(
|
|
&state,
|
|
output.as_mut_ptr().cast(),
|
|
output.len(),
|
|
source.as_ptr().cast(),
|
|
source.len(),
|
|
0,
|
|
0,
|
|
)
|
|
};
|
|
assert_eq!(oversized, ERROR(ZstdErrorCode::SrcSizeWrong));
|
|
assert_eq!(stage, ZSTD_COMPRESSION_STAGE_INIT);
|
|
assert_eq!((consumed, produced), (4, 9));
|
|
assert_eq!(context.window, initial_window);
|
|
assert_eq!(context.ldm_window, initial_ldm_window);
|
|
assert_eq!(context.block_calls, 0);
|
|
|
|
state.check_block_size = 0;
|
|
stage = ZSTD_COMPRESSION_STAGE_CREATED;
|
|
let created = unsafe {
|
|
ZSTD_rust_compressContinue(
|
|
&state,
|
|
output.as_mut_ptr().cast(),
|
|
output.len(),
|
|
source.as_ptr().cast(),
|
|
source.len(),
|
|
0,
|
|
0,
|
|
)
|
|
};
|
|
assert_eq!(created, ERROR(ZstdErrorCode::StageWrong));
|
|
assert_eq!(stage, ZSTD_COMPRESSION_STAGE_CREATED);
|
|
assert_eq!(context.window, initial_window);
|
|
assert_eq!(context.ldm_window, initial_ldm_window);
|
|
assert_eq!(context.block_calls, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn compress_continue_propagates_block_error_without_progression() {
|
|
let mut context = CompressContinueTestContext {
|
|
block_result: ERROR(ZstdErrorCode::DstSizeTooSmall),
|
|
..CompressContinueTestContext::default()
|
|
};
|
|
let mut stage = ZSTD_COMPRESSION_STAGE_ONGOING;
|
|
let mut consumed = 4;
|
|
let mut produced = 9;
|
|
let state = compress_continue_test_state(
|
|
&mut context,
|
|
&mut stage,
|
|
&mut consumed,
|
|
&mut produced,
|
|
0,
|
|
16,
|
|
0,
|
|
);
|
|
let source = [0x33u8; 5];
|
|
let mut output = [0xa5u8; 16];
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_compressContinue(
|
|
&state,
|
|
output.as_mut_ptr().cast(),
|
|
output.len(),
|
|
source.as_ptr().cast(),
|
|
source.len(),
|
|
0,
|
|
0,
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::DstSizeTooSmall));
|
|
assert_eq!(
|
|
(stage, consumed, produced),
|
|
(ZSTD_COMPRESSION_STAGE_ONGOING, 4, 9)
|
|
);
|
|
assert_eq!(context.window.nextSrc, unsafe {
|
|
source.as_ptr().add(source.len()).cast()
|
|
});
|
|
assert_eq!(context.ldm_window.nextSrc, context.window.nextSrc);
|
|
assert_eq!(context.overflow_calls, 1);
|
|
assert_eq!(context.block_calls, 1);
|
|
}
|
|
|
|
#[test]
|
|
fn compress_continue_reports_pledge_overrun_after_updating_counters() {
|
|
let mut context = CompressContinueTestContext {
|
|
block_result: 3,
|
|
..CompressContinueTestContext::default()
|
|
};
|
|
let mut stage = ZSTD_COMPRESSION_STAGE_ONGOING;
|
|
let mut consumed = 0;
|
|
let mut produced = 0;
|
|
let state = compress_continue_test_state(
|
|
&mut context,
|
|
&mut stage,
|
|
&mut consumed,
|
|
&mut produced,
|
|
6,
|
|
16,
|
|
0,
|
|
);
|
|
let source = [0x44u8; 6];
|
|
let mut output = [0xa5u8; 16];
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_compressContinue(
|
|
&state,
|
|
output.as_mut_ptr().cast(),
|
|
output.len(),
|
|
source.as_ptr().cast(),
|
|
source.len(),
|
|
0,
|
|
0,
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::SrcSizeWrong));
|
|
assert_eq!((consumed, produced), (6, 3));
|
|
assert_eq!(context.window.nextSrc, unsafe {
|
|
source.as_ptr().add(source.len()).cast()
|
|
});
|
|
assert_eq!(context.block_calls, 1);
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct CompressStreamTestContext {
|
|
continue_calls: usize,
|
|
end_calls: usize,
|
|
reset_calls: usize,
|
|
continue_result: usize,
|
|
end_result: usize,
|
|
reset_result: usize,
|
|
window: ZSTD_rust_windowUpdateState,
|
|
force_non_contiguous: c_int,
|
|
next_to_update: c_uint,
|
|
window_projection: ZSTD_rust_compressContinueWindowProjection,
|
|
frame_chunk_clamp_state: Option<ZSTD_rust_frameChunkClampState>,
|
|
frame_chunk_prepare_state: Option<ZSTD_rust_frameChunkPrepareState>,
|
|
continue_frame_chunk_state: Option<ZSTD_rust_frameChunkState>,
|
|
end_frame_chunk_state: Option<ZSTD_rust_frameChunkState>,
|
|
continue_state: Option<ZSTD_rust_compressContinueState>,
|
|
end_continue_state: Option<ZSTD_rust_compressContinueState>,
|
|
end_state: Option<ZSTD_rust_compressEndState>,
|
|
compression_stage: c_int,
|
|
is_first_block: c_int,
|
|
end_is_first_block: c_int,
|
|
consumed_src_size: u64,
|
|
produced_c_size: u64,
|
|
}
|
|
|
|
unsafe fn compress_stream_test_context(
|
|
context: *mut c_void,
|
|
) -> &'static mut CompressStreamTestContext {
|
|
unsafe { &mut *context.cast::<CompressStreamTestContext>() }
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_test_block(
|
|
context: *mut c_void,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
_src: *const c_void,
|
|
_src_size: usize,
|
|
_last_block: c_uint,
|
|
) -> usize {
|
|
let context = unsafe { compress_stream_test_context(context) };
|
|
context.continue_calls += 1;
|
|
if !ERR_isError(context.continue_result) && context.continue_result != 0 {
|
|
assert!(context.continue_result <= dst_capacity);
|
|
unsafe { ptr::write_bytes(dst.cast::<u8>(), 0x5a, context.continue_result) };
|
|
}
|
|
context.continue_result
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_test_end(
|
|
context: *mut c_void,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
_src: *const c_void,
|
|
_src_size: usize,
|
|
_last_block: c_uint,
|
|
) -> usize {
|
|
let context = unsafe { compress_stream_test_context(context) };
|
|
context.end_calls += 1;
|
|
if !ERR_isError(context.end_result) && context.end_result != 0 {
|
|
assert!(context.end_result <= dst_capacity);
|
|
unsafe { ptr::write_bytes(dst.cast::<u8>(), 0x3c, context.end_result) };
|
|
}
|
|
context.end_result
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_test_reset(context: *mut c_void) -> usize {
|
|
let context = unsafe { compress_stream_test_context(context) };
|
|
context.reset_calls += 1;
|
|
context.reset_result
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_test_frame_prepare_overflow(
|
|
_context: *mut c_void,
|
|
_src: *const c_void,
|
|
_block_size: usize,
|
|
) {
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_test_frame_prepare_window(
|
|
_context: *mut c_void,
|
|
_src: *const c_void,
|
|
_block_size: usize,
|
|
_max_dist: c_uint,
|
|
) {
|
|
}
|
|
|
|
unsafe extern "C" fn compress_stream_test_trace(_context: *mut c_void, _extra_c_size: usize) {}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn compress_stream_test_state(
|
|
context: &mut CompressStreamTestContext,
|
|
in_buffer_mode: c_int,
|
|
out_buffer_mode: c_int,
|
|
stage: &mut c_int,
|
|
block_size_max: usize,
|
|
stable_in_not_consumed: &mut usize,
|
|
in_buff: *mut c_void,
|
|
in_buff_size: usize,
|
|
in_to_compress: &mut usize,
|
|
in_buff_pos: &mut usize,
|
|
in_buff_target: &mut usize,
|
|
out_buff: *mut c_void,
|
|
out_buff_size: usize,
|
|
out_buff_content_size: &mut usize,
|
|
out_buff_flushed_size: &mut usize,
|
|
frame_ended: &mut c_uint,
|
|
) -> ZSTD_rust_compressStreamState {
|
|
let base = WINDOW_INIT_SENTINEL.as_ptr().cast::<c_void>();
|
|
context.window = ZSTD_rust_windowUpdateState {
|
|
nextSrc: base,
|
|
base,
|
|
dictBase: base,
|
|
dictLimit: ZSTD_WINDOW_START_INDEX,
|
|
lowLimit: ZSTD_WINDOW_START_INDEX,
|
|
};
|
|
context.force_non_contiguous = 0;
|
|
context.next_to_update = ZSTD_WINDOW_START_INDEX;
|
|
context.compression_stage = ZSTD_COMPRESSION_STAGE_ONGOING;
|
|
context.is_first_block = 1;
|
|
context.end_is_first_block = 1;
|
|
context.consumed_src_size = 0;
|
|
context.produced_c_size = 0;
|
|
context.window_projection = ZSTD_rust_compressContinueWindowProjection {
|
|
next_src: ptr::addr_of_mut!(context.window.nextSrc),
|
|
base: ptr::addr_of_mut!(context.window.base),
|
|
dict_base: ptr::addr_of_mut!(context.window.dictBase),
|
|
dict_limit: ptr::addr_of_mut!(context.window.dictLimit),
|
|
low_limit: ptr::addr_of_mut!(context.window.lowLimit),
|
|
force_non_contiguous: ptr::addr_of_mut!(context.force_non_contiguous),
|
|
next_to_update: ptr::addr_of_mut!(context.next_to_update),
|
|
};
|
|
let callback_context = (context as *mut CompressStreamTestContext).cast();
|
|
context.frame_chunk_clamp_state = Some(ZSTD_rust_frameChunkClampState {
|
|
next_to_update: ptr::addr_of_mut!(context.next_to_update),
|
|
low_limit: ptr::addr_of!(context.window.lowLimit),
|
|
});
|
|
let frame_chunk_clamp_state = context
|
|
.frame_chunk_clamp_state
|
|
.as_ref()
|
|
.map_or(ptr::null(), |state| state as *const _);
|
|
context.frame_chunk_prepare_state = Some(ZSTD_rust_frameChunkPrepareState {
|
|
callback_context,
|
|
max_dist: 64,
|
|
correct_overflow: compress_stream_test_frame_prepare_overflow,
|
|
check_dict_validity: compress_stream_test_frame_prepare_window,
|
|
enforce_max_dist: compress_stream_test_frame_prepare_window,
|
|
clamp_state: frame_chunk_clamp_state,
|
|
});
|
|
let frame_chunk_prepare_state = context
|
|
.frame_chunk_prepare_state
|
|
.as_ref()
|
|
.map_or(ptr::null(), |state| state as *const _);
|
|
context.continue_frame_chunk_state = Some(ZSTD_rust_frameChunkState {
|
|
callback_context,
|
|
tmp_workspace: ptr::null_mut(),
|
|
checksum_state: ptr::null_mut(),
|
|
is_first_block: ptr::addr_of_mut!(context.is_first_block),
|
|
stage: ptr::addr_of_mut!(context.compression_stage),
|
|
tmp_wksp_size: 0,
|
|
block_size_max,
|
|
savings: 0,
|
|
pre_block_splitter_level: 1,
|
|
strategy: ZSTD_FAST,
|
|
use_target_c_block_size: 1,
|
|
block_splitter_enabled: 0,
|
|
checksum_flag: 0,
|
|
ending_stage: ZSTD_COMPRESSION_STAGE_ENDING,
|
|
prepare_state: frame_chunk_prepare_state,
|
|
compress_target: Some(compress_stream_test_block),
|
|
compress_split: Some(compress_stream_test_block),
|
|
compress_internal: Some(compress_stream_test_block),
|
|
compress_internal_state: ptr::null(),
|
|
compress_target_state: ptr::null(),
|
|
compress_split_state: ptr::null(),
|
|
derive_block_splits_state: ptr::null(),
|
|
});
|
|
let continue_frame_chunk_state = context
|
|
.continue_frame_chunk_state
|
|
.as_ref()
|
|
.map_or(ptr::null(), |state| state as *const _);
|
|
context.end_frame_chunk_state = Some(ZSTD_rust_frameChunkState {
|
|
callback_context,
|
|
tmp_workspace: ptr::null_mut(),
|
|
checksum_state: ptr::null_mut(),
|
|
is_first_block: ptr::addr_of_mut!(context.end_is_first_block),
|
|
stage: ptr::addr_of_mut!(context.compression_stage),
|
|
tmp_wksp_size: 0,
|
|
block_size_max,
|
|
savings: 0,
|
|
pre_block_splitter_level: 1,
|
|
strategy: ZSTD_FAST,
|
|
use_target_c_block_size: 1,
|
|
block_splitter_enabled: 0,
|
|
checksum_flag: 0,
|
|
ending_stage: ZSTD_COMPRESSION_STAGE_ENDING,
|
|
prepare_state: frame_chunk_prepare_state,
|
|
compress_target: Some(compress_stream_test_end),
|
|
compress_split: Some(compress_stream_test_end),
|
|
compress_internal: Some(compress_stream_test_end),
|
|
compress_internal_state: ptr::null(),
|
|
compress_target_state: ptr::null(),
|
|
compress_split_state: ptr::null(),
|
|
derive_block_splits_state: ptr::null(),
|
|
});
|
|
let end_frame_chunk_state = context
|
|
.end_frame_chunk_state
|
|
.as_ref()
|
|
.map_or(ptr::null(), |state| state as *const _);
|
|
context.continue_state = Some(ZSTD_rust_compressContinueState {
|
|
callback_context,
|
|
window_state: &context.window_projection,
|
|
ldm_window_state: ptr::null(),
|
|
overflow_state: ptr::null(),
|
|
frame_chunk_state: continue_frame_chunk_state,
|
|
compress_block: compress_stream_test_block,
|
|
stage: ptr::addr_of_mut!(context.compression_stage),
|
|
consumed_src_size: ptr::addr_of_mut!(context.consumed_src_size),
|
|
produced_c_size: ptr::addr_of_mut!(context.produced_c_size),
|
|
pledged_src_size_plus_one: 0,
|
|
block_size_max,
|
|
check_block_size: 0,
|
|
no_dict_id_flag: 0,
|
|
checksum_flag: 0,
|
|
content_size_flag: 0,
|
|
format: 0,
|
|
window_log: 20,
|
|
dict_id: 0,
|
|
ldm_enabled: 0,
|
|
});
|
|
let continue_state = context
|
|
.continue_state
|
|
.as_ref()
|
|
.map_or(ptr::null(), |state| state as *const _);
|
|
context.end_continue_state = Some(ZSTD_rust_compressContinueState {
|
|
callback_context,
|
|
window_state: &context.window_projection,
|
|
ldm_window_state: ptr::null(),
|
|
overflow_state: ptr::null(),
|
|
frame_chunk_state: end_frame_chunk_state,
|
|
compress_block: compress_stream_test_end,
|
|
stage: ptr::addr_of_mut!(context.compression_stage),
|
|
consumed_src_size: ptr::addr_of_mut!(context.consumed_src_size),
|
|
produced_c_size: ptr::addr_of_mut!(context.produced_c_size),
|
|
pledged_src_size_plus_one: 0,
|
|
block_size_max,
|
|
check_block_size: 0,
|
|
no_dict_id_flag: 0,
|
|
checksum_flag: 0,
|
|
content_size_flag: 0,
|
|
format: 0,
|
|
window_log: 20,
|
|
dict_id: 0,
|
|
ldm_enabled: 0,
|
|
});
|
|
let end_continue_state = context
|
|
.end_continue_state
|
|
.as_ref()
|
|
.map_or(ptr::null(), |state| state as *const _);
|
|
context.end_state = Some(ZSTD_rust_compressEndState {
|
|
callback_context,
|
|
compress_continue_state: end_continue_state,
|
|
trace: compress_stream_test_trace,
|
|
consumed_src_size: ptr::addr_of!(context.consumed_src_size),
|
|
pledged_src_size_plus_one: 0,
|
|
content_size_flag: 0,
|
|
stage: ptr::addr_of_mut!(context.compression_stage),
|
|
no_dict_id_flag: 0,
|
|
checksum_flag: 0,
|
|
format: 0,
|
|
window_log: 20,
|
|
checksum_state: &COMPRESS_END_TEST_CHECKSUM_STATE,
|
|
});
|
|
let end_state = context
|
|
.end_state
|
|
.as_ref()
|
|
.map_or(ptr::null(), |state| state as *const _);
|
|
ZSTD_rust_compressStreamState {
|
|
callback_context,
|
|
in_buffer_mode,
|
|
out_buffer_mode,
|
|
stream_stage: stage,
|
|
block_size_max,
|
|
stable_in_not_consumed,
|
|
in_buff,
|
|
in_buff_size,
|
|
in_to_compress,
|
|
in_buff_pos,
|
|
in_buff_target,
|
|
out_buff,
|
|
out_buff_size,
|
|
out_buff_content_size,
|
|
out_buff_flushed_size,
|
|
frame_ended,
|
|
compress_continue_state: continue_state,
|
|
compress_end_state: end_state,
|
|
reset_session: compress_stream_test_reset,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn compress_stream_stable_short_input_is_deferred_with_a_progress_hint() {
|
|
let mut context = CompressStreamTestContext::default();
|
|
let mut stage = ZSTD_CSTREAM_STAGE_LOAD;
|
|
let mut stable_in_not_consumed = 0;
|
|
let mut in_to_compress = 0;
|
|
let mut in_buff_pos = 0;
|
|
let mut in_buff_target = 0;
|
|
let mut out_buff_content_size = 0;
|
|
let mut out_buff_flushed_size = 0;
|
|
let mut frame_ended = 0;
|
|
let state = compress_stream_test_state(
|
|
&mut context,
|
|
ZSTD_BM_STABLE,
|
|
ZSTD_BM_STABLE,
|
|
&mut stage,
|
|
8,
|
|
&mut stable_in_not_consumed,
|
|
ptr::null_mut(),
|
|
0,
|
|
&mut in_to_compress,
|
|
&mut in_buff_pos,
|
|
&mut in_buff_target,
|
|
ptr::null_mut(),
|
|
0,
|
|
&mut out_buff_content_size,
|
|
&mut out_buff_flushed_size,
|
|
&mut frame_ended,
|
|
);
|
|
let source = [1u8, 2, 3];
|
|
let mut output = [0xa5u8; 8];
|
|
let mut input = ZSTD_inBuffer {
|
|
src: source.as_ptr().cast(),
|
|
size: source.len(),
|
|
pos: 0,
|
|
};
|
|
let mut output_buffer = ZSTD_outBuffer {
|
|
dst: output.as_mut_ptr().cast(),
|
|
size: output.len(),
|
|
pos: 0,
|
|
};
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_compressStreamGeneric(&state, &mut output_buffer, &mut input, ZSTD_E_CONTINUE)
|
|
};
|
|
|
|
assert_eq!(result, 5);
|
|
assert_eq!(input.pos, source.len());
|
|
assert_eq!(stable_in_not_consumed, source.len());
|
|
assert_eq!(output_buffer.pos, 0);
|
|
assert_eq!(context.continue_calls, 0);
|
|
assert_eq!(context.end_calls, 0);
|
|
assert_eq!(frame_ended, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn compress_stream_buffered_output_preserves_pending_flush_and_drains_it() {
|
|
let mut context = CompressStreamTestContext {
|
|
continue_result: 3,
|
|
..CompressStreamTestContext::default()
|
|
};
|
|
let mut stage = ZSTD_CSTREAM_STAGE_LOAD;
|
|
let mut stable_in_not_consumed = 0;
|
|
let mut in_to_compress = 0;
|
|
let mut in_buff_pos = 0;
|
|
let mut in_buff_target = 4;
|
|
let mut out_buff_content_size = 0;
|
|
let mut out_buff_flushed_size = 0;
|
|
let mut frame_ended = 0;
|
|
let mut input_buffer = [0u8; 8];
|
|
let mut internal_output = [0xa5u8; 8];
|
|
let state = compress_stream_test_state(
|
|
&mut context,
|
|
ZSTD_BM_BUFFERED,
|
|
ZSTD_BM_BUFFERED,
|
|
&mut stage,
|
|
4,
|
|
&mut stable_in_not_consumed,
|
|
input_buffer.as_mut_ptr().cast(),
|
|
input_buffer.len(),
|
|
&mut in_to_compress,
|
|
&mut in_buff_pos,
|
|
&mut in_buff_target,
|
|
internal_output.as_mut_ptr().cast(),
|
|
internal_output.len(),
|
|
&mut out_buff_content_size,
|
|
&mut out_buff_flushed_size,
|
|
&mut frame_ended,
|
|
);
|
|
let source = [1u8, 2, 3, 4];
|
|
let mut output = [0xa5u8; 2];
|
|
let mut input = ZSTD_inBuffer {
|
|
src: source.as_ptr().cast(),
|
|
size: source.len(),
|
|
pos: 0,
|
|
};
|
|
let mut output_buffer = ZSTD_outBuffer {
|
|
dst: output.as_mut_ptr().cast(),
|
|
size: output.len(),
|
|
pos: 0,
|
|
};
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_compressStreamGeneric(&state, &mut output_buffer, &mut input, ZSTD_E_CONTINUE)
|
|
};
|
|
|
|
assert_eq!(result, 4);
|
|
assert_eq!(input.pos, source.len());
|
|
assert_eq!(output_buffer.pos, output.len());
|
|
assert_eq!(output, [0x5a; 2]);
|
|
assert_eq!(out_buff_content_size, 3);
|
|
assert_eq!(out_buff_flushed_size, 2);
|
|
assert_eq!(stage, ZSTD_CSTREAM_STAGE_FLUSH);
|
|
assert_eq!(context.continue_calls, 1);
|
|
|
|
let mut next_output = [0xa5u8; 2];
|
|
output_buffer = ZSTD_outBuffer {
|
|
dst: next_output.as_mut_ptr().cast(),
|
|
size: next_output.len(),
|
|
pos: 0,
|
|
};
|
|
let result = unsafe {
|
|
ZSTD_rust_compressStreamGeneric(&state, &mut output_buffer, &mut input, ZSTD_E_FLUSH)
|
|
};
|
|
|
|
assert_eq!(result, 4);
|
|
assert_eq!(next_output[0], 0x5a);
|
|
assert_eq!(output_buffer.pos, 1);
|
|
assert_eq!((out_buff_content_size, out_buff_flushed_size), (0, 0));
|
|
assert_eq!(stage, ZSTD_CSTREAM_STAGE_LOAD);
|
|
assert_eq!(context.continue_calls, 1);
|
|
}
|
|
|
|
#[test]
|
|
fn compress_stream_end_shortcut_resets_after_direct_completion() {
|
|
let mut context = CompressStreamTestContext {
|
|
end_result: 5,
|
|
..CompressStreamTestContext::default()
|
|
};
|
|
let mut stage = ZSTD_CSTREAM_STAGE_LOAD;
|
|
let mut stable_in_not_consumed = 0;
|
|
let mut in_to_compress = 0;
|
|
let mut in_buff_pos = 0;
|
|
let mut in_buff_target = 4;
|
|
let mut out_buff_content_size = 0;
|
|
let mut out_buff_flushed_size = 0;
|
|
let mut frame_ended = 0;
|
|
let mut input_buffer = [0u8; 8];
|
|
let mut internal_output = [0u8; 8];
|
|
let state = compress_stream_test_state(
|
|
&mut context,
|
|
ZSTD_BM_BUFFERED,
|
|
ZSTD_BM_STABLE,
|
|
&mut stage,
|
|
4,
|
|
&mut stable_in_not_consumed,
|
|
input_buffer.as_mut_ptr().cast(),
|
|
input_buffer.len(),
|
|
&mut in_to_compress,
|
|
&mut in_buff_pos,
|
|
&mut in_buff_target,
|
|
internal_output.as_mut_ptr().cast(),
|
|
internal_output.len(),
|
|
&mut out_buff_content_size,
|
|
&mut out_buff_flushed_size,
|
|
&mut frame_ended,
|
|
);
|
|
let source = [7u8, 8, 9];
|
|
let mut output = [0xa5u8; 8];
|
|
let mut input = ZSTD_inBuffer {
|
|
src: source.as_ptr().cast(),
|
|
size: source.len(),
|
|
pos: 0,
|
|
};
|
|
let mut output_buffer = ZSTD_outBuffer {
|
|
dst: output.as_mut_ptr().cast(),
|
|
size: output.len(),
|
|
pos: 0,
|
|
};
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_compressStreamGeneric(&state, &mut output_buffer, &mut input, ZSTD_E_END)
|
|
};
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(input.pos, source.len());
|
|
assert_eq!(output_buffer.pos, 5);
|
|
assert_eq!(&output[..5], &[0x3c; 5]);
|
|
assert_eq!(frame_ended, 1);
|
|
assert_eq!(context.end_calls, 1);
|
|
assert_eq!(context.continue_calls, 0);
|
|
assert_eq!(context.reset_calls, 1);
|
|
}
|
|
|
|
#[test]
|
|
fn compress_stream_stable_block_error_consumes_input_before_returning() {
|
|
let mut context = CompressStreamTestContext {
|
|
continue_result: ERROR(ZstdErrorCode::DstSizeTooSmall),
|
|
..CompressStreamTestContext::default()
|
|
};
|
|
let mut stage = ZSTD_CSTREAM_STAGE_LOAD;
|
|
let mut stable_in_not_consumed = 0;
|
|
let mut in_to_compress = 0;
|
|
let mut in_buff_pos = 0;
|
|
let mut in_buff_target = 0;
|
|
let mut out_buff_content_size = 0;
|
|
let mut out_buff_flushed_size = 0;
|
|
let mut frame_ended = 0;
|
|
let state = compress_stream_test_state(
|
|
&mut context,
|
|
ZSTD_BM_STABLE,
|
|
ZSTD_BM_STABLE,
|
|
&mut stage,
|
|
4,
|
|
&mut stable_in_not_consumed,
|
|
ptr::null_mut(),
|
|
0,
|
|
&mut in_to_compress,
|
|
&mut in_buff_pos,
|
|
&mut in_buff_target,
|
|
ptr::null_mut(),
|
|
0,
|
|
&mut out_buff_content_size,
|
|
&mut out_buff_flushed_size,
|
|
&mut frame_ended,
|
|
);
|
|
let source = [0x11u8; 4];
|
|
let mut output = [0xa5u8; 8];
|
|
let mut input = ZSTD_inBuffer {
|
|
src: source.as_ptr().cast(),
|
|
size: source.len(),
|
|
pos: 0,
|
|
};
|
|
let mut output_buffer = ZSTD_outBuffer {
|
|
dst: output.as_mut_ptr().cast(),
|
|
size: output.len(),
|
|
pos: 0,
|
|
};
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_compressStreamGeneric(&state, &mut output_buffer, &mut input, ZSTD_E_CONTINUE)
|
|
};
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::DstSizeTooSmall));
|
|
assert_eq!(input.pos, source.len());
|
|
assert_eq!(output_buffer.pos, 0);
|
|
assert_eq!(context.continue_calls, 1);
|
|
assert_eq!(context.end_calls, 0);
|
|
assert_eq!(context.reset_calls, 0);
|
|
assert_eq!(frame_ended, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn compress_stream_rejects_missing_initialization_before_callbacks() {
|
|
let mut context = CompressStreamTestContext::default();
|
|
let mut stage = ZSTD_CSTREAM_STAGE_INIT;
|
|
let mut stable_in_not_consumed = 0;
|
|
let mut in_to_compress = 0;
|
|
let mut in_buff_pos = 0;
|
|
let mut in_buff_target = 0;
|
|
let mut out_buff_content_size = 0;
|
|
let mut out_buff_flushed_size = 0;
|
|
let mut frame_ended = 0;
|
|
let state = compress_stream_test_state(
|
|
&mut context,
|
|
ZSTD_BM_STABLE,
|
|
ZSTD_BM_STABLE,
|
|
&mut stage,
|
|
4,
|
|
&mut stable_in_not_consumed,
|
|
ptr::null_mut(),
|
|
0,
|
|
&mut in_to_compress,
|
|
&mut in_buff_pos,
|
|
&mut in_buff_target,
|
|
ptr::null_mut(),
|
|
0,
|
|
&mut out_buff_content_size,
|
|
&mut out_buff_flushed_size,
|
|
&mut frame_ended,
|
|
);
|
|
let mut input = ZSTD_inBuffer {
|
|
src: ptr::null(),
|
|
size: 0,
|
|
pos: 0,
|
|
};
|
|
let mut output = ZSTD_outBuffer {
|
|
dst: ptr::null_mut(),
|
|
size: 0,
|
|
pos: 0,
|
|
};
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_compressStreamGeneric(&state, &mut output, &mut input, ZSTD_E_FLUSH)
|
|
};
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::InitMissing));
|
|
assert_eq!(context.continue_calls, 0);
|
|
assert_eq!(context.end_calls, 0);
|
|
assert_eq!(context.reset_calls, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn simple_strategy_follows_source_size_tiers() {
|
|
assert_eq!(ZSTD_rust_compressCCtxStrategy(0, 1), ZSTD_FAST);
|
|
assert_eq!(ZSTD_rust_compressCCtxStrategy(16 * 1024, 5), ZSTD_LAZY);
|
|
assert_eq!(
|
|
ZSTD_rust_compressCCtxStrategy(16 * 1024 + 1, 5),
|
|
ZSTD_GREEDY
|
|
);
|
|
assert_eq!(ZSTD_rust_compressCCtxStrategy(128 * 1024, 16), ZSTD_BTULTRA);
|
|
assert_eq!(ZSTD_rust_compressCCtxStrategy(256 * 1024, 16), ZSTD_BTULTRA);
|
|
assert_eq!(
|
|
ZSTD_rust_compressCCtxStrategy(256 * 1024 + 1, 16),
|
|
ZSTD_BTOPT
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn simple_strategy_preserves_default_and_fast_level_selection() {
|
|
assert_eq!(ZSTD_rust_compressCCtxStrategy(64 * 1024, 0), ZSTD_DFAST);
|
|
assert_eq!(ZSTD_rust_compressCCtxStrategy(1024 * 1024, -5), ZSTD_FAST);
|
|
assert_eq!(
|
|
ZSTD_rust_compressCCtxStrategy(1024 * 1024, 22),
|
|
ZSTD_BTULTRA2
|
|
);
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct SimpleStreamPolicyTestContext {
|
|
calls: usize,
|
|
src_size: usize,
|
|
}
|
|
|
|
unsafe extern "C" fn simple_stream_policy_test_level(
|
|
context: *const c_void,
|
|
src_size: usize,
|
|
) -> c_int {
|
|
let context = unsafe { &mut *context.cast_mut().cast::<SimpleStreamPolicyTestContext>() };
|
|
context.calls += 1;
|
|
context.src_size = src_size;
|
|
7
|
|
}
|
|
|
|
#[test]
|
|
fn simple_stream_policy_calls_the_level_leaf_only_for_a_new_frame() {
|
|
let mut context = SimpleStreamPolicyTestContext::default();
|
|
let cctx = (&mut context as *mut SimpleStreamPolicyTestContext).cast();
|
|
let projection = ZSTD_rust_simpleCompressStream2Projection {
|
|
stream_stage: ZSTD_CSTREAM_STAGE_INIT,
|
|
pledged_src_size_plus_one: 0,
|
|
rust_simple_compress2_completed: 0,
|
|
};
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_simpleCompressStream2Policy(
|
|
&projection,
|
|
cctx,
|
|
123,
|
|
Some(simple_stream_policy_test_level),
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, 7);
|
|
assert_eq!(context.calls, 1);
|
|
assert_eq!(context.src_size, 123);
|
|
}
|
|
|
|
#[test]
|
|
fn simple_stream_policy_rejects_non_initial_or_completed_frames() {
|
|
let mut context = SimpleStreamPolicyTestContext::default();
|
|
let cctx = (&mut context as *mut SimpleStreamPolicyTestContext).cast();
|
|
for projection in [
|
|
ZSTD_rust_simpleCompressStream2Projection {
|
|
stream_stage: ZSTD_CSTREAM_STAGE_LOAD,
|
|
..Default::default()
|
|
},
|
|
ZSTD_rust_simpleCompressStream2Projection {
|
|
pledged_src_size_plus_one: 1,
|
|
..Default::default()
|
|
},
|
|
ZSTD_rust_simpleCompressStream2Projection {
|
|
rust_simple_compress2_completed: 1,
|
|
..Default::default()
|
|
},
|
|
] {
|
|
let result = unsafe {
|
|
ZSTD_rust_simpleCompressStream2Policy(
|
|
&projection,
|
|
cctx,
|
|
123,
|
|
Some(simple_stream_policy_test_level),
|
|
)
|
|
};
|
|
assert_eq!(result, c_int::MIN);
|
|
}
|
|
assert_eq!(context.calls, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn reduce_table_applies_threshold_and_wrapping_subtraction() {
|
|
let mut table = [0, 1, 2, 3, 4, 5, 6, u32::MAX, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
reduce_table_internal(&mut table, 3, false);
|
|
assert_eq!(&table[..8], &[0, 0, 0, 0, 0, 2, 3, u32::MAX - 3]);
|
|
|
|
let mut wrapped_threshold = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
|
|
reduce_table_internal(&mut wrapped_threshold, u32::MAX, false);
|
|
assert_eq!(wrapped_threshold[0], 0);
|
|
assert_eq!(wrapped_threshold[1], 2);
|
|
assert_eq!(wrapped_threshold[15], 16);
|
|
}
|
|
|
|
#[test]
|
|
fn reduce_table_preserves_only_the_btlazy2_mark() {
|
|
let mut ordinary = [0u32; ZSTD_ROWSIZE];
|
|
ordinary[..3].copy_from_slice(&[ZSTD_DUBT_UNSORTED_MARK, 5, 6]);
|
|
reduce_table_internal(&mut ordinary, 3, false);
|
|
assert_eq!(&ordinary[..3], &[0, 2, 3]);
|
|
|
|
let mut btlazy2 = [0u32; ZSTD_ROWSIZE];
|
|
btlazy2[..3].copy_from_slice(&[ZSTD_DUBT_UNSORTED_MARK, 5, 6]);
|
|
reduce_table_internal(&mut btlazy2, 3, true);
|
|
assert_eq!(&btlazy2[..3], &[ZSTD_DUBT_UNSORTED_MARK, 2, 3]);
|
|
}
|
|
|
|
#[test]
|
|
fn reduce_table_processes_every_cell_in_multiple_rows() {
|
|
let mut table = [0u32; ZSTD_ROWSIZE * 2];
|
|
table[0] = 2;
|
|
table[ZSTD_ROWSIZE - 1] = 8;
|
|
table[ZSTD_ROWSIZE] = 1;
|
|
table[ZSTD_ROWSIZE * 2 - 1] = u32::MAX;
|
|
|
|
reduce_table_internal(&mut table, 4, false);
|
|
|
|
assert_eq!(table[0], 0);
|
|
assert_eq!(table[ZSTD_ROWSIZE - 1], 4);
|
|
assert_eq!(table[ZSTD_ROWSIZE], 0);
|
|
assert_eq!(table[ZSTD_ROWSIZE * 2 - 1], u32::MAX - 4);
|
|
}
|
|
|
|
#[test]
|
|
fn reduce_index_accepts_zero_sized_optional_tables() {
|
|
let mut hash_table = [0u32; ZSTD_ROWSIZE];
|
|
hash_table[..4].copy_from_slice(&[1, 2, 5, u32::MAX]);
|
|
|
|
unsafe {
|
|
ZSTD_rust_reduceIndex(
|
|
hash_table.as_mut_ptr(),
|
|
hash_table.len() as u32,
|
|
ptr::null_mut(),
|
|
0,
|
|
ptr::null_mut(),
|
|
0,
|
|
3,
|
|
0,
|
|
);
|
|
ZSTD_rust_reduceIndex(
|
|
ptr::null_mut(),
|
|
0,
|
|
ptr::null_mut(),
|
|
0,
|
|
ptr::null_mut(),
|
|
0,
|
|
u32::MAX,
|
|
1,
|
|
);
|
|
}
|
|
|
|
assert_eq!(&hash_table[..4], &[0, 0, 2, u32::MAX - 3]);
|
|
}
|
|
|
|
#[test]
|
|
fn reduce_index_preserves_the_marker_only_in_the_chain_table() {
|
|
let mut hash_table = [0u32; ZSTD_ROWSIZE];
|
|
let mut chain_table = [0u32; ZSTD_ROWSIZE];
|
|
let mut hash_table3 = [0u32; ZSTD_ROWSIZE];
|
|
hash_table[0] = ZSTD_DUBT_UNSORTED_MARK;
|
|
chain_table[0] = ZSTD_DUBT_UNSORTED_MARK;
|
|
hash_table3[0] = ZSTD_DUBT_UNSORTED_MARK;
|
|
|
|
unsafe {
|
|
ZSTD_rust_reduceIndex(
|
|
hash_table.as_mut_ptr(),
|
|
hash_table.len() as u32,
|
|
chain_table.as_mut_ptr(),
|
|
chain_table.len() as u32,
|
|
hash_table3.as_mut_ptr(),
|
|
hash_table3.len() as u32,
|
|
3,
|
|
1,
|
|
);
|
|
}
|
|
|
|
assert_eq!(hash_table[0], 0);
|
|
assert_eq!(chain_table[0], ZSTD_DUBT_UNSORTED_MARK);
|
|
assert_eq!(hash_table3[0], 0);
|
|
}
|
|
|
|
#[test]
|
|
fn reduce_index_for_match_state_selects_chain_and_hash3_tables() {
|
|
let mut hash_table = [0u32; ZSTD_ROWSIZE];
|
|
let mut chain_table = [0u32; ZSTD_ROWSIZE];
|
|
let mut hash_table3 = [0u32; ZSTD_ROWSIZE];
|
|
hash_table[0] = ZSTD_DUBT_UNSORTED_MARK;
|
|
chain_table[0] = ZSTD_DUBT_UNSORTED_MARK;
|
|
hash_table3[0] = ZSTD_DUBT_UNSORTED_MARK;
|
|
|
|
unsafe {
|
|
ZSTD_rust_reduceIndexForMatchState(
|
|
hash_table.as_mut_ptr(),
|
|
4,
|
|
chain_table.as_mut_ptr(),
|
|
4,
|
|
hash_table3.as_mut_ptr(),
|
|
4,
|
|
ZSTD_BTLAZY2,
|
|
ZSTD_RUST_PS_DISABLE,
|
|
0,
|
|
3,
|
|
);
|
|
}
|
|
|
|
assert_eq!(hash_table[0], 0);
|
|
assert_eq!(chain_table[0], ZSTD_DUBT_UNSORTED_MARK);
|
|
assert_eq!(hash_table3[0], 0);
|
|
}
|
|
|
|
#[test]
|
|
fn copy_cdict_table_removes_short_cache_tags() {
|
|
let source = [0x1234_56ff, 0xdead_beef, 0x0000_0100, u32::MAX];
|
|
let mut destination = [0u32; 4];
|
|
|
|
unsafe {
|
|
ZSTD_rust_copyCDictTableIntoCCtx(
|
|
destination.as_mut_ptr(),
|
|
source.as_ptr(),
|
|
source.len(),
|
|
1,
|
|
);
|
|
}
|
|
|
|
assert_eq!(destination, [0x0012_3456, 0x00de_adbe, 1, 0x00ff_ffff]);
|
|
}
|
|
|
|
#[test]
|
|
fn copy_cdict_table_preserves_untagged_entries() {
|
|
let source = [0, 2, 0x1234_5678, u32::MAX];
|
|
let mut destination = [0xa5a5_a5a5; 4];
|
|
|
|
unsafe {
|
|
ZSTD_rust_copyCDictTableIntoCCtx(
|
|
destination.as_mut_ptr(),
|
|
source.as_ptr(),
|
|
source.len(),
|
|
0,
|
|
);
|
|
}
|
|
|
|
assert_eq!(destination, source);
|
|
}
|
|
|
|
#[test]
|
|
fn bitmix_and_hash_salt_match_the_c_arithmetic() {
|
|
assert_eq!(bitmix(0x0123_4567_89ab_cdef, 8), 0xd498_d855_4e8d_d8cb);
|
|
assert_eq!(
|
|
advance_hash_salt(0x0123_4567_89ab_cdef, 0xfedc_ba98_7654_3210),
|
|
0xe5ee_f172_e5ff_3e57
|
|
);
|
|
assert_eq!(
|
|
ZSTD_rust_advanceHashSalt(0x0123_4567_89ab_cdef, 0xfedc_ba98_7654_3210),
|
|
0xe5ee_f172_e5ff_3e57
|
|
);
|
|
let mut hash_salt = 0x0123_4567_89ab_cdef;
|
|
unsafe { ZSTD_rust_advanceHashSaltInPlace(&mut hash_salt, 0xfedc_ba98_7654_3210) };
|
|
assert_eq!(hash_salt, 0xe5ee_f172_e5ff_3e57);
|
|
}
|
|
|
|
#[test]
|
|
fn index_too_close_to_max_uses_a_strict_margin_boundary() {
|
|
let threshold = ZSTD_CURRENT_MAX - ZSTD_INDEXOVERFLOW_MARGIN;
|
|
assert!(!index_too_close_to_max(threshold));
|
|
assert!(index_too_close_to_max(threshold + 1));
|
|
assert_eq!(ZSTD_indexTooCloseToMax(threshold), 0);
|
|
assert_eq!(ZSTD_indexTooCloseToMax(threshold + 1), 1);
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct OverflowCorrectionTestContext {
|
|
events: Vec<&'static str>,
|
|
should_correct: c_int,
|
|
correction: c_uint,
|
|
loaded_dict_end: c_uint,
|
|
dict_match_state: *const c_void,
|
|
}
|
|
|
|
unsafe fn overflow_correction_test_context(
|
|
context: *mut c_void,
|
|
) -> &'static mut OverflowCorrectionTestContext {
|
|
unsafe { &mut *context.cast::<OverflowCorrectionTestContext>() }
|
|
}
|
|
|
|
unsafe extern "C" fn overflow_correction_test_need(
|
|
context: *mut c_void,
|
|
_src: *const c_void,
|
|
_src_end: *const c_void,
|
|
) -> c_int {
|
|
let context = unsafe { overflow_correction_test_context(context) };
|
|
context.events.push("need");
|
|
context.should_correct
|
|
}
|
|
|
|
unsafe extern "C" fn overflow_correction_test_correct(
|
|
context: *mut c_void,
|
|
_src: *const c_void,
|
|
) -> c_uint {
|
|
let context = unsafe { overflow_correction_test_context(context) };
|
|
context.events.push("correct");
|
|
context.correction
|
|
}
|
|
|
|
unsafe extern "C" fn overflow_correction_test_mark_dirty(context: *mut c_void) {
|
|
unsafe { overflow_correction_test_context(context) }
|
|
.events
|
|
.push("dirty");
|
|
}
|
|
|
|
unsafe extern "C" fn overflow_correction_test_reduce(
|
|
context: *mut c_void,
|
|
_correction: c_uint,
|
|
) {
|
|
unsafe { overflow_correction_test_context(context) }
|
|
.events
|
|
.push("reduce");
|
|
}
|
|
|
|
unsafe extern "C" fn overflow_correction_test_mark_clean(context: *mut c_void) {
|
|
unsafe { overflow_correction_test_context(context) }
|
|
.events
|
|
.push("clean");
|
|
}
|
|
|
|
fn overflow_correction_test_state(
|
|
context: &mut OverflowCorrectionTestContext,
|
|
next_to_update: &mut c_uint,
|
|
) -> ZSTD_rust_overflowCorrectState {
|
|
ZSTD_rust_overflowCorrectState {
|
|
callback_context: (context as *mut OverflowCorrectionTestContext).cast(),
|
|
next_to_update,
|
|
need_correction: Some(overflow_correction_test_need),
|
|
correct_overflow: Some(overflow_correction_test_correct),
|
|
mark_tables_dirty: Some(overflow_correction_test_mark_dirty),
|
|
reduce_index: Some(overflow_correction_test_reduce),
|
|
mark_tables_clean: Some(overflow_correction_test_mark_clean),
|
|
loaded_dict_end: &mut context.loaded_dict_end,
|
|
dict_match_state: &mut context.dict_match_state,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn overflow_correction_preserves_order_and_saturates_next_to_update() {
|
|
let mut context = OverflowCorrectionTestContext {
|
|
should_correct: 1,
|
|
correction: 10,
|
|
loaded_dict_end: 33,
|
|
dict_match_state: ptr::dangling(),
|
|
..Default::default()
|
|
};
|
|
let mut next_to_update = 7;
|
|
let state = overflow_correction_test_state(&mut context, &mut next_to_update);
|
|
|
|
unsafe {
|
|
ZSTD_rust_overflowCorrectIfNeeded(&state, ptr::null(), ptr::null());
|
|
}
|
|
|
|
assert_eq!(next_to_update, 0);
|
|
assert_eq!(
|
|
context.events,
|
|
["need", "correct", "dirty", "reduce", "clean"]
|
|
);
|
|
assert_eq!(context.loaded_dict_end, 0);
|
|
assert!(context.dict_match_state.is_null());
|
|
}
|
|
|
|
#[test]
|
|
fn overflow_correction_skips_callbacks_when_window_is_safe() {
|
|
let mut context = OverflowCorrectionTestContext::default();
|
|
let mut next_to_update = 123;
|
|
let state = overflow_correction_test_state(&mut context, &mut next_to_update);
|
|
|
|
unsafe {
|
|
ZSTD_rust_overflowCorrectIfNeeded(&state, ptr::null(), ptr::null());
|
|
}
|
|
|
|
assert_eq!(next_to_update, 123);
|
|
assert_eq!(context.events, ["need"]);
|
|
}
|
|
|
|
#[test]
|
|
fn window_correction_handles_current_cycle_start_boundary() {
|
|
assert_eq!(window_correct_overflow(0x100, 3, 8), 0xf0);
|
|
assert_eq!(window_correct_overflow(0x101, 3, 8), 0xf0);
|
|
assert_eq!(window_correct_overflow(0x102, 3, 8), 0xf8);
|
|
|
|
assert_eq!(
|
|
ZSTD_rust_windowCorrectOverflow(0x100, 3, 8),
|
|
window_correct_overflow(0x100, 3, 8)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn window_correction_selects_larger_of_cycle_and_max_distance() {
|
|
assert_eq!(window_correct_overflow(0x100, 5, 8), 0xc0);
|
|
assert_eq!(window_correct_overflow(0x200, 5, 64), 0x1a0);
|
|
}
|
|
|
|
#[test]
|
|
fn window_correction_wraps_u32_intermediates() {
|
|
assert_eq!(window_correct_overflow(0, 0, 1), u32::MAX - 2);
|
|
assert_eq!(
|
|
window_correct_overflow(0x8000_0000, 31, 0x8000_0000),
|
|
0x8000_0000
|
|
);
|
|
assert_eq!(
|
|
ZSTD_rust_windowCorrectOverflow(0x8000_0000, 31, 0x8000_0000),
|
|
0x8000_0000
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn window_overflow_need_uses_the_explicit_current_max_boundary() {
|
|
assert!(!window_need_overflow_correction(1024, false, 1024, false));
|
|
assert!(window_need_overflow_correction(1025, false, 1024, false));
|
|
assert_eq!(ZSTD_rust_windowNeedOverflowCorrection(1024, 1, 1024, 0), 0);
|
|
assert_eq!(ZSTD_rust_windowNeedOverflowCorrection(1025, 0, 1024, 0), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn window_overflow_need_honors_frequent_dictionary_policy() {
|
|
let can_correct = window_can_overflow_correct(27, 3, 16, 0, 0);
|
|
let dictionary_still_valid = window_can_overflow_correct(27, 3, 16, 20, 0);
|
|
assert!(can_correct);
|
|
assert!(!dictionary_still_valid);
|
|
assert!(window_need_overflow_correction(27, can_correct, 1024, true));
|
|
assert!(!window_need_overflow_correction(
|
|
27,
|
|
dictionary_still_valid,
|
|
1024,
|
|
true
|
|
));
|
|
assert_eq!(
|
|
ZSTD_rust_windowNeedOverflowCorrection(27, can_correct as u32, 1024, 1),
|
|
1
|
|
);
|
|
assert_eq!(
|
|
ZSTD_rust_windowNeedOverflowCorrection(27, dictionary_still_valid as u32, 1024, 1,),
|
|
0
|
|
);
|
|
assert!(!window_need_overflow_correction(27, true, 1024, false));
|
|
}
|
|
|
|
#[test]
|
|
fn limit_next_to_update_preserves_threshold_and_bounded_catch_up() {
|
|
assert_eq!(limit_next_to_update(884, 500), 500);
|
|
assert_eq!(limit_next_to_update(885, 500), 884);
|
|
assert_eq!(limit_next_to_update(1000, 500), 884);
|
|
assert_eq!(limit_next_to_update(1200, 500), 1008);
|
|
assert_eq!(ZSTD_rust_limitNextToUpdate(300, u32::MAX - 100), 283);
|
|
}
|
|
|
|
#[test]
|
|
fn window_overflow_need_scales_the_frequency_with_correction_count() {
|
|
assert!(window_can_overflow_correct(27, 3, 16, 0, 0));
|
|
assert!(!window_can_overflow_correct(27, 3, 16, 0, 1));
|
|
assert!(window_can_overflow_correct(53, 3, 16, 0, 1));
|
|
assert_eq!(ZSTD_rust_windowCanOverflowCorrect(53, 3, 16, 0, 1), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn window_overflow_need_preserves_u32_wrap_boundaries() {
|
|
assert!(window_can_overflow_correct(
|
|
3,
|
|
31,
|
|
0x8000_0000,
|
|
0x8000_0000,
|
|
0,
|
|
));
|
|
assert!(window_can_overflow_correct(27, 3, 16, 0, u32::MAX,));
|
|
assert_eq!(
|
|
ZSTD_rust_windowCanOverflowCorrect(3, 31, 0x8000_0000, 0x8000_0000, 0),
|
|
1
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn dict_too_big_uses_a_strict_chunk_size_boundary() {
|
|
assert!(!dict_too_big(0));
|
|
assert!(!dict_too_big(ZSTD_CHUNKSIZE_MAX));
|
|
assert!(dict_too_big(ZSTD_CHUNKSIZE_MAX + 1));
|
|
assert_eq!(ZSTD_dictTooBig(ZSTD_CHUNKSIZE_MAX), 0);
|
|
assert_eq!(ZSTD_dictTooBig(ZSTD_CHUNKSIZE_MAX + 1), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn target_block_policy_requires_compression_and_nonfirst_rle() {
|
|
assert_eq!(
|
|
target_c_block_size_action(0, 0, 1, 1, 0, 128, ZSTD_FAST),
|
|
TargetCBlockAction::Rle
|
|
);
|
|
assert_eq!(
|
|
target_c_block_size_action(0, 1, 1, 1, 0, 128, ZSTD_FAST),
|
|
TargetCBlockAction::Raw
|
|
);
|
|
assert_eq!(
|
|
target_c_block_size_action(0, 0, 0, 1, 0, 128, ZSTD_FAST),
|
|
TargetCBlockAction::Raw
|
|
);
|
|
assert_eq!(
|
|
target_c_block_size_action(0, 0, 1, 0, 0, 128, ZSTD_FAST),
|
|
TargetCBlockAction::Raw
|
|
);
|
|
assert_eq!(
|
|
ZSTD_rust_targetCBlockSizeAction(0, 0, 1, 1, 0, 128, ZSTD_FAST),
|
|
TargetCBlockAction::Rle as c_int
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn target_block_policy_falls_back_for_uncompressed_and_small_results() {
|
|
assert_eq!(
|
|
target_c_block_size_action(1, 0, 0, 0, 0, 128, ZSTD_FAST),
|
|
TargetCBlockAction::Raw
|
|
);
|
|
assert_eq!(
|
|
target_c_block_size_action(
|
|
ZSTD_TARGET_CBLOCK_BSS_COMPRESS,
|
|
0,
|
|
0,
|
|
0,
|
|
ERROR(ZstdErrorCode::DstSizeTooSmall),
|
|
128,
|
|
ZSTD_FAST,
|
|
),
|
|
TargetCBlockAction::Raw
|
|
);
|
|
assert_eq!(
|
|
target_c_block_size_action(
|
|
ZSTD_TARGET_CBLOCK_BSS_COMPRESS,
|
|
0,
|
|
0,
|
|
0,
|
|
ERROR(ZstdErrorCode::Generic),
|
|
128,
|
|
ZSTD_FAST,
|
|
),
|
|
TargetCBlockAction::Error
|
|
);
|
|
assert_eq!(
|
|
target_c_block_size_action(ZSTD_TARGET_CBLOCK_BSS_COMPRESS, 0, 0, 0, 0, 128, ZSTD_FAST,),
|
|
TargetCBlockAction::Raw
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn target_block_policy_uses_a_strict_three_byte_header_boundary() {
|
|
let src_size = 128;
|
|
let max_c_size = src_size - min_gain(src_size, ZSTD_FAST);
|
|
let compressed = max_c_size + ZSTD_BLOCK_HEADER_SIZE - 1;
|
|
let raw = max_c_size + ZSTD_BLOCK_HEADER_SIZE;
|
|
|
|
assert_eq!(
|
|
target_c_block_size_action(
|
|
ZSTD_TARGET_CBLOCK_BSS_COMPRESS,
|
|
0,
|
|
0,
|
|
0,
|
|
compressed,
|
|
src_size,
|
|
ZSTD_FAST,
|
|
),
|
|
TargetCBlockAction::Compressed
|
|
);
|
|
assert_eq!(
|
|
target_c_block_size_action(
|
|
ZSTD_TARGET_CBLOCK_BSS_COMPRESS,
|
|
0,
|
|
0,
|
|
0,
|
|
raw,
|
|
src_size,
|
|
ZSTD_FAST,
|
|
),
|
|
TargetCBlockAction::Raw
|
|
);
|
|
assert_eq!(
|
|
ZSTD_rust_targetCBlockSizeAction(
|
|
ZSTD_TARGET_CBLOCK_BSS_COMPRESS,
|
|
0,
|
|
0,
|
|
0,
|
|
compressed,
|
|
src_size,
|
|
ZSTD_FAST,
|
|
),
|
|
TargetCBlockAction::Compressed as c_int
|
|
);
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
unsafe extern "C" fn target_block_test_superblock_error(
|
|
_seq_store: *const c_void,
|
|
_prev_cblock: *const c_void,
|
|
_next_cblock: *mut c_void,
|
|
_strategy: c_int,
|
|
_disable_literal_compression: c_int,
|
|
_workspace: *mut c_void,
|
|
_wksp_size: usize,
|
|
_bmi2: c_int,
|
|
_window_log: c_uint,
|
|
_target_cblock_size: usize,
|
|
_dst: *mut c_void,
|
|
_dst_capacity: usize,
|
|
_src: *const c_void,
|
|
_src_size: usize,
|
|
_last_block: c_uint,
|
|
) -> usize {
|
|
ERROR(ZstdErrorCode::Generic)
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
unsafe extern "C" fn target_block_test_superblock_empty(
|
|
_seq_store: *const c_void,
|
|
_prev_cblock: *const c_void,
|
|
_next_cblock: *mut c_void,
|
|
_strategy: c_int,
|
|
_disable_literal_compression: c_int,
|
|
_workspace: *mut c_void,
|
|
_wksp_size: usize,
|
|
_bmi2: c_int,
|
|
_window_log: c_uint,
|
|
_target_cblock_size: usize,
|
|
_dst: *mut c_void,
|
|
_dst_capacity: usize,
|
|
_src: *const c_void,
|
|
_src_size: usize,
|
|
_last_block: c_uint,
|
|
) -> usize {
|
|
0
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
unsafe extern "C" fn target_block_test_superblock_too_small(
|
|
_seq_store: *const c_void,
|
|
_prev_cblock: *const c_void,
|
|
_next_cblock: *mut c_void,
|
|
_strategy: c_int,
|
|
_disable_literal_compression: c_int,
|
|
_workspace: *mut c_void,
|
|
_wksp_size: usize,
|
|
_bmi2: c_int,
|
|
_window_log: c_uint,
|
|
_target_cblock_size: usize,
|
|
_dst: *mut c_void,
|
|
_dst_capacity: usize,
|
|
_src: *const c_void,
|
|
_src_size: usize,
|
|
_last_block: c_uint,
|
|
) -> usize {
|
|
ERROR(ZstdErrorCode::DstSizeTooSmall)
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
unsafe extern "C" fn target_block_test_superblock_compressed(
|
|
_seq_store: *const c_void,
|
|
_prev_cblock: *const c_void,
|
|
_next_cblock: *mut c_void,
|
|
_strategy: c_int,
|
|
_disable_literal_compression: c_int,
|
|
_workspace: *mut c_void,
|
|
_wksp_size: usize,
|
|
_bmi2: c_int,
|
|
_window_log: c_uint,
|
|
_target_cblock_size: usize,
|
|
_dst: *mut c_void,
|
|
_dst_capacity: usize,
|
|
_src: *const c_void,
|
|
_src_size: usize,
|
|
_last_block: c_uint,
|
|
) -> usize {
|
|
1
|
|
}
|
|
|
|
fn target_block_test_state(
|
|
seq_store: &mut SeqStore_t,
|
|
prev_block: &mut ZSTD_compressedBlockState_t,
|
|
next_block: &mut ZSTD_compressedBlockState_t,
|
|
prev_c_block: &mut *mut ZSTD_compressedBlockState_t,
|
|
next_c_block: &mut *mut ZSTD_compressedBlockState_t,
|
|
is_first_block: c_int,
|
|
) -> ZSTD_rust_targetCBlockSizeState {
|
|
*prev_c_block = prev_block as *mut ZSTD_compressedBlockState_t;
|
|
*next_c_block = next_block as *mut ZSTD_compressedBlockState_t;
|
|
ZSTD_rust_targetCBlockSizeState {
|
|
seq_store,
|
|
prev_c_block,
|
|
next_c_block,
|
|
tmp_workspace: ptr::null_mut(),
|
|
tmp_wksp_size: 0,
|
|
strategy: ZSTD_FAST,
|
|
disable_literal_compression: 0,
|
|
bmi2: 0,
|
|
window_log: 20,
|
|
target_c_block_size: 0,
|
|
is_first_block,
|
|
}
|
|
}
|
|
|
|
fn target_block_test_seq_store() -> (SeqStore_t, [SeqDef; 1], [u8; 16]) {
|
|
let mut sequences = [SeqDef::default(); 1];
|
|
let mut literals = [0u8; 16];
|
|
let sequences_start = sequences.as_mut_ptr();
|
|
let literals_start = literals.as_mut_ptr();
|
|
let seq_store = SeqStore_t {
|
|
sequencesStart: sequences_start,
|
|
sequences: sequences_start,
|
|
litStart: literals_start,
|
|
lit: literals_start,
|
|
llCode: ptr::null_mut(),
|
|
mlCode: ptr::null_mut(),
|
|
ofCode: ptr::null_mut(),
|
|
maxNbSeq: sequences.len(),
|
|
maxNbLit: literals.len(),
|
|
longLengthType: 0,
|
|
longLengthPos: 0,
|
|
};
|
|
(seq_store, sequences, literals)
|
|
}
|
|
|
|
#[test]
|
|
fn target_block_body_emits_rle_only_for_nonfirst_repeated_blocks() {
|
|
let (mut seq_store, _sequences, _literals) = target_block_test_seq_store();
|
|
let mut prev_block = zeroed_state();
|
|
let mut next_block = zeroed_state();
|
|
let mut prev_c_block = ptr::null_mut();
|
|
let mut next_c_block = ptr::null_mut();
|
|
let state = target_block_test_state(
|
|
&mut seq_store,
|
|
&mut prev_block,
|
|
&mut next_block,
|
|
&mut prev_c_block,
|
|
&mut next_c_block,
|
|
0,
|
|
);
|
|
let source = [0x5au8; 16];
|
|
let mut output = [0xa5u8; 4];
|
|
|
|
let result = unsafe {
|
|
compress_block_target_c_block_size_body_with(
|
|
&state,
|
|
output.as_mut_ptr().cast(),
|
|
output.len(),
|
|
source.as_ptr().cast(),
|
|
source.len(),
|
|
ZSTD_TARGET_CBLOCK_BSS_COMPRESS,
|
|
1,
|
|
target_block_test_superblock_error,
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, 4);
|
|
assert_eq!(output[3], source[0]);
|
|
}
|
|
|
|
#[test]
|
|
fn target_block_body_suppresses_first_block_rle_and_propagates_superblock_error() {
|
|
let (mut seq_store, _sequences, _literals) = target_block_test_seq_store();
|
|
let mut prev_block = zeroed_state();
|
|
let mut next_block = zeroed_state();
|
|
let mut prev_c_block = ptr::null_mut();
|
|
let mut next_c_block = ptr::null_mut();
|
|
let state = target_block_test_state(
|
|
&mut seq_store,
|
|
&mut prev_block,
|
|
&mut next_block,
|
|
&mut prev_c_block,
|
|
&mut next_c_block,
|
|
1,
|
|
);
|
|
let source = [0x5au8; 16];
|
|
let mut output = [0xa5u8; 16];
|
|
|
|
let result = unsafe {
|
|
compress_block_target_c_block_size_body_with(
|
|
&state,
|
|
output.as_mut_ptr().cast(),
|
|
output.len(),
|
|
source.as_ptr().cast(),
|
|
source.len(),
|
|
ZSTD_TARGET_CBLOCK_BSS_COMPRESS,
|
|
1,
|
|
target_block_test_superblock_error,
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::Generic));
|
|
assert_eq!(output, [0xa5u8; 16]);
|
|
}
|
|
|
|
#[test]
|
|
fn target_block_body_falls_back_to_raw_for_empty_or_small_superblocks() {
|
|
let (mut seq_store, _sequences, _literals) = target_block_test_seq_store();
|
|
let mut prev_block = zeroed_state();
|
|
let mut next_block = zeroed_state();
|
|
let mut prev_c_block = ptr::null_mut();
|
|
let mut next_c_block = ptr::null_mut();
|
|
let state = target_block_test_state(
|
|
&mut seq_store,
|
|
&mut prev_block,
|
|
&mut next_block,
|
|
&mut prev_c_block,
|
|
&mut next_c_block,
|
|
1,
|
|
);
|
|
let source = *b"raw fallback";
|
|
|
|
for superblock in [
|
|
target_block_test_superblock_empty as TargetCBlockSuperBlockFn,
|
|
target_block_test_superblock_too_small,
|
|
] {
|
|
let mut output = [0xa5u8; 32];
|
|
let result = unsafe {
|
|
compress_block_target_c_block_size_body_with(
|
|
&state,
|
|
output.as_mut_ptr().cast(),
|
|
output.len(),
|
|
source.as_ptr().cast(),
|
|
source.len(),
|
|
ZSTD_TARGET_CBLOCK_BSS_COMPRESS,
|
|
0,
|
|
superblock,
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, source.len() + ZSTD_BLOCK_HEADER_SIZE);
|
|
assert_eq!(&output[ZSTD_BLOCK_HEADER_SIZE..result], &source);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn target_block_body_swaps_state_only_for_compressed_output() {
|
|
let (mut seq_store, _sequences, _literals) = target_block_test_seq_store();
|
|
let mut prev_block = zeroed_state();
|
|
let mut next_block = zeroed_state();
|
|
let mut prev_c_block = &mut prev_block as *mut ZSTD_compressedBlockState_t;
|
|
let mut next_c_block = &mut next_block as *mut ZSTD_compressedBlockState_t;
|
|
let state = target_block_test_state(
|
|
&mut seq_store,
|
|
&mut prev_block,
|
|
&mut next_block,
|
|
&mut prev_c_block,
|
|
&mut next_c_block,
|
|
1,
|
|
);
|
|
let prev_ptr = prev_c_block;
|
|
let next_ptr = next_c_block;
|
|
let source = [0x3cu8; 128];
|
|
let mut output = [0xa5u8; 128];
|
|
|
|
let result = unsafe {
|
|
compress_block_target_c_block_size_body_with(
|
|
&state,
|
|
output.as_mut_ptr().cast(),
|
|
output.len(),
|
|
source.as_ptr().cast(),
|
|
source.len(),
|
|
ZSTD_TARGET_CBLOCK_BSS_COMPRESS,
|
|
0,
|
|
target_block_test_superblock_compressed,
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, 1);
|
|
assert_eq!(unsafe { *state.prev_c_block }, next_ptr);
|
|
assert_eq!(unsafe { *state.next_c_block }, prev_ptr);
|
|
assert_eq!(output, [0xa5u8; 128]);
|
|
}
|
|
|
|
#[test]
|
|
fn frame_progress_unknown_pledge_updates_counters() {
|
|
let mut consumed = 7;
|
|
let mut produced = 11;
|
|
let result =
|
|
unsafe { ZSTD_rust_updateFrameProgression(&mut consumed, &mut produced, 0, 5, 13, 2) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(consumed, 12);
|
|
assert_eq!(produced, 26);
|
|
}
|
|
|
|
#[test]
|
|
fn frame_progress_exact_pledge_is_accepted() {
|
|
let mut consumed = 7;
|
|
let mut produced = 11;
|
|
let result =
|
|
unsafe { ZSTD_rust_updateFrameProgression(&mut consumed, &mut produced, 13, 5, 13, 2) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(consumed, 12);
|
|
assert_eq!(produced, 26);
|
|
}
|
|
|
|
#[test]
|
|
fn frame_progress_one_byte_overrun_is_reported() {
|
|
let mut consumed = 7;
|
|
let mut produced = 11;
|
|
let result =
|
|
unsafe { ZSTD_rust_updateFrameProgression(&mut consumed, &mut produced, 13, 6, 13, 2) };
|
|
|
|
assert_eq!(result, 1);
|
|
assert_eq!(consumed, 13);
|
|
}
|
|
|
|
#[test]
|
|
fn frame_progress_counters_update_before_overrun_result() {
|
|
let mut consumed = 100;
|
|
let mut produced = 200;
|
|
let result = unsafe {
|
|
ZSTD_rust_updateFrameProgression(&mut consumed, &mut produced, 106, 7, 17, 3)
|
|
};
|
|
|
|
assert_eq!(result, 1);
|
|
assert_eq!(consumed, 107);
|
|
assert_eq!(produced, 220);
|
|
}
|
|
|
|
#[test]
|
|
fn frame_progression_constructs_single_thread_values() {
|
|
let consumed = 11_u64;
|
|
let buffered = 37_usize;
|
|
let produced = 53_u64;
|
|
let expected = ZSTD_frameProgression {
|
|
ingested: consumed + buffered as u64,
|
|
consumed,
|
|
produced,
|
|
flushed: produced,
|
|
currentJobID: 0,
|
|
nbActiveWorkers: 0,
|
|
};
|
|
|
|
assert_eq!(frame_progression(consumed, buffered, produced), expected);
|
|
assert_eq!(
|
|
ZSTD_rust_frameProgression(consumed, buffered, produced),
|
|
expected
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn frame_progression_wraps_buffered_input_to_u64() {
|
|
let consumed = u64::MAX - 3;
|
|
let buffered = 8_usize;
|
|
let progression = ZSTD_rust_frameProgression(consumed, buffered, 17);
|
|
|
|
assert_eq!(progression.ingested, consumed.wrapping_add(buffered as u64));
|
|
assert_eq!(progression.consumed, consumed);
|
|
assert_eq!(progression.produced, 17);
|
|
assert_eq!(progression.flushed, 17);
|
|
assert_eq!(progression.currentJobID, 0);
|
|
assert_eq!(progression.nbActiveWorkers, 0);
|
|
}
|
|
|
|
unsafe extern "C" fn to_flush_now_test_callback(context: *mut c_void) -> usize {
|
|
let calls = unsafe { &mut *context.cast::<usize>() };
|
|
*calls += 1;
|
|
17
|
|
}
|
|
|
|
#[test]
|
|
fn to_flush_now_only_calls_mt_callback_for_workers() {
|
|
let mut calls: usize = 0;
|
|
let mut state = ZSTD_rust_toFlushNowState {
|
|
callback_context: &mut calls as *mut usize as *mut c_void,
|
|
nb_workers: 0,
|
|
to_flush_now: to_flush_now_test_callback,
|
|
};
|
|
|
|
assert_eq!(unsafe { ZSTD_rust_toFlushNow(&state) }, 0);
|
|
assert_eq!(calls, 0);
|
|
|
|
state.nb_workers = 1;
|
|
assert_eq!(unsafe { ZSTD_rust_toFlushNow(&state) }, 17);
|
|
assert_eq!(calls, 1);
|
|
}
|
|
|
|
#[test]
|
|
fn to_flush_now_rejects_a_null_state() {
|
|
assert_eq!(
|
|
unsafe { ZSTD_rust_toFlushNow(ptr::null()) },
|
|
ERROR(ZstdErrorCode::Generic)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn next_input_size_hint_uses_remaining_stable_block_capacity() {
|
|
let hint = ZSTD_rust_nextInputSizeHint(ZSTD_BM_STABLE, 256, 37, 99, 12);
|
|
|
|
assert_eq!(next_input_size_hint(ZSTD_BM_STABLE, 256, 37, 99, 12), 219);
|
|
assert_eq!(hint, 219);
|
|
}
|
|
|
|
#[test]
|
|
fn next_input_size_hint_replaces_empty_buffered_hint_with_block_size() {
|
|
assert_eq!(
|
|
ZSTD_rust_nextInputSizeHint(ZSTD_BM_BUFFERED, 256, 37, 128, 128),
|
|
256
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn next_input_size_hint_returns_nonzero_buffered_hint() {
|
|
assert_eq!(
|
|
ZSTD_rust_nextInputSizeHint(ZSTD_BM_BUFFERED, 256, 37, 128, 32),
|
|
96
|
|
);
|
|
}
|
|
|
|
unsafe extern "C" fn next_input_size_hint_mt_or_st_test_callback(
|
|
context: *mut c_void,
|
|
) -> usize {
|
|
let calls = unsafe { &mut *context.cast::<usize>() };
|
|
*calls += 1;
|
|
777
|
|
}
|
|
|
|
fn next_input_size_hint_mt_or_st_test_state(
|
|
nb_workers: c_int,
|
|
calls: &mut usize,
|
|
) -> ZSTD_rust_nextInputSizeHintMTorSTState {
|
|
ZSTD_rust_nextInputSizeHintMTorSTState {
|
|
block_size_max: 256,
|
|
stable_in_not_consumed: 37,
|
|
in_buff_target: 128,
|
|
in_buff_pos: 32,
|
|
nb_workers,
|
|
in_buffer_mode: ZSTD_BM_BUFFERED,
|
|
mt_context: calls as *mut usize as *mut c_void,
|
|
mt_next_input_size_hint: Some(next_input_size_hint_mt_or_st_test_callback),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn next_input_size_hint_mt_or_st_dispatches_to_mt_callback_for_workers() {
|
|
let mut calls = 0;
|
|
let state = next_input_size_hint_mt_or_st_test_state(1, &mut calls);
|
|
|
|
assert_eq!(unsafe { ZSTD_rust_nextInputSizeHintMTorST(&state) }, 777);
|
|
assert_eq!(calls, 1);
|
|
}
|
|
|
|
#[test]
|
|
fn next_input_size_hint_mt_or_st_uses_single_thread_leaf_without_workers() {
|
|
let mut calls = 0;
|
|
let state = next_input_size_hint_mt_or_st_test_state(0, &mut calls);
|
|
|
|
assert_eq!(unsafe { ZSTD_rust_nextInputSizeHintMTorST(&state) }, 96);
|
|
assert_eq!(calls, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn mt_next_input_size_hint_handles_empty_input_buffer() {
|
|
assert_eq!(mt_next_input_size_hint(128, 0), 128);
|
|
assert_eq!(ZSTDMT_rust_nextInputSizeHint(128, 0), 128);
|
|
}
|
|
|
|
#[test]
|
|
fn mt_next_input_size_hint_returns_remaining_capacity() {
|
|
assert_eq!(mt_next_input_size_hint(128, 37), 91);
|
|
assert_eq!(ZSTDMT_rust_nextInputSizeHint(128, 37), 91);
|
|
}
|
|
|
|
#[test]
|
|
fn mt_next_input_size_hint_replaces_full_buffer_with_target_size() {
|
|
assert_eq!(ZSTDMT_rust_nextInputSizeHint(128, 128), 128);
|
|
}
|
|
|
|
#[test]
|
|
fn mt_next_input_size_hint_preserves_zero_target_behavior() {
|
|
assert_eq!(ZSTDMT_rust_nextInputSizeHint(0, 0), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn mt_next_input_size_hint_preserves_wrapped_overfill() {
|
|
assert_eq!(ZSTDMT_rust_nextInputSizeHint(3, 4), usize::MAX);
|
|
}
|
|
|
|
#[test]
|
|
fn mt_sizeof_cctx_handles_zero_components() {
|
|
assert_eq!(mt_sizeof_cctx(0, 0, 0, 0, 0, 0, 0, 0), 0);
|
|
assert_eq!(ZSTDMT_rust_sizeofCCtx(0, 0, 0, 0, 0, 0, 0, 0), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn mt_sizeof_cctx_adds_all_components_in_order() {
|
|
assert_eq!(mt_sizeof_cctx(1, 2, 3, 4, 5, 6, 7, 8), 36);
|
|
assert_eq!(ZSTDMT_rust_sizeofCCtx(1, 2, 3, 4, 5, 6, 7, 8), 36);
|
|
}
|
|
|
|
#[test]
|
|
fn mt_sizeof_cctx_wraps_like_c_size_t_addition() {
|
|
assert_eq!(mt_sizeof_cctx(usize::MAX, 1, 2, 3, 4, 5, 6, 7), 27);
|
|
assert_eq!(ZSTDMT_rust_sizeofCCtx(usize::MAX, 1, 2, 3, 4, 5, 6, 7), 27);
|
|
}
|
|
|
|
#[test]
|
|
fn in_buffer_for_end_flush_returns_stable_expected_buffer() {
|
|
let expected_src = b"input".as_ptr().cast::<c_void>();
|
|
let result = ZSTD_rust_inBufferForEndFlush(ZSTD_BM_STABLE, expected_src, 37, 11);
|
|
|
|
assert_eq!(result.src, expected_src);
|
|
assert_eq!(result.size, 37);
|
|
assert_eq!(result.pos, 11);
|
|
|
|
let null_result = ZSTD_rust_inBufferForEndFlush(ZSTD_BM_STABLE, ptr::null(), 37, 11);
|
|
|
|
assert!(null_result.src.is_null());
|
|
assert_eq!(null_result.size, 37);
|
|
assert_eq!(null_result.pos, 11);
|
|
}
|
|
|
|
#[test]
|
|
fn in_buffer_for_end_flush_clears_buffered_and_other_modes() {
|
|
let expected_src = b"input".as_ptr().cast::<c_void>();
|
|
|
|
for mode in [ZSTD_BM_BUFFERED, 42] {
|
|
let result = ZSTD_rust_inBufferForEndFlush(mode, expected_src, 37, 11);
|
|
|
|
assert!(result.src.is_null());
|
|
assert_eq!(result.size, 0);
|
|
assert_eq!(result.pos, 0);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn end_stream_remaining_ignores_estimate_components_after_frame_end() {
|
|
assert_eq!(end_stream_remaining(17, 1, 1), 17);
|
|
assert_eq!(ZSTD_rust_endStreamRemaining(17, 1, 1), 17);
|
|
}
|
|
|
|
#[test]
|
|
fn end_stream_remaining_adds_block_header_without_checksum() {
|
|
assert_eq!(end_stream_remaining(17, 0, 0), 20);
|
|
assert_eq!(ZSTD_rust_endStreamRemaining(17, 0, 0), 20);
|
|
}
|
|
|
|
#[test]
|
|
fn end_stream_remaining_adds_block_header_and_checksum() {
|
|
assert_eq!(end_stream_remaining(17, 0, 1), 24);
|
|
assert_eq!(ZSTD_rust_endStreamRemaining(17, 0, 1), 24);
|
|
}
|
|
|
|
#[test]
|
|
fn end_stream_remaining_wraps_size_t_additions() {
|
|
assert_eq!(end_stream_remaining(usize::MAX, 0, 1), 6);
|
|
assert_eq!(ZSTD_rust_endStreamRemaining(usize::MAX, 0, 1), 6);
|
|
}
|
|
|
|
#[test]
|
|
fn check_buffer_stability_accepts_matching_stable_input() {
|
|
let expected_src = b"input".as_ptr().cast::<c_void>();
|
|
|
|
assert_eq!(
|
|
check_buffer_stability(
|
|
ZSTD_BM_STABLE,
|
|
ZSTD_BM_BUFFERED,
|
|
expected_src,
|
|
11,
|
|
expected_src,
|
|
11,
|
|
0,
|
|
37,
|
|
5,
|
|
),
|
|
0
|
|
);
|
|
assert_eq!(
|
|
ZSTD_rust_checkBufferStability(
|
|
ZSTD_BM_STABLE,
|
|
ZSTD_BM_BUFFERED,
|
|
expected_src,
|
|
11,
|
|
expected_src,
|
|
11,
|
|
0,
|
|
37,
|
|
5,
|
|
),
|
|
0
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn check_buffer_stability_rejects_changed_input_pointer_or_position() {
|
|
let expected_src = b"input".as_ptr().cast::<c_void>();
|
|
let other_src = b"other".as_ptr().cast::<c_void>();
|
|
let error = ERROR(ZstdErrorCode::StabilityConditionNotRespected);
|
|
|
|
assert_eq!(
|
|
ZSTD_rust_checkBufferStability(
|
|
ZSTD_BM_STABLE,
|
|
ZSTD_BM_BUFFERED,
|
|
expected_src,
|
|
11,
|
|
other_src,
|
|
11,
|
|
0,
|
|
37,
|
|
5,
|
|
),
|
|
error
|
|
);
|
|
assert_eq!(
|
|
ZSTD_rust_checkBufferStability(
|
|
ZSTD_BM_STABLE,
|
|
ZSTD_BM_BUFFERED,
|
|
expected_src,
|
|
11,
|
|
expected_src,
|
|
12,
|
|
0,
|
|
37,
|
|
5,
|
|
),
|
|
error
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn check_buffer_stability_ignores_input_changes_in_buffered_mode() {
|
|
let expected_src = b"input".as_ptr().cast::<c_void>();
|
|
let other_src = b"other".as_ptr().cast::<c_void>();
|
|
|
|
assert_eq!(
|
|
ZSTD_rust_checkBufferStability(
|
|
ZSTD_BM_BUFFERED,
|
|
ZSTD_BM_BUFFERED,
|
|
expected_src,
|
|
11,
|
|
other_src,
|
|
12,
|
|
0,
|
|
37,
|
|
5,
|
|
),
|
|
0
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn check_buffer_stability_validates_stable_output_remainder() {
|
|
let expected_src = b"input".as_ptr().cast::<c_void>();
|
|
let error = ERROR(ZstdErrorCode::StabilityConditionNotRespected);
|
|
|
|
assert_eq!(
|
|
ZSTD_rust_checkBufferStability(
|
|
ZSTD_BM_BUFFERED,
|
|
ZSTD_BM_STABLE,
|
|
expected_src,
|
|
0,
|
|
ptr::null(),
|
|
0,
|
|
32,
|
|
40,
|
|
8,
|
|
),
|
|
0
|
|
);
|
|
assert_eq!(
|
|
ZSTD_rust_checkBufferStability(
|
|
ZSTD_BM_BUFFERED,
|
|
ZSTD_BM_STABLE,
|
|
expected_src,
|
|
0,
|
|
ptr::null(),
|
|
0,
|
|
32,
|
|
40,
|
|
7,
|
|
),
|
|
error
|
|
);
|
|
assert_eq!(
|
|
ZSTD_rust_checkBufferStability(
|
|
ZSTD_BM_BUFFERED,
|
|
ZSTD_BM_STABLE,
|
|
expected_src,
|
|
0,
|
|
ptr::null(),
|
|
0,
|
|
usize::MAX,
|
|
3,
|
|
4,
|
|
),
|
|
0
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn set_buffer_expectations_updates_only_enabled_stable_modes() {
|
|
let expected_src = b"expected".as_ptr().cast::<c_void>();
|
|
let input_src = b"input".as_ptr().cast::<c_void>();
|
|
let mut expected_input = ZSTD_inBuffer {
|
|
src: expected_src,
|
|
size: 3,
|
|
pos: 1,
|
|
};
|
|
let input = ZSTD_inBuffer {
|
|
src: input_src,
|
|
size: 11,
|
|
pos: 7,
|
|
};
|
|
let output = ZSTD_outBuffer {
|
|
dst: ptr::null_mut(),
|
|
size: 40,
|
|
pos: 9,
|
|
};
|
|
let mut expected_output_size = 5;
|
|
|
|
unsafe {
|
|
ZSTD_rust_setBufferExpectations(
|
|
ZSTD_BM_STABLE,
|
|
ZSTD_BM_STABLE,
|
|
&mut expected_input,
|
|
&mut expected_output_size,
|
|
&output,
|
|
&input,
|
|
);
|
|
}
|
|
|
|
assert_eq!(expected_input.src, input.src);
|
|
assert_eq!(expected_input.size, input.size);
|
|
assert_eq!(expected_input.pos, input.pos);
|
|
assert_eq!(expected_output_size, 31);
|
|
|
|
let original_input_src = expected_input.src;
|
|
let original_input_size = expected_input.size;
|
|
let original_input_pos = expected_input.pos;
|
|
let original_output_size = expected_output_size;
|
|
let other_input = ZSTD_inBuffer {
|
|
src: expected_src,
|
|
size: 99,
|
|
pos: 22,
|
|
};
|
|
let other_output = ZSTD_outBuffer {
|
|
dst: ptr::null_mut(),
|
|
size: 3,
|
|
pos: 8,
|
|
};
|
|
|
|
unsafe {
|
|
ZSTD_rust_setBufferExpectations(
|
|
ZSTD_BM_BUFFERED,
|
|
ZSTD_BM_BUFFERED,
|
|
&mut expected_input,
|
|
&mut expected_output_size,
|
|
&other_output,
|
|
&other_input,
|
|
);
|
|
}
|
|
|
|
assert_eq!(expected_input.src, original_input_src);
|
|
assert_eq!(expected_input.size, original_input_size);
|
|
assert_eq!(expected_input.pos, original_input_pos);
|
|
assert_eq!(expected_output_size, original_output_size);
|
|
}
|
|
|
|
#[test]
|
|
fn sequence_api_plan_places_input_and_frame_checksums_around_blocks() {
|
|
assert_eq!(
|
|
sequence_api_plan(false, ZSTD_SF_NO_BLOCK_DELIMITERS, 1, 1),
|
|
Ok(SequenceApiPlan {
|
|
update_input_checksum: true,
|
|
append_frame_checksum: true,
|
|
})
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn sequence_api_plan_disables_checksums_for_literals_variant() {
|
|
assert_eq!(
|
|
sequence_api_plan(true, ZSTD_SF_EXPLICIT_BLOCK_DELIMITERS, 0, 0),
|
|
Ok(SequenceApiPlan {
|
|
update_input_checksum: false,
|
|
append_frame_checksum: false,
|
|
})
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn sequence_api_plan_preserves_literals_validation_precedence() {
|
|
assert_eq!(
|
|
ERR_getErrorCode(
|
|
sequence_api_plan(true, ZSTD_SF_NO_BLOCK_DELIMITERS, 1, 1).unwrap_err()
|
|
),
|
|
ZstdErrorCode::FrameParameterUnsupported as i32
|
|
);
|
|
assert_eq!(
|
|
ERR_getErrorCode(
|
|
sequence_api_plan(true, ZSTD_SF_EXPLICIT_BLOCK_DELIMITERS, 1, 1).unwrap_err()
|
|
),
|
|
ZstdErrorCode::ParameterUnsupported as i32
|
|
);
|
|
assert_eq!(
|
|
ERR_getErrorCode(
|
|
sequence_api_plan(true, ZSTD_SF_EXPLICIT_BLOCK_DELIMITERS, 0, 1).unwrap_err()
|
|
),
|
|
ZstdErrorCode::FrameParameterUnsupported as i32
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn sequence_api_literal_capacity_check_precedes_initialization() {
|
|
assert_eq!(
|
|
sequence_api_validate_literal_capacity(9, 8),
|
|
ERROR(ZstdErrorCode::WorkSpaceTooSmall)
|
|
);
|
|
assert_eq!(sequence_api_validate_literal_capacity(8, 9), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn sequence_copier_selector_returns_no_delimiters_mode() {
|
|
assert_eq!(
|
|
select_sequence_copier(ZSTD_SF_NO_BLOCK_DELIMITERS),
|
|
ZSTD_SF_NO_BLOCK_DELIMITERS
|
|
);
|
|
assert_eq!(
|
|
ZSTD_rust_selectSequenceCopier(ZSTD_SF_NO_BLOCK_DELIMITERS),
|
|
ZSTD_SF_NO_BLOCK_DELIMITERS
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn sequence_copier_selector_returns_explicit_delimiters_mode() {
|
|
assert_eq!(
|
|
select_sequence_copier(ZSTD_SF_EXPLICIT_BLOCK_DELIMITERS),
|
|
ZSTD_SF_EXPLICIT_BLOCK_DELIMITERS
|
|
);
|
|
assert_eq!(
|
|
ZSTD_rust_selectSequenceCopier(ZSTD_SF_EXPLICIT_BLOCK_DELIMITERS),
|
|
ZSTD_SF_EXPLICIT_BLOCK_DELIMITERS
|
|
);
|
|
}
|
|
|
|
#[cfg(debug_assertions)]
|
|
#[test]
|
|
#[should_panic]
|
|
fn sequence_copier_selector_rejects_invalid_mode_in_debug() {
|
|
let _ = select_sequence_copier(-1);
|
|
}
|
|
|
|
#[cfg(debug_assertions)]
|
|
#[test]
|
|
#[should_panic]
|
|
fn sequence_copier_selector_rejects_unsupported_mode_in_debug() {
|
|
let _ = select_sequence_copier(2);
|
|
}
|
|
|
|
#[cfg(not(debug_assertions))]
|
|
#[test]
|
|
fn sequence_copier_selector_falls_back_to_no_delimiters_in_release() {
|
|
assert_eq!(
|
|
ZSTD_rust_selectSequenceCopier(-1),
|
|
ZSTD_SF_NO_BLOCK_DELIMITERS
|
|
);
|
|
assert_eq!(
|
|
ZSTD_rust_selectSequenceCopier(2),
|
|
ZSTD_SF_NO_BLOCK_DELIMITERS
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn sequence_block_action_keeps_first_block_from_using_rle() {
|
|
assert_eq!(
|
|
sequence_block_action(1, 1, 1, 2),
|
|
SequenceBlockAction::Compressed
|
|
);
|
|
assert_eq!(sequence_block_action(0, 1, 1, 2), SequenceBlockAction::Rle);
|
|
}
|
|
|
|
#[test]
|
|
fn sequence_block_action_maps_entropy_fallbacks() {
|
|
assert_eq!(sequence_block_action(0, 0, 0, 0), SequenceBlockAction::Raw);
|
|
assert_eq!(sequence_block_action(0, 0, 0, 1), SequenceBlockAction::Rle);
|
|
assert_eq!(
|
|
sequence_block_action(0, 0, 0, 2),
|
|
SequenceBlockAction::Compressed
|
|
);
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
unsafe extern "C" fn single_block_test_entropy_compress(
|
|
_seq_store: *const SeqStore_t,
|
|
_prev_entropy: *const ZSTD_entropyCTables_t,
|
|
_next_entropy: *mut ZSTD_entropyCTables_t,
|
|
strategy: c_int,
|
|
_disable_literal_compression: c_int,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
_src_size: usize,
|
|
_workspace: *mut c_void,
|
|
_workspace_size: usize,
|
|
_bmi2: c_int,
|
|
) -> usize {
|
|
match strategy {
|
|
0 => 0,
|
|
1 => {
|
|
assert!(dst_capacity >= 2);
|
|
unsafe {
|
|
*dst.cast::<u8>() = 0x11;
|
|
*dst.cast::<u8>().add(1) = 0x22;
|
|
}
|
|
2
|
|
}
|
|
_ => ERROR(ZstdErrorCode::Generic),
|
|
}
|
|
}
|
|
|
|
fn single_block_test_state(
|
|
seq_store: &SeqStore_t,
|
|
d_rep: &mut [u32; ZSTD_REP_NUM],
|
|
c_rep: &mut [u32; ZSTD_REP_NUM],
|
|
prev_block: &mut ZSTD_compressedBlockState_t,
|
|
next_block: &mut ZSTD_compressedBlockState_t,
|
|
prev_c_block: &mut *mut ZSTD_compressedBlockState_t,
|
|
next_c_block: &mut *mut ZSTD_compressedBlockState_t,
|
|
seq_collector: &mut SeqCollector,
|
|
strategy: c_int,
|
|
is_first_block: c_int,
|
|
) -> ZSTD_rust_seqStoreSingleBlockState {
|
|
*prev_c_block = prev_block;
|
|
*next_c_block = next_block;
|
|
ZSTD_rust_seqStoreSingleBlockState {
|
|
seq_store,
|
|
d_rep: d_rep.as_mut_ptr(),
|
|
c_rep: c_rep.as_mut_ptr(),
|
|
prev_c_block,
|
|
next_c_block,
|
|
tmp_workspace: ptr::null_mut(),
|
|
tmp_wksp_size: 0,
|
|
seq_collector,
|
|
strategy,
|
|
disable_literal_compression: 0,
|
|
bmi2: 0,
|
|
is_first_block,
|
|
}
|
|
}
|
|
|
|
fn single_block_test_seams() -> SingleBlockSeams {
|
|
SingleBlockSeams {
|
|
entropy_compress: single_block_test_entropy_compress,
|
|
..SingleBlockSeams::production()
|
|
}
|
|
}
|
|
|
|
fn block_internal_test_state(
|
|
seq_store: &mut SeqStore_t,
|
|
prev_block: &mut ZSTD_compressedBlockState_t,
|
|
next_block: &mut ZSTD_compressedBlockState_t,
|
|
prev_c_block: &mut *mut ZSTD_compressedBlockState_t,
|
|
next_c_block: &mut *mut ZSTD_compressedBlockState_t,
|
|
seq_collector: &mut SeqCollector,
|
|
strategy: c_int,
|
|
is_first_block: c_int,
|
|
) -> ZSTD_rust_blockInternalState {
|
|
*prev_c_block = prev_block;
|
|
*next_c_block = next_block;
|
|
ZSTD_rust_blockInternalState {
|
|
seq_store,
|
|
prev_c_block,
|
|
next_c_block,
|
|
tmp_workspace: ptr::null_mut(),
|
|
tmp_wksp_size: 0,
|
|
seq_collector,
|
|
strategy,
|
|
disable_literal_compression: 0,
|
|
bmi2: 0,
|
|
is_first_block,
|
|
}
|
|
}
|
|
|
|
struct SplitBlockTestStore {
|
|
seq_store: SeqStore_t,
|
|
_sequences: Box<[SeqDef; 2]>,
|
|
_ll_code: Box<[u8; 2]>,
|
|
_ml_code: Box<[u8; 2]>,
|
|
_of_code: Box<[u8; 2]>,
|
|
_literals: Box<[u8; 6]>,
|
|
}
|
|
|
|
fn split_block_test_seq_store() -> SplitBlockTestStore {
|
|
let mut sequences = Box::new([
|
|
SeqDef {
|
|
offBase: 4,
|
|
litLength: 2,
|
|
mlBase: 0,
|
|
},
|
|
SeqDef {
|
|
offBase: 5,
|
|
litLength: 2,
|
|
mlBase: 0,
|
|
},
|
|
]);
|
|
let mut ll_code = Box::new([1u8; 2]);
|
|
let mut ml_code = Box::new([1u8; 2]);
|
|
let mut of_code = Box::new([1u8; 2]);
|
|
let mut literals = Box::new([0u8; 6]);
|
|
let sequences_start = sequences.as_mut_ptr();
|
|
let literals_start = literals.as_mut_ptr();
|
|
let seq_store = SeqStore_t {
|
|
sequencesStart: sequences_start,
|
|
sequences: unsafe { sequences_start.add(sequences.len()) },
|
|
litStart: literals_start,
|
|
lit: unsafe { literals_start.add(literals.len()) },
|
|
llCode: ll_code.as_mut_ptr(),
|
|
mlCode: ml_code.as_mut_ptr(),
|
|
ofCode: of_code.as_mut_ptr(),
|
|
maxNbSeq: sequences.len(),
|
|
maxNbLit: literals.len(),
|
|
longLengthType: 0,
|
|
longLengthPos: 0,
|
|
};
|
|
SplitBlockTestStore {
|
|
seq_store,
|
|
_sequences: sequences,
|
|
_ll_code: ll_code,
|
|
_ml_code: ml_code,
|
|
_of_code: of_code,
|
|
_literals: literals,
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn split_block_test_state(
|
|
seq_store: &SeqStore_t,
|
|
partitions: &[u32],
|
|
next_seq_store: &mut SeqStore_t,
|
|
curr_seq_store: &mut SeqStore_t,
|
|
prev_block: &mut ZSTD_compressedBlockState_t,
|
|
next_block: &mut ZSTD_compressedBlockState_t,
|
|
prev_c_block: &mut *mut ZSTD_compressedBlockState_t,
|
|
next_c_block: &mut *mut ZSTD_compressedBlockState_t,
|
|
seq_collector: &mut SeqCollector,
|
|
) -> ZSTD_rust_splitBlockState {
|
|
*prev_c_block = prev_block;
|
|
*next_c_block = next_block;
|
|
ZSTD_rust_splitBlockState {
|
|
seq_store,
|
|
partitions: partitions.as_ptr(),
|
|
next_seq_store,
|
|
curr_seq_store,
|
|
prev_c_block,
|
|
next_c_block,
|
|
tmp_workspace: ptr::null_mut(),
|
|
tmp_wksp_size: 0,
|
|
seq_collector,
|
|
block_size_max: ZSTD_BLOCKSIZE_MAX,
|
|
strategy: 0,
|
|
disable_literal_compression: 0,
|
|
bmi2: 0,
|
|
is_first_block: 1,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn split_block_body_emits_partitions_and_final_literals() {
|
|
let fixture = split_block_test_seq_store();
|
|
let partitions = [1, 2];
|
|
let mut next_seq_store = unsafe { MaybeUninit::<SeqStore_t>::zeroed().assume_init() };
|
|
let mut curr_seq_store = unsafe { MaybeUninit::<SeqStore_t>::zeroed().assume_init() };
|
|
let mut prev_block = zeroed_state();
|
|
let mut next_block = zeroed_state();
|
|
let mut prev_c_block = ptr::null_mut();
|
|
let mut next_c_block = ptr::null_mut();
|
|
let mut seq_collector = SeqCollector {
|
|
collectSequences: 0,
|
|
seqStart: ptr::null_mut(),
|
|
seqIndex: 0,
|
|
maxSequences: 0,
|
|
};
|
|
let state = split_block_test_state(
|
|
&fixture.seq_store,
|
|
&partitions,
|
|
&mut next_seq_store,
|
|
&mut curr_seq_store,
|
|
&mut prev_block,
|
|
&mut next_block,
|
|
&mut prev_c_block,
|
|
&mut next_c_block,
|
|
&mut seq_collector,
|
|
);
|
|
let source: Vec<u8> = (0..12).collect();
|
|
let mut output = [0xa5u8; 32];
|
|
|
|
let result = unsafe {
|
|
compress_block_split_body_with(
|
|
&state,
|
|
output.as_mut_ptr().cast(),
|
|
output.len(),
|
|
source.as_ptr().cast(),
|
|
source.len(),
|
|
1,
|
|
1,
|
|
single_block_test_seams(),
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, 18);
|
|
assert_eq!(&output[ZSTD_BLOCK_HEADER_SIZE..8], &source[..5]);
|
|
assert_eq!(&output[8 + ZSTD_BLOCK_HEADER_SIZE..result], &source[5..]);
|
|
assert!(std::ptr::eq(prev_c_block, &prev_block));
|
|
}
|
|
|
|
#[test]
|
|
fn block_internal_body_collects_sequences_before_entropy_emission() {
|
|
let (mut seq_store, _sequences, _literals) = target_block_test_seq_store();
|
|
let mut prev_block = zeroed_state();
|
|
prev_block.entropy.fse.offcode_repeatMode = 2;
|
|
let mut next_block = zeroed_state();
|
|
let mut prev_c_block = ptr::null_mut();
|
|
let mut next_c_block = ptr::null_mut();
|
|
let mut output_sequences = [ZSTD_Sequence {
|
|
offset: 0xa5,
|
|
litLength: 0xa5,
|
|
matchLength: 0xa5,
|
|
rep: 0xa5,
|
|
}];
|
|
let mut seq_collector = SeqCollector {
|
|
collectSequences: 1,
|
|
seqStart: output_sequences.as_mut_ptr(),
|
|
seqIndex: 0,
|
|
maxSequences: output_sequences.len(),
|
|
};
|
|
let state = block_internal_test_state(
|
|
&mut seq_store,
|
|
&mut prev_block,
|
|
&mut next_block,
|
|
&mut prev_c_block,
|
|
&mut next_c_block,
|
|
&mut seq_collector,
|
|
1,
|
|
0,
|
|
);
|
|
let source = [0x5au8; 16];
|
|
let mut output = [0xa5u8; 16];
|
|
|
|
let result = unsafe {
|
|
compress_block_internal_body_with(
|
|
&state,
|
|
output.as_mut_ptr().cast(),
|
|
output.len(),
|
|
source.as_ptr().cast(),
|
|
source.len(),
|
|
1,
|
|
single_block_test_seams(),
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(seq_collector.seqIndex, 1);
|
|
assert_eq!(output_sequences[0].litLength, 0);
|
|
assert!(std::ptr::eq(prev_c_block, &next_block));
|
|
assert!(std::ptr::eq(next_c_block, &prev_block));
|
|
assert_eq!(prev_block.entropy.fse.offcode_repeatMode, 2);
|
|
}
|
|
|
|
#[test]
|
|
fn block_internal_body_gates_rle_and_cleans_repeat_mode() {
|
|
let (mut seq_store, _sequences, _literals) = target_block_test_seq_store();
|
|
let mut prev_block = zeroed_state();
|
|
prev_block.entropy.fse.offcode_repeatMode = 2;
|
|
let mut next_block = zeroed_state();
|
|
let mut prev_c_block = ptr::null_mut();
|
|
let mut next_c_block = ptr::null_mut();
|
|
let mut seq_collector = SeqCollector {
|
|
collectSequences: 0,
|
|
seqStart: ptr::null_mut(),
|
|
seqIndex: 0,
|
|
maxSequences: 0,
|
|
};
|
|
let state = block_internal_test_state(
|
|
&mut seq_store,
|
|
&mut prev_block,
|
|
&mut next_block,
|
|
&mut prev_c_block,
|
|
&mut next_c_block,
|
|
&mut seq_collector,
|
|
1,
|
|
0,
|
|
);
|
|
let source = [0x5au8; 16];
|
|
let mut output = [0xa5u8; 32];
|
|
|
|
let result = unsafe {
|
|
compress_block_internal_body_with(
|
|
&state,
|
|
output.as_mut_ptr().cast(),
|
|
output.len(),
|
|
source.as_ptr().cast(),
|
|
source.len(),
|
|
1,
|
|
single_block_test_seams(),
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, 1);
|
|
assert_eq!(output[0], source[0]);
|
|
assert_eq!(prev_block.entropy.fse.offcode_repeatMode, 1);
|
|
}
|
|
|
|
#[test]
|
|
fn block_internal_body_propagates_entropy_error_through_repeat_cleanup() {
|
|
let (mut seq_store, _sequences, _literals) = target_block_test_seq_store();
|
|
let mut prev_block = zeroed_state();
|
|
prev_block.entropy.fse.offcode_repeatMode = 2;
|
|
let mut next_block = zeroed_state();
|
|
let mut prev_c_block = ptr::null_mut();
|
|
let mut next_c_block = ptr::null_mut();
|
|
let mut seq_collector = SeqCollector {
|
|
collectSequences: 0,
|
|
seqStart: ptr::null_mut(),
|
|
seqIndex: 0,
|
|
maxSequences: 0,
|
|
};
|
|
let state = block_internal_test_state(
|
|
&mut seq_store,
|
|
&mut prev_block,
|
|
&mut next_block,
|
|
&mut prev_c_block,
|
|
&mut next_c_block,
|
|
&mut seq_collector,
|
|
-1,
|
|
0,
|
|
);
|
|
let source = [0x5au8; 16];
|
|
let mut output = [0xa5u8; 32];
|
|
|
|
let result = unsafe {
|
|
compress_block_internal_body_with(
|
|
&state,
|
|
output.as_mut_ptr().cast(),
|
|
output.len(),
|
|
source.as_ptr().cast(),
|
|
source.len(),
|
|
1,
|
|
single_block_test_seams(),
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::Generic));
|
|
assert_eq!(output, [0xa5u8; 32]);
|
|
assert_eq!(prev_block.entropy.fse.offcode_repeatMode, 1);
|
|
}
|
|
|
|
#[test]
|
|
fn single_block_body_propagates_entropy_errors_without_serializing() {
|
|
let (seq_store, _sequences, _literals) = target_block_test_seq_store();
|
|
let mut d_rep = [1, 4, 8];
|
|
let mut c_rep = d_rep;
|
|
let mut prev_block = zeroed_state();
|
|
let mut next_block = zeroed_state();
|
|
let mut prev_c_block = ptr::null_mut();
|
|
let mut next_c_block = ptr::null_mut();
|
|
let mut seq_collector = SeqCollector {
|
|
collectSequences: 0,
|
|
seqStart: ptr::null_mut(),
|
|
seqIndex: 0,
|
|
maxSequences: 0,
|
|
};
|
|
let state = single_block_test_state(
|
|
&seq_store,
|
|
&mut d_rep,
|
|
&mut c_rep,
|
|
&mut prev_block,
|
|
&mut next_block,
|
|
&mut prev_c_block,
|
|
&mut next_c_block,
|
|
&mut seq_collector,
|
|
-1,
|
|
1,
|
|
);
|
|
let source = *b"entropy error";
|
|
let mut output = [0xa5u8; 32];
|
|
|
|
let result = unsafe {
|
|
compress_seq_store_single_block_body_with(
|
|
&state,
|
|
output.as_mut_ptr().cast(),
|
|
output.len(),
|
|
source.as_ptr().cast(),
|
|
source.len(),
|
|
1,
|
|
0,
|
|
single_block_test_seams(),
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::Generic));
|
|
assert_eq!(output, [0xa5u8; 32]);
|
|
assert_eq!(d_rep, [1, 4, 8]);
|
|
assert_eq!(c_rep, [1, 4, 8]);
|
|
}
|
|
|
|
#[test]
|
|
fn single_block_body_serializes_raw_entropy_fallback() {
|
|
let (seq_store, _sequences, _literals) = target_block_test_seq_store();
|
|
let mut d_rep = [1, 4, 8];
|
|
let mut c_rep = d_rep;
|
|
let mut prev_block = zeroed_state();
|
|
let mut next_block = zeroed_state();
|
|
let mut prev_c_block = ptr::null_mut();
|
|
let mut next_c_block = ptr::null_mut();
|
|
let mut seq_collector = SeqCollector {
|
|
collectSequences: 0,
|
|
seqStart: ptr::null_mut(),
|
|
seqIndex: 0,
|
|
maxSequences: 0,
|
|
};
|
|
let state = single_block_test_state(
|
|
&seq_store,
|
|
&mut d_rep,
|
|
&mut c_rep,
|
|
&mut prev_block,
|
|
&mut next_block,
|
|
&mut prev_c_block,
|
|
&mut next_c_block,
|
|
&mut seq_collector,
|
|
0,
|
|
1,
|
|
);
|
|
let source = *b"raw fallback";
|
|
let mut output = [0xa5u8; 32];
|
|
|
|
let result = unsafe {
|
|
compress_seq_store_single_block_body_with(
|
|
&state,
|
|
output.as_mut_ptr().cast(),
|
|
output.len(),
|
|
source.as_ptr().cast(),
|
|
source.len(),
|
|
1,
|
|
0,
|
|
single_block_test_seams(),
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, source.len() + ZSTD_BLOCK_HEADER_SIZE);
|
|
assert_eq!(&output[ZSTD_BLOCK_HEADER_SIZE..result], &source);
|
|
}
|
|
|
|
#[test]
|
|
fn single_block_body_gates_rle_on_first_block_and_confirms_compressed_state() {
|
|
for is_first_block in [0, 1] {
|
|
let (seq_store, _sequences, _literals) = target_block_test_seq_store();
|
|
let mut d_rep = [1, 4, 8];
|
|
let mut c_rep = d_rep;
|
|
let mut prev_block = zeroed_state();
|
|
let mut next_block = zeroed_state();
|
|
next_block.entropy.fse.offcode_repeatMode = 2;
|
|
let mut prev_c_block = ptr::null_mut();
|
|
let mut next_c_block = ptr::null_mut();
|
|
let mut seq_collector = SeqCollector {
|
|
collectSequences: 0,
|
|
seqStart: ptr::null_mut(),
|
|
seqIndex: 0,
|
|
maxSequences: 0,
|
|
};
|
|
let state = single_block_test_state(
|
|
&seq_store,
|
|
&mut d_rep,
|
|
&mut c_rep,
|
|
&mut prev_block,
|
|
&mut next_block,
|
|
&mut prev_c_block,
|
|
&mut next_c_block,
|
|
&mut seq_collector,
|
|
1,
|
|
is_first_block,
|
|
);
|
|
let source = [0x5au8; 16];
|
|
let mut output = [0xa5u8; 32];
|
|
|
|
let result = unsafe {
|
|
compress_seq_store_single_block_body_with(
|
|
&state,
|
|
output.as_mut_ptr().cast(),
|
|
output.len(),
|
|
source.as_ptr().cast(),
|
|
source.len(),
|
|
1,
|
|
0,
|
|
single_block_test_seams(),
|
|
)
|
|
};
|
|
|
|
if is_first_block == 0 {
|
|
assert_eq!(result, 4);
|
|
assert_eq!(output[3], source[0]);
|
|
assert!(std::ptr::eq(prev_c_block, &prev_block));
|
|
} else {
|
|
assert_eq!(result, ZSTD_BLOCK_HEADER_SIZE + 2);
|
|
assert_eq!(&output[ZSTD_BLOCK_HEADER_SIZE..result], &[0x11, 0x22]);
|
|
assert!(std::ptr::eq(prev_c_block, &next_block));
|
|
assert!(std::ptr::eq(next_c_block, &prev_block));
|
|
}
|
|
let expected_repeat_mode = if is_first_block == 0 { 0 } else { 1 };
|
|
assert_eq!(
|
|
unsafe { (*prev_c_block).entropy.fse.offcode_repeatMode },
|
|
expected_repeat_mode
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn empty_sequence_block_preserves_header_and_capacity_contract() {
|
|
let mut output = [0xa5; 4];
|
|
assert_eq!(
|
|
unsafe { write_empty_sequence_block(output.as_mut_ptr(), output.len()) },
|
|
ZSTD_BLOCK_HEADER_SIZE
|
|
);
|
|
assert_eq!(output, [1, 0, 0, 0]);
|
|
|
|
let mut short_output = [0xa5; 3];
|
|
assert_eq!(
|
|
unsafe { write_empty_sequence_block(short_output.as_mut_ptr(), short_output.len()) },
|
|
ERROR(ZstdErrorCode::DstSizeTooSmall)
|
|
);
|
|
assert_eq!(short_output, [0xa5; 3]);
|
|
}
|
|
|
|
#[test]
|
|
fn empty_sequence_literals_block_writes_only_the_three_byte_header() {
|
|
let mut output = [0xa5; 4];
|
|
assert_eq!(
|
|
unsafe { write_empty_sequence_literals_block(output.as_mut_ptr(), output.len()) },
|
|
ZSTD_BLOCK_HEADER_SIZE
|
|
);
|
|
assert_eq!(output, [1, 0, 0, 0xa5]);
|
|
|
|
let mut short_output = [0xa5; 2];
|
|
assert_eq!(
|
|
unsafe {
|
|
write_empty_sequence_literals_block(short_output.as_mut_ptr(), short_output.len())
|
|
},
|
|
ERROR(ZstdErrorCode::DstSizeTooSmall)
|
|
);
|
|
assert_eq!(short_output, [0xa5; 2]);
|
|
}
|
|
|
|
#[test]
|
|
fn invalidate_rep_codes_clears_all_entries() {
|
|
let mut rep = [11u32, 22, 33];
|
|
|
|
unsafe { ZSTD_rust_invalidateRepCodes(rep.as_mut_ptr()) };
|
|
|
|
assert_eq!(rep, [0; ZSTD_REP_NUM]);
|
|
}
|
|
|
|
#[test]
|
|
fn window_clear_writes_the_same_end_to_both_limits() {
|
|
let mut low_limit = 11u32;
|
|
let mut dict_limit = 22u32;
|
|
|
|
unsafe { ZSTD_rust_windowClear(0x1234_5678, &mut low_limit, &mut dict_limit) };
|
|
|
|
assert_eq!(low_limit, 0x1234_5678);
|
|
assert_eq!(dict_limit, 0x1234_5678);
|
|
}
|
|
|
|
#[cfg(target_pointer_width = "64")]
|
|
#[test]
|
|
fn window_clear_truncates_a_large_size_t_like_c_u32_cast() {
|
|
let mut low_limit = 11u32;
|
|
let mut dict_limit = 22u32;
|
|
let end_t = 0x1_0000_0000usize + 0x89ab_cdef;
|
|
|
|
unsafe { ZSTD_rust_windowClear(end_t, &mut low_limit, &mut dict_limit) };
|
|
|
|
assert_eq!(low_limit, 0x89ab_cdef);
|
|
assert_eq!(dict_limit, 0x89ab_cdef);
|
|
}
|
|
|
|
#[test]
|
|
fn invalidate_match_state_clears_window_and_private_fields() {
|
|
let mut low_limit = 11u32;
|
|
let mut dict_limit = 22u32;
|
|
let mut next_to_update = 33u32;
|
|
let mut loaded_dict_end = 44u32;
|
|
let mut lit_length_sum = 55u32;
|
|
let marker = 0x5au8;
|
|
let mut dict_match_state = (&marker as *const u8).cast::<c_void>();
|
|
|
|
unsafe {
|
|
ZSTD_rust_invalidateMatchState(
|
|
0x1234_5678,
|
|
&mut low_limit,
|
|
&mut dict_limit,
|
|
&mut next_to_update,
|
|
&mut loaded_dict_end,
|
|
&mut lit_length_sum,
|
|
&mut dict_match_state,
|
|
)
|
|
};
|
|
|
|
assert_eq!(low_limit, 0x1234_5678);
|
|
assert_eq!(dict_limit, 0x1234_5678);
|
|
assert_eq!(next_to_update, 0x1234_5678);
|
|
assert_eq!(loaded_dict_end, 0);
|
|
assert_eq!(lit_length_sum, 0);
|
|
assert!(dict_match_state.is_null());
|
|
}
|
|
|
|
#[test]
|
|
fn window_update_leaves_empty_input_untouched() {
|
|
let mut state = ZSTD_rust_windowUpdateState {
|
|
nextSrc: ptr::null(),
|
|
base: ptr::null(),
|
|
dictBase: ptr::null(),
|
|
dictLimit: 2,
|
|
lowLimit: 2,
|
|
};
|
|
let original = state;
|
|
|
|
let result = unsafe { ZSTD_rust_windowUpdate(&mut state, ptr::null(), 0, 0) };
|
|
|
|
assert_eq!(result, 1);
|
|
assert_eq!(state, original);
|
|
}
|
|
|
|
#[test]
|
|
fn window_init_sets_empty_sentinel_and_dictionary_predicates() {
|
|
let mut state = ZSTD_rust_windowUpdateState {
|
|
nextSrc: ptr::null(),
|
|
base: ptr::null(),
|
|
dictBase: ptr::null(),
|
|
dictLimit: 0,
|
|
lowLimit: 0,
|
|
};
|
|
|
|
unsafe { ZSTD_rust_windowInit(&mut state) };
|
|
|
|
assert_eq!(state.dictLimit, ZSTD_WINDOW_START_INDEX);
|
|
assert_eq!(state.lowLimit, ZSTD_WINDOW_START_INDEX);
|
|
assert_eq!(state.dictBase as usize, state.base as usize);
|
|
assert_eq!(
|
|
(state.nextSrc as usize).wrapping_sub(state.base as usize),
|
|
ZSTD_WINDOW_START_INDEX as usize
|
|
);
|
|
assert_eq!(
|
|
ZSTD_rust_windowIsEmpty(state.nextSrc, state.base, state.dictLimit, state.lowLimit),
|
|
1
|
|
);
|
|
assert_eq!(ZSTD_rust_windowHasExtDict(10, 2), 1);
|
|
assert_eq!(ZSTD_rust_windowHasExtDict(10, 10), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn window_enforce_max_dist_updates_limits_and_invalidates_dictionary() {
|
|
let state = ZSTD_rust_windowEnforceMaxDist(
|
|
100,
|
|
64,
|
|
ZSTD_rust_windowDictState {
|
|
lowLimit: 2,
|
|
dictLimit: 10,
|
|
loadedDictEnd: 8,
|
|
invalidate: 0,
|
|
},
|
|
);
|
|
|
|
assert_eq!(state.lowLimit, 36);
|
|
assert_eq!(state.dictLimit, 36);
|
|
assert_eq!(state.loadedDictEnd, 0);
|
|
assert_eq!(state.invalidate, 1);
|
|
}
|
|
|
|
#[test]
|
|
fn window_dictionary_policy_preserves_valid_state_and_detects_mismatch() {
|
|
let state = ZSTD_rust_windowEnforceMaxDist(
|
|
70,
|
|
64,
|
|
ZSTD_rust_windowDictState {
|
|
lowLimit: 2,
|
|
dictLimit: 10,
|
|
loadedDictEnd: 8,
|
|
invalidate: 1,
|
|
},
|
|
);
|
|
|
|
assert_eq!(state.lowLimit, 2);
|
|
assert_eq!(state.dictLimit, 10);
|
|
assert_eq!(state.loadedDictEnd, 8);
|
|
assert_eq!(state.invalidate, 0);
|
|
assert_eq!(ZSTD_rust_windowCheckDictValidity(70, 64, 8, 8), 0);
|
|
assert_eq!(ZSTD_rust_windowCheckDictValidity(100, 64, 8, 8), 1);
|
|
assert_eq!(ZSTD_rust_windowCheckDictValidity(70, 64, 8, 10), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn window_update_preserves_contiguous_input_and_clips_overlapping_extdict() {
|
|
let storage = [0u8; 64];
|
|
let storage_start = storage.as_ptr() as usize;
|
|
let base = unsafe { storage.as_ptr().add(16) };
|
|
let next_src = unsafe { base.add(2) };
|
|
let mut contiguous_state = ZSTD_rust_windowUpdateState {
|
|
nextSrc: next_src.cast(),
|
|
base: base.cast(),
|
|
dictBase: base.cast(),
|
|
dictLimit: 2,
|
|
lowLimit: 2,
|
|
};
|
|
|
|
let contiguous =
|
|
unsafe { ZSTD_rust_windowUpdate(&mut contiguous_state, next_src.cast(), 3, 0) };
|
|
|
|
assert_eq!(contiguous, 1);
|
|
assert_eq!(contiguous_state.nextSrc as usize, storage_start + 21);
|
|
assert_eq!(contiguous_state.base as usize, storage_start + 16);
|
|
assert_eq!(contiguous_state.dictBase as usize, storage_start + 16);
|
|
assert_eq!(contiguous_state.dictLimit, 2);
|
|
assert_eq!(contiguous_state.lowLimit, 2);
|
|
|
|
let next_src = unsafe { base.add(6) };
|
|
let mut forced_state = ZSTD_rust_windowUpdateState {
|
|
nextSrc: next_src.cast(),
|
|
base: base.cast(),
|
|
dictBase: base.cast(),
|
|
dictLimit: 5,
|
|
lowLimit: 2,
|
|
};
|
|
|
|
let forced = unsafe { ZSTD_rust_windowUpdate(&mut forced_state, next_src.cast(), 3, 1) };
|
|
|
|
assert_eq!(forced, 0);
|
|
assert_eq!(forced_state.base as usize, storage_start + 16);
|
|
assert_eq!(forced_state.nextSrc as usize, storage_start + 25);
|
|
assert_eq!(forced_state.dictBase as usize, storage_start + 16);
|
|
assert_eq!(forced_state.dictLimit, 6);
|
|
assert_eq!(forced_state.lowLimit, 6);
|
|
|
|
let next_src = unsafe { base.add(20) };
|
|
let src = unsafe { base.add(4) };
|
|
let mut non_contiguous_state = ZSTD_rust_windowUpdateState {
|
|
nextSrc: next_src.cast(),
|
|
base: base.cast(),
|
|
dictBase: base.cast(),
|
|
dictLimit: 5,
|
|
lowLimit: 2,
|
|
};
|
|
|
|
let non_contiguous =
|
|
unsafe { ZSTD_rust_windowUpdate(&mut non_contiguous_state, src.cast(), 10, 0) };
|
|
|
|
assert_eq!(non_contiguous, 0);
|
|
assert_eq!(non_contiguous_state.base as usize, storage_start);
|
|
assert_eq!(non_contiguous_state.nextSrc as usize, storage_start + 30);
|
|
assert_eq!(non_contiguous_state.dictBase as usize, storage_start + 16);
|
|
assert_eq!(non_contiguous_state.dictLimit, 20);
|
|
assert_eq!(non_contiguous_state.lowLimit, 14);
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct ClearAllDictsTestContext {
|
|
events: Vec<&'static str>,
|
|
}
|
|
|
|
unsafe fn clear_all_dicts_test_context(
|
|
context: *mut c_void,
|
|
) -> &'static mut ClearAllDictsTestContext {
|
|
unsafe { &mut *context.cast::<ClearAllDictsTestContext>() }
|
|
}
|
|
|
|
unsafe extern "C" fn clear_all_dicts_test_free_local_dict_buffer(context: *mut c_void) {
|
|
unsafe { clear_all_dicts_test_context(context) }
|
|
.events
|
|
.push("free-local-buffer");
|
|
}
|
|
|
|
unsafe extern "C" fn clear_all_dicts_test_free_local_cdict(context: *mut c_void) {
|
|
unsafe { clear_all_dicts_test_context(context) }
|
|
.events
|
|
.push("free-local-cdict");
|
|
}
|
|
|
|
unsafe extern "C" fn clear_all_dicts_test_clear_local_dict(context: *mut c_void) {
|
|
unsafe { clear_all_dicts_test_context(context) }
|
|
.events
|
|
.push("clear-local");
|
|
}
|
|
|
|
unsafe extern "C" fn clear_all_dicts_test_clear_prefix_dict(context: *mut c_void) {
|
|
unsafe { clear_all_dicts_test_context(context) }
|
|
.events
|
|
.push("clear-prefix");
|
|
}
|
|
|
|
unsafe extern "C" fn clear_all_dicts_test_clear_cdict(context: *mut c_void) {
|
|
unsafe { clear_all_dicts_test_context(context) }
|
|
.events
|
|
.push("clear-cdict");
|
|
}
|
|
|
|
fn clear_all_dicts_test_state(
|
|
context: &mut ClearAllDictsTestContext,
|
|
) -> ZSTD_rust_clearAllDictsState {
|
|
ZSTD_rust_clearAllDictsState {
|
|
callback_context: (context as *mut ClearAllDictsTestContext).cast(),
|
|
free_local_dict_buffer: clear_all_dicts_test_free_local_dict_buffer,
|
|
free_local_cdict: clear_all_dicts_test_free_local_cdict,
|
|
clear_local_dict: clear_all_dicts_test_clear_local_dict,
|
|
clear_prefix_dict: clear_all_dicts_test_clear_prefix_dict,
|
|
clear_cdict: clear_all_dicts_test_clear_cdict,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn clear_all_dicts_preserves_dictionary_teardown_order() {
|
|
let mut context = ClearAllDictsTestContext::default();
|
|
let state = clear_all_dicts_test_state(&mut context);
|
|
|
|
unsafe { ZSTD_rust_clearAllDicts(&state) };
|
|
|
|
assert_eq!(
|
|
context.events,
|
|
[
|
|
"free-local-buffer",
|
|
"free-local-cdict",
|
|
"clear-local",
|
|
"clear-prefix",
|
|
"clear-cdict"
|
|
]
|
|
);
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct ResetCCtxTestContext {
|
|
events: Vec<&'static str>,
|
|
rust_simple_compress2_completed: c_uint,
|
|
stream_stage: c_int,
|
|
pledged_src_size_plus_one: u64,
|
|
rust_simple_compress2_max_block_size_set: c_uint,
|
|
reset_params_result: usize,
|
|
}
|
|
|
|
unsafe fn reset_cctx_test_context(context: *mut c_void) -> &'static mut ResetCCtxTestContext {
|
|
unsafe { &mut *context.cast::<ResetCCtxTestContext>() }
|
|
}
|
|
|
|
unsafe extern "C" fn reset_cctx_test_clear_all_dicts(context: *mut c_void) {
|
|
unsafe { reset_cctx_test_context(context) }
|
|
.events
|
|
.push("clear-dicts");
|
|
}
|
|
|
|
unsafe extern "C" fn reset_cctx_test_reset_params(context: *mut c_void) -> usize {
|
|
let context = unsafe { reset_cctx_test_context(context) };
|
|
assert_eq!(context.rust_simple_compress2_max_block_size_set, 0);
|
|
context.events.push("reset-params");
|
|
context.reset_params_result
|
|
}
|
|
|
|
fn reset_cctx_test_state(context: &mut ResetCCtxTestContext) -> ZSTD_rust_resetCCtxState {
|
|
ZSTD_rust_resetCCtxState {
|
|
callback_context: (context as *mut ResetCCtxTestContext).cast(),
|
|
rust_simple_compress2_completed: &mut context.rust_simple_compress2_completed,
|
|
stream_stage: &mut context.stream_stage,
|
|
pledged_src_size_plus_one: &mut context.pledged_src_size_plus_one,
|
|
rust_simple_compress2_max_block_size_set: &mut context
|
|
.rust_simple_compress2_max_block_size_set,
|
|
clear_all_dicts: reset_cctx_test_clear_all_dicts,
|
|
reset_params: reset_cctx_test_reset_params,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn reset_cctx_session_only_clears_session_state() {
|
|
let mut context = ResetCCtxTestContext {
|
|
rust_simple_compress2_completed: 1,
|
|
stream_stage: ZSTD_CSTREAM_STAGE_LOAD,
|
|
pledged_src_size_plus_one: 123,
|
|
rust_simple_compress2_max_block_size_set: 1,
|
|
..ResetCCtxTestContext::default()
|
|
};
|
|
let state = reset_cctx_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_resetCCtx(&state, ZSTD_RESET_SESSION_ONLY) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(context.rust_simple_compress2_completed, 0);
|
|
assert_eq!(context.stream_stage, ZSTD_CSTREAM_STAGE_INIT);
|
|
assert_eq!(context.pledged_src_size_plus_one, 0);
|
|
assert_eq!(context.rust_simple_compress2_max_block_size_set, 1);
|
|
assert!(context.events.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn reset_cctx_parameters_runs_callbacks_in_order_and_clears_max_block_flag() {
|
|
let mut context = ResetCCtxTestContext {
|
|
rust_simple_compress2_completed: 1,
|
|
stream_stage: ZSTD_CSTREAM_STAGE_INIT,
|
|
pledged_src_size_plus_one: 123,
|
|
rust_simple_compress2_max_block_size_set: 1,
|
|
..ResetCCtxTestContext::default()
|
|
};
|
|
let state = reset_cctx_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_resetCCtx(&state, ZSTD_RESET_PARAMETERS) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(context.rust_simple_compress2_completed, 0);
|
|
assert_eq!(context.stream_stage, ZSTD_CSTREAM_STAGE_INIT);
|
|
assert_eq!(context.pledged_src_size_plus_one, 123);
|
|
assert_eq!(context.rust_simple_compress2_max_block_size_set, 0);
|
|
assert_eq!(context.events, ["clear-dicts", "reset-params"]);
|
|
}
|
|
|
|
#[test]
|
|
fn reset_cctx_session_and_parameters_resets_session_before_callbacks() {
|
|
let mut context = ResetCCtxTestContext {
|
|
rust_simple_compress2_completed: 1,
|
|
stream_stage: ZSTD_CSTREAM_STAGE_LOAD,
|
|
pledged_src_size_plus_one: 123,
|
|
rust_simple_compress2_max_block_size_set: 1,
|
|
..ResetCCtxTestContext::default()
|
|
};
|
|
let state = reset_cctx_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_resetCCtx(&state, ZSTD_RESET_SESSION_AND_PARAMETERS) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(context.rust_simple_compress2_completed, 0);
|
|
assert_eq!(context.stream_stage, ZSTD_CSTREAM_STAGE_INIT);
|
|
assert_eq!(context.pledged_src_size_plus_one, 0);
|
|
assert_eq!(context.rust_simple_compress2_max_block_size_set, 0);
|
|
assert_eq!(context.events, ["clear-dicts", "reset-params"]);
|
|
}
|
|
|
|
#[test]
|
|
fn reset_cctx_parameters_rejects_non_init_stage_before_mutating_parameters() {
|
|
let mut context = ResetCCtxTestContext {
|
|
rust_simple_compress2_completed: 1,
|
|
stream_stage: ZSTD_CSTREAM_STAGE_LOAD,
|
|
pledged_src_size_plus_one: 123,
|
|
rust_simple_compress2_max_block_size_set: 1,
|
|
..ResetCCtxTestContext::default()
|
|
};
|
|
let state = reset_cctx_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_resetCCtx(&state, ZSTD_RESET_PARAMETERS) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::StageWrong));
|
|
assert_eq!(context.rust_simple_compress2_completed, 0);
|
|
assert_eq!(context.stream_stage, ZSTD_CSTREAM_STAGE_LOAD);
|
|
assert_eq!(context.pledged_src_size_plus_one, 123);
|
|
assert_eq!(context.rust_simple_compress2_max_block_size_set, 1);
|
|
assert!(context.events.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn reset_cctx_propagates_parameter_reset_error_after_clearing_state() {
|
|
let mut context = ResetCCtxTestContext {
|
|
rust_simple_compress2_completed: 1,
|
|
stream_stage: ZSTD_CSTREAM_STAGE_INIT,
|
|
pledged_src_size_plus_one: 123,
|
|
rust_simple_compress2_max_block_size_set: 1,
|
|
reset_params_result: ERROR(ZstdErrorCode::MemoryAllocation),
|
|
..ResetCCtxTestContext::default()
|
|
};
|
|
let state = reset_cctx_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_resetCCtx(&state, ZSTD_RESET_PARAMETERS) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::MemoryAllocation));
|
|
assert_eq!(context.rust_simple_compress2_completed, 0);
|
|
assert_eq!(context.rust_simple_compress2_max_block_size_set, 0);
|
|
assert_eq!(context.events, ["clear-dicts", "reset-params"]);
|
|
}
|
|
|
|
#[test]
|
|
fn reset_cctx_ignores_unknown_directives_after_clearing_completion() {
|
|
let mut context = ResetCCtxTestContext {
|
|
rust_simple_compress2_completed: 1,
|
|
stream_stage: ZSTD_CSTREAM_STAGE_LOAD,
|
|
pledged_src_size_plus_one: 123,
|
|
rust_simple_compress2_max_block_size_set: 1,
|
|
..ResetCCtxTestContext::default()
|
|
};
|
|
let state = reset_cctx_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_resetCCtx(&state, 99) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(context.rust_simple_compress2_completed, 0);
|
|
assert_eq!(context.stream_stage, ZSTD_CSTREAM_STAGE_LOAD);
|
|
assert_eq!(context.pledged_src_size_plus_one, 123);
|
|
assert_eq!(context.rust_simple_compress2_max_block_size_set, 1);
|
|
assert!(context.events.is_empty());
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct CopyCCtxTestContext {
|
|
events: Vec<&'static str>,
|
|
src_cctx: *const c_void,
|
|
frame_params: ZSTD_frameParameters,
|
|
pledged_src_size: u64,
|
|
zbuff: c_int,
|
|
result: usize,
|
|
}
|
|
|
|
unsafe extern "C" fn copy_cctx_test_internal(
|
|
context: *mut c_void,
|
|
src_cctx: *const c_void,
|
|
f_params: *const ZSTD_frameParameters,
|
|
pledged_src_size: u64,
|
|
zbuff: c_int,
|
|
) -> usize {
|
|
let context = unsafe { &mut *context.cast::<CopyCCtxTestContext>() };
|
|
context.events.push("copy");
|
|
context.src_cctx = src_cctx;
|
|
context.frame_params = unsafe { *f_params };
|
|
context.pledged_src_size = pledged_src_size;
|
|
context.zbuff = zbuff;
|
|
context.result
|
|
}
|
|
|
|
fn copy_cctx_test_state(
|
|
context: &mut CopyCCtxTestContext,
|
|
src_cctx: *const c_void,
|
|
f_params: &ZSTD_frameParameters,
|
|
pledged_src_size: &u64,
|
|
zbuff: &c_int,
|
|
) -> ZSTD_rust_copyCCtxState {
|
|
ZSTD_rust_copyCCtxState {
|
|
callback_context: (context as *mut CopyCCtxTestContext).cast(),
|
|
src_cctx,
|
|
f_params,
|
|
pledged_src_size,
|
|
zbuff,
|
|
copy_internal: Some(copy_cctx_test_internal),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn copy_cctx_converts_zero_pledge_to_unknown_and_clears_content_flag() {
|
|
let mut context = CopyCCtxTestContext::default();
|
|
let f_params = ZSTD_frameParameters {
|
|
contentSizeFlag: 1,
|
|
checksumFlag: 1,
|
|
noDictIDFlag: 1,
|
|
};
|
|
let pledged_src_size = 0;
|
|
let zbuff = 1;
|
|
let src_cctx = 0x4000usize as *const c_void;
|
|
let state =
|
|
copy_cctx_test_state(&mut context, src_cctx, &f_params, &pledged_src_size, &zbuff);
|
|
|
|
let result = unsafe { ZSTD_rust_copyCCtx(&state) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(context.events, ["copy"]);
|
|
assert_eq!(context.src_cctx, src_cctx);
|
|
assert_eq!(context.pledged_src_size, ZSTD_CONTENTSIZE_UNKNOWN);
|
|
assert_eq!(context.zbuff, zbuff);
|
|
assert_eq!(context.frame_params.contentSizeFlag, 0);
|
|
assert_eq!(context.frame_params.checksumFlag, 1);
|
|
assert_eq!(context.frame_params.noDictIDFlag, 1);
|
|
}
|
|
|
|
#[test]
|
|
fn copy_cctx_forwards_nonzero_pledge_and_propagates_internal_error() {
|
|
let mut context = CopyCCtxTestContext {
|
|
result: ERROR(ZstdErrorCode::MemoryAllocation),
|
|
..CopyCCtxTestContext::default()
|
|
};
|
|
let f_params = ZSTD_frameParameters {
|
|
contentSizeFlag: 0,
|
|
checksumFlag: 1,
|
|
noDictIDFlag: 1,
|
|
};
|
|
let pledged_src_size = 123;
|
|
let zbuff = 7;
|
|
let state = copy_cctx_test_state(
|
|
&mut context,
|
|
0x4000usize as *const c_void,
|
|
&f_params,
|
|
&pledged_src_size,
|
|
&zbuff,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_copyCCtx(&state) };
|
|
|
|
assert_eq!(result, context.result);
|
|
assert_eq!(context.events, ["copy"]);
|
|
assert_eq!(context.pledged_src_size, pledged_src_size);
|
|
assert_eq!(context.frame_params.contentSizeFlag, 1);
|
|
assert_eq!(context.frame_params.checksumFlag, 1);
|
|
assert_eq!(context.frame_params.noDictIDFlag, 1);
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct CopyCCtxInternalTestContext {
|
|
events: Vec<&'static str>,
|
|
reset_result: usize,
|
|
frame_params: ZSTD_frameParameters,
|
|
pledged_src_size: u64,
|
|
zbuff: c_int,
|
|
}
|
|
|
|
unsafe fn copy_cctx_internal_test_context(
|
|
context: *mut c_void,
|
|
) -> &'static mut CopyCCtxInternalTestContext {
|
|
unsafe { &mut *context.cast::<CopyCCtxInternalTestContext>() }
|
|
}
|
|
|
|
unsafe extern "C" fn copy_cctx_internal_test_reset(
|
|
context: *mut c_void,
|
|
_src_cctx: *const c_void,
|
|
f_params: *const ZSTD_frameParameters,
|
|
pledged_src_size: u64,
|
|
zbuff: c_int,
|
|
) -> usize {
|
|
let context = unsafe { copy_cctx_internal_test_context(context) };
|
|
context.events.push("reset");
|
|
context.frame_params = unsafe { *f_params };
|
|
context.pledged_src_size = pledged_src_size;
|
|
context.zbuff = zbuff;
|
|
context.reset_result
|
|
}
|
|
|
|
unsafe extern "C" fn copy_cctx_internal_test_mark_tables_dirty(context: *mut c_void) {
|
|
unsafe { copy_cctx_internal_test_context(context) }
|
|
.events
|
|
.push("dirty");
|
|
}
|
|
|
|
unsafe extern "C" fn copy_cctx_internal_test_mark_tables_clean(context: *mut c_void) {
|
|
unsafe { copy_cctx_internal_test_context(context) }
|
|
.events
|
|
.push("clean");
|
|
}
|
|
|
|
fn copy_cctx_internal_test_state(
|
|
context: &mut CopyCCtxInternalTestContext,
|
|
src_cctx: *const c_void,
|
|
f_params: &ZSTD_frameParameters,
|
|
source_stage: *const c_int,
|
|
destination_custom_mem: *mut c_void,
|
|
source_custom_mem: *const c_void,
|
|
destination_hash_table: *mut *mut c_uint,
|
|
source_hash_table: *const c_uint,
|
|
source_hash_log: *const c_uint,
|
|
destination_chain_table: *mut *mut c_uint,
|
|
source_chain_table: *const c_uint,
|
|
source_chain_log: *const c_uint,
|
|
destination_hash_table3: *mut *mut c_uint,
|
|
source_hash_table3: *const c_uint,
|
|
source_hash_log3: *const c_uint,
|
|
source_strategy: *const c_int,
|
|
source_use_row_match_finder: *const c_int,
|
|
destination_window: *mut c_void,
|
|
source_window: *const c_void,
|
|
destination_next_to_update: *mut c_uint,
|
|
source_next_to_update: *const c_uint,
|
|
destination_loaded_dict_end: *mut c_uint,
|
|
source_loaded_dict_end: *const c_uint,
|
|
destination_dict_id: *mut c_uint,
|
|
source_dict_id: *const c_uint,
|
|
destination_dict_content_size: *mut usize,
|
|
source_dict_content_size: *const usize,
|
|
destination_block_state: *mut *mut ZSTD_compressedBlockState_t,
|
|
source_block_state: *const ZSTD_compressedBlockState_t,
|
|
) -> ZSTD_rust_copyCCtxInternalState {
|
|
ZSTD_rust_copyCCtxInternalState {
|
|
callback_context: (context as *mut CopyCCtxInternalTestContext).cast(),
|
|
src_cctx,
|
|
f_params,
|
|
pledged_src_size: 123,
|
|
source_stage,
|
|
destination_custom_mem,
|
|
source_custom_mem,
|
|
reset: Some(copy_cctx_internal_test_reset),
|
|
mark_tables_dirty: Some(copy_cctx_internal_test_mark_tables_dirty),
|
|
mark_tables_clean: Some(copy_cctx_internal_test_mark_tables_clean),
|
|
destination_hash_table,
|
|
source_hash_table,
|
|
source_hash_log,
|
|
destination_chain_table,
|
|
source_chain_table,
|
|
source_chain_log,
|
|
destination_hash_table3,
|
|
source_hash_table3,
|
|
source_hash_log3,
|
|
source_strategy,
|
|
source_use_row_match_finder,
|
|
destination_window,
|
|
source_window,
|
|
destination_next_to_update,
|
|
source_next_to_update,
|
|
destination_loaded_dict_end,
|
|
source_loaded_dict_end,
|
|
destination_dict_id,
|
|
source_dict_id,
|
|
destination_dict_content_size,
|
|
source_dict_content_size,
|
|
destination_block_state,
|
|
source_block_state,
|
|
zbuff: 7,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn copy_cctx_internal_runs_private_callbacks_in_original_order() {
|
|
let mut context = CopyCCtxInternalTestContext::default();
|
|
let f_params = ZSTD_frameParameters {
|
|
contentSizeFlag: 1,
|
|
checksumFlag: 1,
|
|
noDictIDFlag: 1,
|
|
};
|
|
let source_stage = ZSTD_COMPRESSION_STAGE_INIT;
|
|
let source_custom_mem = [0x11usize, 0x22, 0x33];
|
|
let mut destination_custom_mem = [0usize; 3];
|
|
let source_hash_table = [11u32, 12, 13, 14];
|
|
let mut destination_hash_table = [0u32; 4];
|
|
let mut destination_hash_table_ptr = destination_hash_table.as_mut_ptr();
|
|
let source_hash_log = 2;
|
|
let source_chain_table = [21u32, 22];
|
|
let mut destination_chain_table = [0u32; 2];
|
|
let mut destination_chain_table_ptr = destination_chain_table.as_mut_ptr();
|
|
let source_chain_log = 1;
|
|
let source_hash_table3 = [31u32, 32];
|
|
let mut destination_hash_table3 = [0u32; 2];
|
|
let mut destination_hash_table3_ptr = destination_hash_table3.as_mut_ptr();
|
|
let source_hash_log3 = 1;
|
|
let source_strategy = ZSTD_DFAST;
|
|
let source_use_row_match_finder = ZSTD_RUST_PS_DISABLE;
|
|
let mut source_block_state =
|
|
unsafe { MaybeUninit::<ZSTD_compressedBlockState_t>::zeroed().assume_init() };
|
|
source_block_state.rep = [15, 16, 17];
|
|
source_block_state.entropy.huf.repeatMode = 2;
|
|
source_block_state.entropy.fse.offcode_repeatMode = FSE_REPEAT_VALID;
|
|
let source_window = ZSTD_rust_copyWindowState {
|
|
next_src: 0x1000usize as *const c_void,
|
|
base: 0x2000usize as *const c_void,
|
|
dict_base: 0x3000usize as *const c_void,
|
|
dict_limit: 21,
|
|
low_limit: 13,
|
|
nb_overflow_corrections: 8,
|
|
};
|
|
let mut destination_window = ZSTD_rust_copyWindowState {
|
|
next_src: ptr::null(),
|
|
base: ptr::null(),
|
|
dict_base: ptr::null(),
|
|
dict_limit: 0,
|
|
low_limit: 0,
|
|
nb_overflow_corrections: 0,
|
|
};
|
|
let source_next_to_update = 34;
|
|
let mut destination_next_to_update = 0;
|
|
let source_loaded_dict_end = 55;
|
|
let mut destination_loaded_dict_end = 0;
|
|
let mut destination_block_state =
|
|
unsafe { MaybeUninit::<ZSTD_compressedBlockState_t>::zeroed().assume_init() };
|
|
let mut destination_block_state_slot =
|
|
&mut destination_block_state as *mut ZSTD_compressedBlockState_t;
|
|
let source_dict_id = 0x1234_5678;
|
|
let mut destination_dict_id = 0;
|
|
let source_dict_content_size = 9876;
|
|
let mut destination_dict_content_size = 0;
|
|
let state = copy_cctx_internal_test_state(
|
|
&mut context,
|
|
0x5000usize as *const c_void,
|
|
&f_params,
|
|
&source_stage,
|
|
(&mut destination_custom_mem as *mut [usize; 3]).cast(),
|
|
(&source_custom_mem as *const [usize; 3]).cast(),
|
|
&mut destination_hash_table_ptr,
|
|
source_hash_table.as_ptr(),
|
|
&source_hash_log,
|
|
&mut destination_chain_table_ptr,
|
|
source_chain_table.as_ptr(),
|
|
&source_chain_log,
|
|
&mut destination_hash_table3_ptr,
|
|
source_hash_table3.as_ptr(),
|
|
&source_hash_log3,
|
|
&source_strategy,
|
|
&source_use_row_match_finder,
|
|
(&mut destination_window as *mut ZSTD_rust_copyWindowState).cast(),
|
|
(&source_window as *const ZSTD_rust_copyWindowState).cast(),
|
|
&mut destination_next_to_update,
|
|
&source_next_to_update,
|
|
&mut destination_loaded_dict_end,
|
|
&source_loaded_dict_end,
|
|
&mut destination_dict_id,
|
|
&source_dict_id,
|
|
&mut destination_dict_content_size,
|
|
&source_dict_content_size,
|
|
&mut destination_block_state_slot,
|
|
&source_block_state,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_copyCCtxInternal(&state) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(context.events, ["reset", "dirty", "clean"]);
|
|
assert_eq!(context.frame_params, f_params);
|
|
assert_eq!(context.pledged_src_size, 123);
|
|
assert_eq!(context.zbuff, 7);
|
|
assert_eq!(destination_custom_mem, source_custom_mem);
|
|
assert_eq!(destination_hash_table, source_hash_table);
|
|
assert_eq!(destination_chain_table, source_chain_table);
|
|
assert_eq!(destination_hash_table3, source_hash_table3);
|
|
assert_eq!(destination_window, source_window);
|
|
assert_eq!(destination_next_to_update, source_next_to_update);
|
|
assert_eq!(destination_loaded_dict_end, source_loaded_dict_end);
|
|
assert_eq!(destination_dict_id, source_dict_id);
|
|
assert_eq!(destination_dict_content_size, source_dict_content_size);
|
|
assert_eq!(destination_block_state.rep, source_block_state.rep);
|
|
assert_eq!(
|
|
destination_block_state.entropy.huf.repeatMode,
|
|
source_block_state.entropy.huf.repeatMode
|
|
);
|
|
assert_eq!(
|
|
destination_block_state.entropy.fse.offcode_repeatMode,
|
|
source_block_state.entropy.fse.offcode_repeatMode
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn copy_cctx_internal_rejects_stage_before_mutation() {
|
|
let mut context = CopyCCtxInternalTestContext::default();
|
|
let f_params = ZSTD_frameParameters::default();
|
|
let source_stage = ZSTD_COMPRESSION_STAGE_ONGOING;
|
|
let source_custom_mem = [0usize; 3];
|
|
let mut destination_custom_mem = [0usize; 3];
|
|
let mut destination_block_state =
|
|
unsafe { MaybeUninit::<ZSTD_compressedBlockState_t>::zeroed().assume_init() };
|
|
let mut destination_block_state_slot =
|
|
&mut destination_block_state as *mut ZSTD_compressedBlockState_t;
|
|
let source_block_state =
|
|
unsafe { MaybeUninit::<ZSTD_compressedBlockState_t>::zeroed().assume_init() };
|
|
let source_dict_id = 0x1234_5678;
|
|
let mut destination_dict_id = 0;
|
|
let source_dict_content_size = 9876;
|
|
let mut destination_dict_content_size = 0;
|
|
let state = copy_cctx_internal_test_state(
|
|
&mut context,
|
|
0x5000usize as *const c_void,
|
|
&f_params,
|
|
&source_stage,
|
|
(&mut destination_custom_mem as *mut [usize; 3]).cast(),
|
|
(&source_custom_mem as *const [usize; 3]).cast(),
|
|
ptr::dangling_mut(),
|
|
ptr::dangling(),
|
|
ptr::dangling(),
|
|
ptr::dangling_mut(),
|
|
ptr::dangling(),
|
|
ptr::dangling(),
|
|
ptr::dangling_mut(),
|
|
ptr::dangling(),
|
|
ptr::dangling(),
|
|
ptr::dangling(),
|
|
ptr::dangling(),
|
|
ptr::dangling_mut(),
|
|
ptr::dangling(),
|
|
ptr::dangling_mut(),
|
|
ptr::dangling(),
|
|
ptr::dangling_mut(),
|
|
ptr::dangling(),
|
|
&mut destination_dict_id,
|
|
&source_dict_id,
|
|
&mut destination_dict_content_size,
|
|
&source_dict_content_size,
|
|
&mut destination_block_state_slot,
|
|
&source_block_state,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_copyCCtxInternal(&state) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::StageWrong));
|
|
assert!(context.events.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn copy_cctx_internal_propagates_reset_error_before_table_mutation() {
|
|
let mut context = CopyCCtxInternalTestContext {
|
|
reset_result: ERROR(ZstdErrorCode::MemoryAllocation),
|
|
..CopyCCtxInternalTestContext::default()
|
|
};
|
|
let f_params = ZSTD_frameParameters::default();
|
|
let source_stage = ZSTD_COMPRESSION_STAGE_INIT;
|
|
let source_custom_mem = [0usize; 3];
|
|
let mut destination_custom_mem = [0usize; 3];
|
|
let source_hash_log = 0;
|
|
let source_chain_log = 0;
|
|
let source_hash_log3 = 0;
|
|
let source_strategy = ZSTD_DFAST;
|
|
let source_use_row_match_finder = ZSTD_RUST_PS_DISABLE;
|
|
let mut destination_block_state =
|
|
unsafe { MaybeUninit::<ZSTD_compressedBlockState_t>::zeroed().assume_init() };
|
|
let mut destination_block_state_slot =
|
|
&mut destination_block_state as *mut ZSTD_compressedBlockState_t;
|
|
let source_block_state =
|
|
unsafe { MaybeUninit::<ZSTD_compressedBlockState_t>::zeroed().assume_init() };
|
|
let source_dict_id = 0x1234_5678;
|
|
let mut destination_dict_id = 0;
|
|
let source_dict_content_size = 9876;
|
|
let mut destination_dict_content_size = 0;
|
|
let state = copy_cctx_internal_test_state(
|
|
&mut context,
|
|
0x5000usize as *const c_void,
|
|
&f_params,
|
|
&source_stage,
|
|
(&mut destination_custom_mem as *mut [usize; 3]).cast(),
|
|
(&source_custom_mem as *const [usize; 3]).cast(),
|
|
ptr::dangling_mut(),
|
|
ptr::dangling(),
|
|
&source_hash_log,
|
|
ptr::dangling_mut(),
|
|
ptr::dangling(),
|
|
&source_chain_log,
|
|
ptr::dangling_mut(),
|
|
ptr::dangling(),
|
|
&source_hash_log3,
|
|
&source_strategy,
|
|
&source_use_row_match_finder,
|
|
ptr::dangling_mut(),
|
|
ptr::dangling(),
|
|
ptr::dangling_mut(),
|
|
ptr::dangling(),
|
|
ptr::dangling_mut(),
|
|
ptr::dangling(),
|
|
&mut destination_dict_id,
|
|
&source_dict_id,
|
|
&mut destination_dict_content_size,
|
|
&source_dict_content_size,
|
|
&mut destination_block_state_slot,
|
|
&source_block_state,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_copyCCtxInternal(&state) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::MemoryAllocation));
|
|
assert_eq!(context.events, ["reset"]);
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct CompressAdvancedTestContext {
|
|
events: Vec<&'static str>,
|
|
init_params: ZSTD_parameters,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
result: usize,
|
|
}
|
|
|
|
unsafe extern "C" fn compress_advanced_test_init(
|
|
context: *mut c_void,
|
|
params: *const ZSTD_parameters,
|
|
) {
|
|
let context = unsafe { &mut *context.cast::<CompressAdvancedTestContext>() };
|
|
context.events.push("init");
|
|
context.init_params = unsafe { *params };
|
|
}
|
|
|
|
unsafe extern "C" fn compress_advanced_test_internal(
|
|
context: *mut c_void,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
) -> usize {
|
|
let context = unsafe { &mut *context.cast::<CompressAdvancedTestContext>() };
|
|
context.events.push("compress");
|
|
context.dst = dst;
|
|
context.dst_capacity = dst_capacity;
|
|
context.src = src;
|
|
context.src_size = src_size;
|
|
context.dict = dict;
|
|
context.dict_size = dict_size;
|
|
context.result
|
|
}
|
|
|
|
fn compress_advanced_test_state(
|
|
context: &mut CompressAdvancedTestContext,
|
|
) -> ZSTD_rust_compressAdvancedState {
|
|
ZSTD_rust_compressAdvancedState {
|
|
callback_context: (context as *mut CompressAdvancedTestContext).cast(),
|
|
init_params: Some(compress_advanced_test_init),
|
|
compress_internal: Some(compress_advanced_test_internal),
|
|
}
|
|
}
|
|
|
|
fn compress_advanced_test_params() -> ZSTD_parameters {
|
|
ZSTD_parameters {
|
|
cParams: ZSTD_compressionParameters {
|
|
windowLog: 10,
|
|
chainLog: 11,
|
|
hashLog: 12,
|
|
searchLog: 13,
|
|
minMatch: 4,
|
|
targetLength: 5,
|
|
strategy: 1,
|
|
},
|
|
fParams: ZSTD_frameParameters {
|
|
contentSizeFlag: 1,
|
|
checksumFlag: 1,
|
|
noDictIDFlag: 0,
|
|
},
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn compress_advanced_validates_then_initializes_and_compresses() {
|
|
let mut context = CompressAdvancedTestContext {
|
|
result: 17,
|
|
..CompressAdvancedTestContext::default()
|
|
};
|
|
let state = compress_advanced_test_state(&mut context);
|
|
let params = compress_advanced_test_params();
|
|
let mut dst = [0u8; 8];
|
|
let src = [1u8, 2, 3];
|
|
let dict = [4u8, 5];
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_compressAdvanced(
|
|
&state,
|
|
dst.as_mut_ptr().cast(),
|
|
dst.len(),
|
|
src.as_ptr().cast(),
|
|
src.len(),
|
|
dict.as_ptr().cast(),
|
|
dict.len(),
|
|
¶ms,
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, context.result);
|
|
assert_eq!(context.events, ["init", "compress"]);
|
|
assert_eq!(context.init_params, params);
|
|
assert_eq!(context.dst, dst.as_mut_ptr().cast());
|
|
assert_eq!(context.dst_capacity, dst.len());
|
|
assert_eq!(context.src, src.as_ptr().cast());
|
|
assert_eq!(context.src_size, src.len());
|
|
assert_eq!(context.dict, dict.as_ptr().cast());
|
|
assert_eq!(context.dict_size, dict.len());
|
|
}
|
|
|
|
#[test]
|
|
fn compress_advanced_stops_before_callbacks_on_invalid_parameters() {
|
|
let mut context = CompressAdvancedTestContext::default();
|
|
let state = compress_advanced_test_state(&mut context);
|
|
let mut params = compress_advanced_test_params();
|
|
params.cParams.windowLog = 0;
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_compressAdvanced(
|
|
&state,
|
|
ptr::null_mut(),
|
|
0,
|
|
ptr::null(),
|
|
0,
|
|
ptr::null(),
|
|
0,
|
|
¶ms,
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::ParameterOutOfBound));
|
|
assert!(context.events.is_empty());
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct CompressBeginAdvancedTestContext {
|
|
events: Vec<&'static str>,
|
|
params: ZSTD_parameters,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
cctx_params: *const c_void,
|
|
pledged_src_size: u64,
|
|
result: usize,
|
|
}
|
|
|
|
unsafe extern "C" fn compress_begin_advanced_test_init(
|
|
context: *mut c_void,
|
|
params: *const ZSTD_parameters,
|
|
) {
|
|
let context = unsafe { &mut *context.cast::<CompressBeginAdvancedTestContext>() };
|
|
context.events.push("init");
|
|
context.params = unsafe { *params };
|
|
}
|
|
|
|
unsafe extern "C" fn compress_begin_advanced_test_begin(
|
|
context: *mut c_void,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
cctx_params: *const c_void,
|
|
pledged_src_size: u64,
|
|
) -> usize {
|
|
let context = unsafe { &mut *context.cast::<CompressBeginAdvancedTestContext>() };
|
|
context.events.push("begin");
|
|
context.dict = dict;
|
|
context.dict_size = dict_size;
|
|
context.cctx_params = cctx_params;
|
|
context.pledged_src_size = pledged_src_size;
|
|
context.result
|
|
}
|
|
|
|
fn compress_begin_advanced_test_state(
|
|
context: &mut CompressBeginAdvancedTestContext,
|
|
) -> ZSTD_rust_compressBeginAdvancedState {
|
|
ZSTD_rust_compressBeginAdvancedState {
|
|
cctx: (context as *mut CompressBeginAdvancedTestContext).cast(),
|
|
cctx_params: (context as *mut CompressBeginAdvancedTestContext).cast(),
|
|
init_params: compress_begin_advanced_test_init,
|
|
begin: compress_begin_advanced_test_begin,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn compress_begin_advanced_validates_and_preserves_init_begin_order() {
|
|
let mut context = CompressBeginAdvancedTestContext {
|
|
result: 29,
|
|
..CompressBeginAdvancedTestContext::default()
|
|
};
|
|
let state = compress_begin_advanced_test_state(&mut context);
|
|
let params = compress_advanced_test_params();
|
|
let dict = [1u8, 2, 3];
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_compressBeginAdvanced(&state, dict.as_ptr().cast(), dict.len(), ¶ms, 123)
|
|
};
|
|
|
|
assert_eq!(result, context.result);
|
|
assert_eq!(context.events, ["init", "begin"]);
|
|
assert_eq!(context.params, params);
|
|
assert_eq!(context.dict, dict.as_ptr().cast());
|
|
assert_eq!(context.dict_size, dict.len());
|
|
assert_eq!(
|
|
context.cctx_params,
|
|
(&context as *const CompressBeginAdvancedTestContext).cast()
|
|
);
|
|
assert_eq!(context.pledged_src_size, 123);
|
|
}
|
|
|
|
#[test]
|
|
fn compress_begin_advanced_stops_before_callbacks_on_invalid_parameters() {
|
|
let mut context = CompressBeginAdvancedTestContext::default();
|
|
let state = compress_begin_advanced_test_state(&mut context);
|
|
let mut params = compress_advanced_test_params();
|
|
params.cParams.windowLog = 0;
|
|
|
|
let result = unsafe { ZSTD_rust_compressBeginAdvanced(&state, ptr::null(), 0, ¶ms, 0) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::ParameterOutOfBound));
|
|
assert!(context.events.is_empty());
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct CompressUsingDictTestContext {
|
|
events: Vec<&'static str>,
|
|
init_params: ZSTD_parameters,
|
|
init_level: c_int,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
result: usize,
|
|
}
|
|
|
|
unsafe extern "C" fn compress_using_dict_test_init(
|
|
context: *mut c_void,
|
|
params: *const ZSTD_parameters,
|
|
compression_level: c_int,
|
|
) {
|
|
let context = unsafe { &mut *context.cast::<CompressUsingDictTestContext>() };
|
|
context.events.push("init");
|
|
context.init_params = unsafe { *params };
|
|
context.init_level = compression_level;
|
|
}
|
|
|
|
unsafe extern "C" fn compress_using_dict_test_internal(
|
|
context: *mut c_void,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
) -> usize {
|
|
let context = unsafe { &mut *context.cast::<CompressUsingDictTestContext>() };
|
|
context.events.push("compress");
|
|
context.dst = dst;
|
|
context.dst_capacity = dst_capacity;
|
|
context.src = src;
|
|
context.src_size = src_size;
|
|
context.dict = dict;
|
|
context.dict_size = dict_size;
|
|
context.result
|
|
}
|
|
|
|
fn compress_using_dict_test_state(
|
|
context: &mut CompressUsingDictTestContext,
|
|
exclusion_mask: &u32,
|
|
) -> ZSTD_rust_compressUsingDictState {
|
|
ZSTD_rust_compressUsingDictState {
|
|
callback_context: (context as *mut CompressUsingDictTestContext).cast(),
|
|
exclusion_mask,
|
|
init_params: Some(compress_using_dict_test_init),
|
|
compress_internal: Some(compress_using_dict_test_internal),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn compress_using_dict_selects_params_and_normalizes_default_level() {
|
|
let mut context = CompressUsingDictTestContext {
|
|
result: 23,
|
|
..CompressUsingDictTestContext::default()
|
|
};
|
|
let exclusion_mask = 0;
|
|
let state = compress_using_dict_test_state(&mut context, &exclusion_mask);
|
|
let mut dst = [0u8; 8];
|
|
let src = [1u8, 2, 3];
|
|
let dict = [4u8, 5];
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_compressUsingDict(
|
|
&state,
|
|
dst.as_mut_ptr().cast(),
|
|
dst.len(),
|
|
src.as_ptr().cast(),
|
|
src.len(),
|
|
dict.as_ptr().cast(),
|
|
dict.len(),
|
|
0,
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, context.result);
|
|
assert_eq!(context.events, ["init", "compress"]);
|
|
assert_eq!(
|
|
context.init_params,
|
|
ZSTD_rust_params_getParamsInternal(
|
|
0,
|
|
src.len() as u64,
|
|
dict.len(),
|
|
ZSTD_RUST_CPM_NO_ATTACH_DICT,
|
|
exclusion_mask,
|
|
)
|
|
);
|
|
assert_eq!(context.init_level, ZSTD_rust_params_defaultCLevel());
|
|
assert_eq!(context.dst, dst.as_mut_ptr().cast());
|
|
assert_eq!(context.dst_capacity, dst.len());
|
|
assert_eq!(context.src, src.as_ptr().cast());
|
|
assert_eq!(context.src_size, src.len());
|
|
assert_eq!(context.dict, dict.as_ptr().cast());
|
|
assert_eq!(context.dict_size, dict.len());
|
|
}
|
|
|
|
#[test]
|
|
fn compress_using_dict_ignores_size_without_a_dictionary_buffer_for_params() {
|
|
let mut context = CompressUsingDictTestContext::default();
|
|
let exclusion_mask = 0;
|
|
let state = compress_using_dict_test_state(&mut context, &exclusion_mask);
|
|
let expected = ZSTD_rust_params_getParamsInternal(
|
|
3,
|
|
0,
|
|
0,
|
|
ZSTD_RUST_CPM_NO_ATTACH_DICT,
|
|
exclusion_mask,
|
|
);
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_compressUsingDict(
|
|
&state,
|
|
ptr::null_mut(),
|
|
0,
|
|
ptr::null(),
|
|
0,
|
|
ptr::null(),
|
|
17,
|
|
3,
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(context.events, ["init", "compress"]);
|
|
assert_eq!(context.init_params, expected);
|
|
assert_eq!(context.dict, ptr::null());
|
|
assert_eq!(context.dict_size, 17);
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct ResetCStreamTestContext {
|
|
events: Vec<&'static str>,
|
|
reset_result: usize,
|
|
pledged_result: usize,
|
|
pledged_src_size: u64,
|
|
}
|
|
|
|
unsafe extern "C" fn reset_cstream_test_reset(context: *mut c_void) -> usize {
|
|
let context = unsafe { &mut *context.cast::<ResetCStreamTestContext>() };
|
|
context.events.push("reset");
|
|
context.reset_result
|
|
}
|
|
|
|
unsafe extern "C" fn reset_cstream_test_set_pledged_src_size(
|
|
context: *mut c_void,
|
|
pledged_src_size: u64,
|
|
) -> usize {
|
|
let context = unsafe { &mut *context.cast::<ResetCStreamTestContext>() };
|
|
context.events.push("pledged");
|
|
context.pledged_src_size = pledged_src_size;
|
|
context.pledged_result
|
|
}
|
|
|
|
fn reset_cstream_test_state(
|
|
context: &mut ResetCStreamTestContext,
|
|
) -> ZSTD_rust_resetCStreamState {
|
|
ZSTD_rust_resetCStreamState {
|
|
callback_context: (context as *mut ResetCStreamTestContext).cast(),
|
|
reset_session: reset_cstream_test_reset,
|
|
set_pledged_src_size: reset_cstream_test_set_pledged_src_size,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn reset_cstream_converts_zero_pledge_to_unknown_after_reset() {
|
|
let mut context = ResetCStreamTestContext::default();
|
|
let state = reset_cstream_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_resetCStream(&state, 0) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(context.events, ["reset", "pledged"]);
|
|
assert_eq!(context.pledged_src_size, ZSTD_CONTENTSIZE_UNKNOWN);
|
|
}
|
|
|
|
#[test]
|
|
fn reset_cstream_forwards_nonzero_pledge() {
|
|
let mut context = ResetCStreamTestContext::default();
|
|
let state = reset_cstream_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_resetCStream(&state, 123) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(context.events, ["reset", "pledged"]);
|
|
assert_eq!(context.pledged_src_size, 123);
|
|
}
|
|
|
|
#[test]
|
|
fn reset_cstream_stops_before_pledge_after_reset_error() {
|
|
let mut context = ResetCStreamTestContext {
|
|
reset_result: ERROR(ZstdErrorCode::MemoryAllocation),
|
|
..ResetCStreamTestContext::default()
|
|
};
|
|
let state = reset_cstream_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_resetCStream(&state, 123) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::MemoryAllocation));
|
|
assert_eq!(context.events, ["reset"]);
|
|
assert_eq!(context.pledged_src_size, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn reset_cstream_propagates_pledge_error_after_reset() {
|
|
let mut context = ResetCStreamTestContext {
|
|
pledged_result: ERROR(ZstdErrorCode::StageWrong),
|
|
..ResetCStreamTestContext::default()
|
|
};
|
|
let state = reset_cstream_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_resetCStream(&state, 123) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::StageWrong));
|
|
assert_eq!(context.events, ["reset", "pledged"]);
|
|
assert_eq!(context.pledged_src_size, 123);
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct InitCStreamUsingCDictAdvancedTestContext {
|
|
events: Vec<&'static str>,
|
|
reset_result: usize,
|
|
pledged_result: usize,
|
|
set_level_result: usize,
|
|
load_dict_result: usize,
|
|
check_params_result: usize,
|
|
ref_result: usize,
|
|
pledged_src_size: u64,
|
|
compression_level: c_int,
|
|
frame_params: [c_uint; 3],
|
|
cdict: *const c_void,
|
|
dict_size: usize,
|
|
zstd_params: ZSTD_parameters,
|
|
}
|
|
|
|
unsafe fn init_cstream_using_cdict_advanced_test_context(
|
|
context: *mut c_void,
|
|
) -> &'static mut InitCStreamUsingCDictAdvancedTestContext {
|
|
unsafe { &mut *context.cast::<InitCStreamUsingCDictAdvancedTestContext>() }
|
|
}
|
|
|
|
unsafe extern "C" fn init_cstream_using_cdict_advanced_test_reset(
|
|
context: *mut c_void,
|
|
) -> usize {
|
|
let context = unsafe { init_cstream_using_cdict_advanced_test_context(context) };
|
|
context.events.push("reset");
|
|
context.reset_result
|
|
}
|
|
|
|
unsafe extern "C" fn init_cstream_using_cdict_advanced_test_set_pledged(
|
|
context: *mut c_void,
|
|
pledged_src_size: u64,
|
|
) -> usize {
|
|
let context = unsafe { init_cstream_using_cdict_advanced_test_context(context) };
|
|
context.events.push("pledged");
|
|
context.pledged_src_size = pledged_src_size;
|
|
context.pledged_result
|
|
}
|
|
|
|
unsafe extern "C" fn init_cstream_src_size_test_set_level(
|
|
context: *mut c_void,
|
|
compression_level: c_int,
|
|
) -> usize {
|
|
let context = unsafe { init_cstream_using_cdict_advanced_test_context(context) };
|
|
context.events.push("level");
|
|
context.compression_level = compression_level;
|
|
context.set_level_result
|
|
}
|
|
|
|
unsafe extern "C" fn init_cstream_using_dict_test_load_dictionary(
|
|
context: *mut c_void,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
) -> usize {
|
|
let context = unsafe { init_cstream_using_cdict_advanced_test_context(context) };
|
|
context.events.push("load-dict");
|
|
context.cdict = dict;
|
|
context.dict_size = dict_size;
|
|
context.load_dict_result
|
|
}
|
|
|
|
unsafe extern "C" fn init_cstream_advanced_test_check_c_params(
|
|
context: *mut c_void,
|
|
_c_params: ZSTD_compressionParameters,
|
|
) -> usize {
|
|
let context = unsafe { init_cstream_using_cdict_advanced_test_context(context) };
|
|
context.events.push("check");
|
|
context.check_params_result
|
|
}
|
|
|
|
unsafe extern "C" fn init_cstream_advanced_test_set_zstd_params(
|
|
context: *mut c_void,
|
|
params: *const ZSTD_parameters,
|
|
) {
|
|
let context = unsafe { init_cstream_using_cdict_advanced_test_context(context) };
|
|
context.events.push("params");
|
|
context.zstd_params = unsafe { *params };
|
|
}
|
|
|
|
unsafe extern "C" fn init_cstream_using_cdict_advanced_test_set_frame_params(
|
|
context: *mut c_void,
|
|
content_size_flag: c_uint,
|
|
checksum_flag: c_uint,
|
|
no_dict_id_flag: c_uint,
|
|
) {
|
|
let context = unsafe { init_cstream_using_cdict_advanced_test_context(context) };
|
|
context.events.push("frame");
|
|
context.frame_params = [content_size_flag, checksum_flag, no_dict_id_flag];
|
|
}
|
|
|
|
unsafe extern "C" fn init_cstream_using_cdict_advanced_test_ref_cdict(
|
|
context: *mut c_void,
|
|
cdict: *const c_void,
|
|
) -> usize {
|
|
let context = unsafe { init_cstream_using_cdict_advanced_test_context(context) };
|
|
context.events.push("ref-cdict");
|
|
context.cdict = cdict;
|
|
context.ref_result
|
|
}
|
|
|
|
fn init_cstream_using_cdict_advanced_test_state(
|
|
context: &mut InitCStreamUsingCDictAdvancedTestContext,
|
|
) -> ZSTD_rust_initCStreamUsingCDictAdvancedState {
|
|
ZSTD_rust_initCStreamUsingCDictAdvancedState {
|
|
callback_context: (context as *mut InitCStreamUsingCDictAdvancedTestContext).cast(),
|
|
reset_session: init_cstream_using_cdict_advanced_test_reset,
|
|
set_pledged_src_size: init_cstream_using_cdict_advanced_test_set_pledged,
|
|
set_frame_params: init_cstream_using_cdict_advanced_test_set_frame_params,
|
|
ref_cdict: init_cstream_using_cdict_advanced_test_ref_cdict,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn init_cstream_using_cdict_advanced_preserves_order_and_scalars() {
|
|
let cdict = ptr::dangling::<c_void>();
|
|
let mut context = InitCStreamUsingCDictAdvancedTestContext::default();
|
|
let state = init_cstream_using_cdict_advanced_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_initCStreamUsingCDictAdvanced(&state, 77, 1, 2, 3, cdict) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(context.events, ["reset", "pledged", "frame", "ref-cdict"]);
|
|
assert_eq!(context.pledged_src_size, 77);
|
|
assert_eq!(context.frame_params, [1, 2, 3]);
|
|
assert_eq!(context.cdict, cdict);
|
|
}
|
|
|
|
#[test]
|
|
fn init_cstream_using_cdict_advanced_stops_after_reset_error() {
|
|
let mut context = InitCStreamUsingCDictAdvancedTestContext {
|
|
reset_result: ERROR(ZstdErrorCode::MemoryAllocation),
|
|
..InitCStreamUsingCDictAdvancedTestContext::default()
|
|
};
|
|
let state = init_cstream_using_cdict_advanced_test_state(&mut context);
|
|
|
|
let result =
|
|
unsafe { ZSTD_rust_initCStreamUsingCDictAdvanced(&state, 77, 1, 2, 3, ptr::null()) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::MemoryAllocation));
|
|
assert_eq!(context.events, ["reset"]);
|
|
}
|
|
|
|
#[test]
|
|
fn init_cstream_using_cdict_advanced_stops_after_pledged_size_error() {
|
|
let mut context = InitCStreamUsingCDictAdvancedTestContext {
|
|
pledged_result: ERROR(ZstdErrorCode::StageWrong),
|
|
..InitCStreamUsingCDictAdvancedTestContext::default()
|
|
};
|
|
let state = init_cstream_using_cdict_advanced_test_state(&mut context);
|
|
|
|
let result =
|
|
unsafe { ZSTD_rust_initCStreamUsingCDictAdvanced(&state, 77, 1, 2, 3, ptr::null()) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::StageWrong));
|
|
assert_eq!(context.events, ["reset", "pledged"]);
|
|
}
|
|
|
|
#[test]
|
|
fn init_cstream_using_cdict_advanced_propagates_ref_error_after_frame_params() {
|
|
let mut context = InitCStreamUsingCDictAdvancedTestContext {
|
|
ref_result: ERROR(ZstdErrorCode::DictionaryCreationFailed),
|
|
..InitCStreamUsingCDictAdvancedTestContext::default()
|
|
};
|
|
let state = init_cstream_using_cdict_advanced_test_state(&mut context);
|
|
|
|
let result =
|
|
unsafe { ZSTD_rust_initCStreamUsingCDictAdvanced(&state, 77, 1, 2, 3, ptr::null()) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::DictionaryCreationFailed));
|
|
assert_eq!(context.events, ["reset", "pledged", "frame", "ref-cdict"]);
|
|
assert_eq!(context.frame_params, [1, 2, 3]);
|
|
}
|
|
|
|
fn init_cstream_using_cdict_test_state(
|
|
context: &mut InitCStreamUsingCDictAdvancedTestContext,
|
|
) -> ZSTD_rust_initCStreamUsingCDictState {
|
|
ZSTD_rust_initCStreamUsingCDictState {
|
|
callback_context: (context as *mut InitCStreamUsingCDictAdvancedTestContext).cast(),
|
|
reset_session: init_cstream_using_cdict_advanced_test_reset,
|
|
ref_cdict: init_cstream_using_cdict_advanced_test_ref_cdict,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn init_cstream_using_cdict_preserves_reset_then_reference_order() {
|
|
let cdict = ptr::dangling::<c_void>();
|
|
let mut context = InitCStreamUsingCDictAdvancedTestContext::default();
|
|
let state = init_cstream_using_cdict_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_initCStreamUsingCDict(&state, cdict) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(context.events, ["reset", "ref-cdict"]);
|
|
assert_eq!(context.cdict, cdict);
|
|
}
|
|
|
|
#[test]
|
|
fn init_cstream_using_cdict_stops_after_reset_error() {
|
|
let mut context = InitCStreamUsingCDictAdvancedTestContext {
|
|
reset_result: ERROR(ZstdErrorCode::MemoryAllocation),
|
|
..InitCStreamUsingCDictAdvancedTestContext::default()
|
|
};
|
|
let state = init_cstream_using_cdict_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_initCStreamUsingCDict(&state, ptr::null()) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::MemoryAllocation));
|
|
assert_eq!(context.events, ["reset"]);
|
|
}
|
|
|
|
#[test]
|
|
fn init_cstream_using_cdict_propagates_reference_error() {
|
|
let mut context = InitCStreamUsingCDictAdvancedTestContext {
|
|
ref_result: ERROR(ZstdErrorCode::DictionaryCreationFailed),
|
|
..InitCStreamUsingCDictAdvancedTestContext::default()
|
|
};
|
|
let state = init_cstream_using_cdict_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_initCStreamUsingCDict(&state, ptr::null()) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::DictionaryCreationFailed));
|
|
assert_eq!(context.events, ["reset", "ref-cdict"]);
|
|
}
|
|
|
|
fn init_cstream_src_size_test_state(
|
|
context: &mut InitCStreamUsingCDictAdvancedTestContext,
|
|
) -> ZSTD_rust_initCStreamSrcSizeState {
|
|
ZSTD_rust_initCStreamSrcSizeState {
|
|
callback_context: (context as *mut InitCStreamUsingCDictAdvancedTestContext).cast(),
|
|
reset_session: init_cstream_using_cdict_advanced_test_reset,
|
|
ref_cdict: init_cstream_using_cdict_advanced_test_ref_cdict,
|
|
set_level: init_cstream_src_size_test_set_level,
|
|
set_pledged_src_size: init_cstream_using_cdict_advanced_test_set_pledged,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn init_cstream_src_size_preserves_order_and_normalizes_unknown_pledge() {
|
|
let mut context = InitCStreamUsingCDictAdvancedTestContext::default();
|
|
let state = init_cstream_src_size_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_initCStreamSrcSize(&state, 0, -3) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(context.events, ["reset", "ref-cdict", "level", "pledged"]);
|
|
assert_eq!(context.compression_level, -3);
|
|
assert_eq!(context.pledged_src_size, ZSTD_CONTENTSIZE_UNKNOWN);
|
|
assert_eq!(context.cdict, ptr::null());
|
|
}
|
|
|
|
#[test]
|
|
fn init_cstream_src_size_stops_after_reset_error() {
|
|
let mut context = InitCStreamUsingCDictAdvancedTestContext {
|
|
reset_result: ERROR(ZstdErrorCode::MemoryAllocation),
|
|
..InitCStreamUsingCDictAdvancedTestContext::default()
|
|
};
|
|
let state = init_cstream_src_size_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_initCStreamSrcSize(&state, 77, 4) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::MemoryAllocation));
|
|
assert_eq!(context.events, ["reset"]);
|
|
}
|
|
|
|
#[test]
|
|
fn init_cstream_src_size_stops_after_cdict_clear_error() {
|
|
let mut context = InitCStreamUsingCDictAdvancedTestContext {
|
|
ref_result: ERROR(ZstdErrorCode::DictionaryCreationFailed),
|
|
..InitCStreamUsingCDictAdvancedTestContext::default()
|
|
};
|
|
let state = init_cstream_src_size_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_initCStreamSrcSize(&state, 77, 4) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::DictionaryCreationFailed));
|
|
assert_eq!(context.events, ["reset", "ref-cdict"]);
|
|
}
|
|
|
|
#[test]
|
|
fn init_cstream_src_size_stops_after_level_error() {
|
|
let mut context = InitCStreamUsingCDictAdvancedTestContext {
|
|
set_level_result: ERROR(ZstdErrorCode::ParameterOutOfBound),
|
|
..InitCStreamUsingCDictAdvancedTestContext::default()
|
|
};
|
|
let state = init_cstream_src_size_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_initCStreamSrcSize(&state, 77, 4) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::ParameterOutOfBound));
|
|
assert_eq!(context.events, ["reset", "ref-cdict", "level"]);
|
|
}
|
|
|
|
#[test]
|
|
fn init_cstream_src_size_propagates_pledge_error_last() {
|
|
let mut context = InitCStreamUsingCDictAdvancedTestContext {
|
|
pledged_result: ERROR(ZstdErrorCode::StageWrong),
|
|
..InitCStreamUsingCDictAdvancedTestContext::default()
|
|
};
|
|
let state = init_cstream_src_size_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_initCStreamSrcSize(&state, 77, 4) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::StageWrong));
|
|
assert_eq!(context.events, ["reset", "ref-cdict", "level", "pledged"]);
|
|
}
|
|
|
|
fn init_cstream_test_state(
|
|
context: &mut InitCStreamUsingCDictAdvancedTestContext,
|
|
) -> ZSTD_rust_initCStreamState {
|
|
ZSTD_rust_initCStreamState {
|
|
callback_context: (context as *mut InitCStreamUsingCDictAdvancedTestContext).cast(),
|
|
reset_session: init_cstream_using_cdict_advanced_test_reset,
|
|
ref_cdict: init_cstream_using_cdict_advanced_test_ref_cdict,
|
|
set_level: init_cstream_src_size_test_set_level,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn init_cstream_preserves_reset_clear_and_level_order() {
|
|
let mut context = InitCStreamUsingCDictAdvancedTestContext::default();
|
|
let state = init_cstream_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_initCStream(&state, -3) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(context.events, ["reset", "ref-cdict", "level"]);
|
|
assert_eq!(context.cdict, ptr::null());
|
|
assert_eq!(context.compression_level, -3);
|
|
}
|
|
|
|
#[test]
|
|
fn init_cstream_stops_after_reset_error() {
|
|
let mut context = InitCStreamUsingCDictAdvancedTestContext {
|
|
reset_result: ERROR(ZstdErrorCode::MemoryAllocation),
|
|
..InitCStreamUsingCDictAdvancedTestContext::default()
|
|
};
|
|
let state = init_cstream_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_initCStream(&state, 4) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::MemoryAllocation));
|
|
assert_eq!(context.events, ["reset"]);
|
|
}
|
|
|
|
#[test]
|
|
fn init_cstream_stops_after_cdict_clear_error() {
|
|
let mut context = InitCStreamUsingCDictAdvancedTestContext {
|
|
ref_result: ERROR(ZstdErrorCode::DictionaryCreationFailed),
|
|
..InitCStreamUsingCDictAdvancedTestContext::default()
|
|
};
|
|
let state = init_cstream_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_initCStream(&state, 4) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::DictionaryCreationFailed));
|
|
assert_eq!(context.events, ["reset", "ref-cdict"]);
|
|
}
|
|
|
|
#[test]
|
|
fn init_cstream_propagates_level_error_last() {
|
|
let mut context = InitCStreamUsingCDictAdvancedTestContext {
|
|
set_level_result: ERROR(ZstdErrorCode::ParameterOutOfBound),
|
|
..InitCStreamUsingCDictAdvancedTestContext::default()
|
|
};
|
|
let state = init_cstream_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_initCStream(&state, 4) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::ParameterOutOfBound));
|
|
assert_eq!(context.events, ["reset", "ref-cdict", "level"]);
|
|
}
|
|
|
|
fn init_cstream_using_dict_test_state(
|
|
context: &mut InitCStreamUsingCDictAdvancedTestContext,
|
|
) -> ZSTD_rust_initCStreamUsingDictState {
|
|
ZSTD_rust_initCStreamUsingDictState {
|
|
callback_context: (context as *mut InitCStreamUsingCDictAdvancedTestContext).cast(),
|
|
reset_session: init_cstream_using_cdict_advanced_test_reset,
|
|
set_level: init_cstream_src_size_test_set_level,
|
|
load_dictionary: init_cstream_using_dict_test_load_dictionary,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn init_cstream_using_dict_preserves_order_and_dictionary_arguments() {
|
|
let dict = ptr::dangling::<c_void>();
|
|
let mut context = InitCStreamUsingCDictAdvancedTestContext::default();
|
|
let state = init_cstream_using_dict_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_initCStreamUsingDict(&state, dict, 123, -3) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(context.events, ["reset", "level", "load-dict"]);
|
|
assert_eq!(context.compression_level, -3);
|
|
assert_eq!(context.cdict, dict);
|
|
assert_eq!(context.dict_size, 123);
|
|
}
|
|
|
|
#[test]
|
|
fn init_cstream_using_dict_stops_after_reset_error() {
|
|
let mut context = InitCStreamUsingCDictAdvancedTestContext {
|
|
reset_result: ERROR(ZstdErrorCode::MemoryAllocation),
|
|
..InitCStreamUsingCDictAdvancedTestContext::default()
|
|
};
|
|
let state = init_cstream_using_dict_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_initCStreamUsingDict(&state, ptr::null(), 0, 4) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::MemoryAllocation));
|
|
assert_eq!(context.events, ["reset"]);
|
|
}
|
|
|
|
#[test]
|
|
fn init_cstream_using_dict_stops_after_level_error() {
|
|
let mut context = InitCStreamUsingCDictAdvancedTestContext {
|
|
set_level_result: ERROR(ZstdErrorCode::ParameterOutOfBound),
|
|
..InitCStreamUsingCDictAdvancedTestContext::default()
|
|
};
|
|
let state = init_cstream_using_dict_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_initCStreamUsingDict(&state, ptr::null(), 0, 4) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::ParameterOutOfBound));
|
|
assert_eq!(context.events, ["reset", "level"]);
|
|
}
|
|
|
|
#[test]
|
|
fn init_cstream_using_dict_propagates_dictionary_error_last() {
|
|
let mut context = InitCStreamUsingCDictAdvancedTestContext {
|
|
load_dict_result: ERROR(ZstdErrorCode::DictionaryCreationFailed),
|
|
..InitCStreamUsingCDictAdvancedTestContext::default()
|
|
};
|
|
let state = init_cstream_using_dict_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_initCStreamUsingDict(&state, ptr::null(), 0, 4) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::DictionaryCreationFailed));
|
|
assert_eq!(context.events, ["reset", "level", "load-dict"]);
|
|
}
|
|
|
|
fn init_cstream_advanced_test_state(
|
|
context: &mut InitCStreamUsingCDictAdvancedTestContext,
|
|
) -> ZSTD_rust_initCStreamAdvancedState {
|
|
ZSTD_rust_initCStreamAdvancedState {
|
|
callback_context: (context as *mut InitCStreamUsingCDictAdvancedTestContext).cast(),
|
|
reset_session: init_cstream_using_cdict_advanced_test_reset,
|
|
set_pledged_src_size: init_cstream_using_cdict_advanced_test_set_pledged,
|
|
check_c_params: init_cstream_advanced_test_check_c_params,
|
|
set_zstd_params: init_cstream_advanced_test_set_zstd_params,
|
|
load_dictionary: init_cstream_using_dict_test_load_dictionary,
|
|
}
|
|
}
|
|
|
|
fn init_cstream_advanced_test_params(content_size_flag: c_int) -> ZSTD_parameters {
|
|
ZSTD_parameters {
|
|
cParams: ZSTD_compressionParameters {
|
|
windowLog: 10,
|
|
chainLog: 11,
|
|
hashLog: 12,
|
|
searchLog: 13,
|
|
minMatch: 4,
|
|
targetLength: 5,
|
|
strategy: 1,
|
|
},
|
|
fParams: ZSTD_frameParameters {
|
|
contentSizeFlag: content_size_flag,
|
|
checksumFlag: 1,
|
|
noDictIDFlag: 2,
|
|
},
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn init_cstream_advanced_preserves_order_and_normalizes_unknown_pledge() {
|
|
let params = init_cstream_advanced_test_params(0);
|
|
let dict = ptr::dangling::<c_void>();
|
|
let mut context = InitCStreamUsingCDictAdvancedTestContext::default();
|
|
let state = init_cstream_advanced_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_initCStreamAdvanced(&state, ¶ms, 0, dict, 123) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(
|
|
context.events,
|
|
["reset", "pledged", "check", "params", "load-dict"]
|
|
);
|
|
assert_eq!(context.pledged_src_size, ZSTD_CONTENTSIZE_UNKNOWN);
|
|
assert_eq!(context.zstd_params, params);
|
|
assert_eq!(context.cdict, dict);
|
|
assert_eq!(context.dict_size, 123);
|
|
}
|
|
|
|
#[test]
|
|
fn init_cstream_advanced_keeps_zero_pledge_for_known_empty_frame() {
|
|
let params = init_cstream_advanced_test_params(1);
|
|
let mut context = InitCStreamUsingCDictAdvancedTestContext::default();
|
|
let state = init_cstream_advanced_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_initCStreamAdvanced(&state, ¶ms, 0, ptr::null(), 0) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(context.pledged_src_size, 0);
|
|
assert_eq!(
|
|
context.events,
|
|
["reset", "pledged", "check", "params", "load-dict"]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn init_cstream_advanced_stops_after_reset_error() {
|
|
let params = init_cstream_advanced_test_params(0);
|
|
let mut context = InitCStreamUsingCDictAdvancedTestContext {
|
|
reset_result: ERROR(ZstdErrorCode::MemoryAllocation),
|
|
..InitCStreamUsingCDictAdvancedTestContext::default()
|
|
};
|
|
let state = init_cstream_advanced_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_initCStreamAdvanced(&state, ¶ms, 77, ptr::null(), 0) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::MemoryAllocation));
|
|
assert_eq!(context.events, ["reset"]);
|
|
}
|
|
|
|
#[test]
|
|
fn init_cstream_advanced_stops_after_pledged_size_error() {
|
|
let params = init_cstream_advanced_test_params(0);
|
|
let mut context = InitCStreamUsingCDictAdvancedTestContext {
|
|
pledged_result: ERROR(ZstdErrorCode::StageWrong),
|
|
..InitCStreamUsingCDictAdvancedTestContext::default()
|
|
};
|
|
let state = init_cstream_advanced_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_initCStreamAdvanced(&state, ¶ms, 77, ptr::null(), 0) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::StageWrong));
|
|
assert_eq!(context.events, ["reset", "pledged"]);
|
|
}
|
|
|
|
#[test]
|
|
fn init_cstream_advanced_stops_after_parameter_error() {
|
|
let params = init_cstream_advanced_test_params(0);
|
|
let mut context = InitCStreamUsingCDictAdvancedTestContext {
|
|
check_params_result: ERROR(ZstdErrorCode::ParameterOutOfBound),
|
|
..InitCStreamUsingCDictAdvancedTestContext::default()
|
|
};
|
|
let state = init_cstream_advanced_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_initCStreamAdvanced(&state, ¶ms, 77, ptr::null(), 0) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::ParameterOutOfBound));
|
|
assert_eq!(context.events, ["reset", "pledged", "check"]);
|
|
}
|
|
|
|
#[test]
|
|
fn init_cstream_advanced_propagates_dictionary_error_last() {
|
|
let params = init_cstream_advanced_test_params(0);
|
|
let mut context = InitCStreamUsingCDictAdvancedTestContext {
|
|
load_dict_result: ERROR(ZstdErrorCode::DictionaryCreationFailed),
|
|
..InitCStreamUsingCDictAdvancedTestContext::default()
|
|
};
|
|
let state = init_cstream_advanced_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_initCStreamAdvanced(&state, ¶ms, 77, ptr::null(), 0) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::DictionaryCreationFailed));
|
|
assert_eq!(
|
|
context.events,
|
|
["reset", "pledged", "check", "params", "load-dict"]
|
|
);
|
|
}
|
|
|
|
fn set_parameters_using_cctx_params_test_value(params: &ZSTD_CCtx_params) -> c_int {
|
|
let mut value = -1;
|
|
let result = unsafe { ZSTD_CCtxParams_getParameter(params, 10, &mut value) };
|
|
assert_eq!(result, 0);
|
|
value
|
|
}
|
|
|
|
fn set_parameters_using_cctx_params_test_state(
|
|
requested_params: &mut ZSTD_CCtx_params,
|
|
source_params: &ZSTD_CCtx_params,
|
|
stream_stage: c_int,
|
|
cdict: *const c_void,
|
|
) -> ZSTD_rust_setParametersUsingCCtxParamsState {
|
|
ZSTD_rust_setParametersUsingCCtxParamsState {
|
|
requested_params,
|
|
source_params,
|
|
stream_stage,
|
|
cdict,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn set_parameters_using_cctx_params_copies_source_after_policy_checks() {
|
|
let mut source_params = unsafe { MaybeUninit::<ZSTD_CCtx_params>::zeroed().assume_init() };
|
|
let mut requested_params =
|
|
unsafe { MaybeUninit::<ZSTD_CCtx_params>::zeroed().assume_init() };
|
|
assert_eq!(
|
|
unsafe { ZSTD_CCtxParams_setParameter(&mut source_params, 10, 1) },
|
|
1
|
|
);
|
|
let state = set_parameters_using_cctx_params_test_state(
|
|
&mut requested_params,
|
|
&source_params,
|
|
ZSTD_CSTREAM_STAGE_INIT,
|
|
ptr::null(),
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_setParametersUsingCCtxParams(&state) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(
|
|
set_parameters_using_cctx_params_test_value(&requested_params),
|
|
1
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn set_parameters_using_cctx_params_rejects_non_init_stage_without_copying() {
|
|
let mut source_params = unsafe { MaybeUninit::<ZSTD_CCtx_params>::zeroed().assume_init() };
|
|
let mut requested_params =
|
|
unsafe { MaybeUninit::<ZSTD_CCtx_params>::zeroed().assume_init() };
|
|
assert_eq!(
|
|
unsafe { ZSTD_CCtxParams_setParameter(&mut source_params, 10, 1) },
|
|
1
|
|
);
|
|
let state = set_parameters_using_cctx_params_test_state(
|
|
&mut requested_params,
|
|
&source_params,
|
|
ZSTD_CSTREAM_STAGE_LOAD,
|
|
ptr::null(),
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_setParametersUsingCCtxParams(&state) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::StageWrong));
|
|
assert_eq!(
|
|
set_parameters_using_cctx_params_test_value(&requested_params),
|
|
0
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn set_parameters_using_cctx_params_rejects_cdict_without_copying() {
|
|
let mut source_params = unsafe { MaybeUninit::<ZSTD_CCtx_params>::zeroed().assume_init() };
|
|
let mut requested_params =
|
|
unsafe { MaybeUninit::<ZSTD_CCtx_params>::zeroed().assume_init() };
|
|
assert_eq!(
|
|
unsafe { ZSTD_CCtxParams_setParameter(&mut source_params, 10, 1) },
|
|
1
|
|
);
|
|
let state = set_parameters_using_cctx_params_test_state(
|
|
&mut requested_params,
|
|
&source_params,
|
|
ZSTD_CSTREAM_STAGE_INIT,
|
|
ptr::dangling(),
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_setParametersUsingCCtxParams(&state) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::StageWrong));
|
|
assert_eq!(
|
|
set_parameters_using_cctx_params_test_value(&requested_params),
|
|
0
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn set_parameters_using_cctx_params_rejects_null_state() {
|
|
let result = unsafe { ZSTD_rust_setParametersUsingCCtxParams(ptr::null()) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::Generic));
|
|
}
|
|
|
|
fn set_parameter_test_state(
|
|
requested_params: &mut ZSTD_CCtx_params,
|
|
stream_stage: c_int,
|
|
c_params_changed: &mut c_int,
|
|
static_size: usize,
|
|
max_block_size_set: &mut c_uint,
|
|
) -> ZSTD_rust_setParameterState {
|
|
ZSTD_rust_setParameterState {
|
|
requested_params,
|
|
stream_stage,
|
|
c_params_changed,
|
|
static_size,
|
|
rust_simple_compress2_max_block_size_set: max_block_size_set,
|
|
}
|
|
}
|
|
|
|
fn set_parameter_test_get(requested_params: &ZSTD_CCtx_params, param: c_int) -> (usize, c_int) {
|
|
let mut value = -1;
|
|
let result = unsafe { ZSTD_CCtxParams_getParameter(requested_params, param, &mut value) };
|
|
(result, value)
|
|
}
|
|
|
|
#[test]
|
|
fn set_parameter_authorizes_in_flight_updates_and_marks_cparams_changed() {
|
|
let mut requested_params =
|
|
unsafe { MaybeUninit::<ZSTD_CCtx_params>::zeroed().assume_init() };
|
|
let mut c_params_changed = 0;
|
|
let mut max_block_size_set = 0;
|
|
let state = set_parameter_test_state(
|
|
&mut requested_params,
|
|
ZSTD_CSTREAM_STAGE_LOAD,
|
|
&mut c_params_changed,
|
|
0,
|
|
&mut max_block_size_set,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_setParameter(&state, ZSTD_C_COMPRESSION_LEVEL, 5) };
|
|
|
|
assert_eq!(result, 5);
|
|
assert_eq!(c_params_changed, 1);
|
|
assert_eq!(max_block_size_set, 0);
|
|
assert_eq!(
|
|
set_parameter_test_get(&requested_params, ZSTD_C_COMPRESSION_LEVEL),
|
|
(0, 5)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn set_parameter_rejects_unauthorized_stage_updates_without_mutation() {
|
|
let mut requested_params =
|
|
unsafe { MaybeUninit::<ZSTD_CCtx_params>::zeroed().assume_init() };
|
|
let mut c_params_changed = 0;
|
|
let mut max_block_size_set = 0;
|
|
let state = set_parameter_test_state(
|
|
&mut requested_params,
|
|
ZSTD_CSTREAM_STAGE_LOAD,
|
|
&mut c_params_changed,
|
|
0,
|
|
&mut max_block_size_set,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_setParameter(&state, ZSTD_C_FORMAT, 1) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::StageWrong));
|
|
assert_eq!(c_params_changed, 0);
|
|
assert_eq!(
|
|
set_parameter_test_get(&requested_params, ZSTD_C_FORMAT),
|
|
(0, 0)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn set_parameter_rejects_nonzero_workers_for_static_contexts() {
|
|
let mut requested_params =
|
|
unsafe { MaybeUninit::<ZSTD_CCtx_params>::zeroed().assume_init() };
|
|
let mut c_params_changed = 0;
|
|
let mut max_block_size_set = 0;
|
|
let state = set_parameter_test_state(
|
|
&mut requested_params,
|
|
ZSTD_CSTREAM_STAGE_INIT,
|
|
&mut c_params_changed,
|
|
1,
|
|
&mut max_block_size_set,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_setParameter(&state, ZSTD_C_NB_WORKERS, 1) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::ParameterUnsupported));
|
|
assert_eq!(c_params_changed, 0);
|
|
assert_eq!(
|
|
set_parameter_test_get(&requested_params, ZSTD_C_FORMAT),
|
|
(0, 0)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn set_parameter_rejects_unknown_parameters() {
|
|
let mut requested_params =
|
|
unsafe { MaybeUninit::<ZSTD_CCtx_params>::zeroed().assume_init() };
|
|
let mut c_params_changed = 0;
|
|
let mut max_block_size_set = 0;
|
|
let state = set_parameter_test_state(
|
|
&mut requested_params,
|
|
ZSTD_CSTREAM_STAGE_INIT,
|
|
&mut c_params_changed,
|
|
0,
|
|
&mut max_block_size_set,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_setParameter(&state, 12345, 1) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::ParameterUnsupported));
|
|
assert_eq!(
|
|
set_parameter_test_get(&requested_params, ZSTD_C_FORMAT),
|
|
(0, 0)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn set_parameter_marks_max_block_size_after_success() {
|
|
let mut requested_params =
|
|
unsafe { MaybeUninit::<ZSTD_CCtx_params>::zeroed().assume_init() };
|
|
let mut c_params_changed = 0;
|
|
let mut max_block_size_set = 0;
|
|
let state = set_parameter_test_state(
|
|
&mut requested_params,
|
|
ZSTD_CSTREAM_STAGE_INIT,
|
|
&mut c_params_changed,
|
|
0,
|
|
&mut max_block_size_set,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_setParameter(&state, ZSTD_C_MAX_BLOCK_SIZE, 0) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(max_block_size_set, 1);
|
|
}
|
|
|
|
#[test]
|
|
fn set_parameter_does_not_mark_max_block_size_after_underlying_error() {
|
|
let mut requested_params =
|
|
unsafe { MaybeUninit::<ZSTD_CCtx_params>::zeroed().assume_init() };
|
|
let mut c_params_changed = 0;
|
|
let mut max_block_size_set = 0;
|
|
let state = set_parameter_test_state(
|
|
&mut requested_params,
|
|
ZSTD_CSTREAM_STAGE_INIT,
|
|
&mut c_params_changed,
|
|
0,
|
|
&mut max_block_size_set,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_setParameter(&state, ZSTD_C_MAX_BLOCK_SIZE, 1) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::ParameterOutOfBound));
|
|
assert_eq!(max_block_size_set, 0);
|
|
}
|
|
|
|
fn ref_thread_pool_test_state(
|
|
pool: &mut *mut c_void,
|
|
requested_pool: *mut c_void,
|
|
stream_stage: c_int,
|
|
) -> ZSTD_rust_refThreadPoolState {
|
|
ZSTD_rust_refThreadPoolState {
|
|
pool,
|
|
requested_pool,
|
|
stream_stage,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn ref_thread_pool_assigns_the_requested_pool_at_init_stage() {
|
|
let mut pool = ptr::null_mut::<c_void>();
|
|
let requested_pool = ptr::dangling_mut::<c_void>();
|
|
let state = ref_thread_pool_test_state(&mut pool, requested_pool, ZSTD_CSTREAM_STAGE_INIT);
|
|
|
|
let result = unsafe { ZSTD_rust_refThreadPool(&state) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(pool, requested_pool);
|
|
}
|
|
|
|
#[test]
|
|
fn ref_thread_pool_rejects_non_init_stage_without_mutating_the_pool() {
|
|
let original_pool = ptr::dangling_mut::<c_void>();
|
|
let mut pool = original_pool;
|
|
let state = ref_thread_pool_test_state(&mut pool, ptr::null_mut(), ZSTD_CSTREAM_STAGE_LOAD);
|
|
|
|
let result = unsafe { ZSTD_rust_refThreadPool(&state) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::StageWrong));
|
|
assert_eq!(pool, original_pool);
|
|
}
|
|
|
|
#[test]
|
|
fn ref_thread_pool_rejects_null_state() {
|
|
let result = unsafe { ZSTD_rust_refThreadPool(ptr::null()) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::Generic));
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct CreateCCtxTestContext {
|
|
events: Vec<&'static str>,
|
|
valid: c_int,
|
|
allocation: *mut c_void,
|
|
requested_size: usize,
|
|
initialized: *mut c_void,
|
|
}
|
|
|
|
unsafe extern "C" fn create_cctx_test_validate(context: *mut c_void) -> c_int {
|
|
let context = unsafe { &mut *context.cast::<CreateCCtxTestContext>() };
|
|
context.events.push("validate");
|
|
context.valid
|
|
}
|
|
|
|
unsafe extern "C" fn create_cctx_test_allocate(
|
|
context: *mut c_void,
|
|
size: usize,
|
|
) -> *mut c_void {
|
|
let context = unsafe { &mut *context.cast::<CreateCCtxTestContext>() };
|
|
context.events.push("allocate");
|
|
context.requested_size = size;
|
|
context.allocation
|
|
}
|
|
|
|
unsafe extern "C" fn create_cctx_test_init(context: *mut c_void, cctx: *mut c_void) {
|
|
let context = unsafe { &mut *context.cast::<CreateCCtxTestContext>() };
|
|
context.events.push("init");
|
|
context.initialized = cctx;
|
|
}
|
|
|
|
fn create_cctx_test_state(context: &mut CreateCCtxTestContext) -> ZSTD_rust_createCCtxState {
|
|
ZSTD_rust_createCCtxState {
|
|
callback_context: (context as *mut CreateCCtxTestContext).cast(),
|
|
cctx_size: 123,
|
|
validate_custom_mem: create_cctx_test_validate,
|
|
allocate: create_cctx_test_allocate,
|
|
init: create_cctx_test_init,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn create_cctx_validates_allocates_and_initializes_in_order() {
|
|
let allocation = ptr::dangling_mut::<c_void>();
|
|
let mut context = CreateCCtxTestContext {
|
|
valid: 1,
|
|
allocation,
|
|
..Default::default()
|
|
};
|
|
let state = create_cctx_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_createCCtx(&state) };
|
|
|
|
assert_eq!(result, allocation);
|
|
assert_eq!(context.events, ["validate", "allocate", "init"]);
|
|
assert_eq!(context.requested_size, 123);
|
|
assert_eq!(context.initialized, allocation);
|
|
}
|
|
|
|
#[test]
|
|
fn create_cctx_stops_before_allocation_for_invalid_memory_callbacks() {
|
|
let mut context = CreateCCtxTestContext {
|
|
valid: 0,
|
|
allocation: ptr::dangling_mut(),
|
|
..Default::default()
|
|
};
|
|
let state = create_cctx_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_createCCtx(&state) };
|
|
|
|
assert!(result.is_null());
|
|
assert_eq!(context.events, ["validate"]);
|
|
assert_eq!(context.requested_size, 0);
|
|
assert!(context.initialized.is_null());
|
|
}
|
|
|
|
#[test]
|
|
fn create_cctx_stops_before_initialization_after_allocation_failure() {
|
|
let mut context = CreateCCtxTestContext {
|
|
valid: 1,
|
|
..Default::default()
|
|
};
|
|
let state = create_cctx_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_createCCtx(&state) };
|
|
|
|
assert!(result.is_null());
|
|
assert_eq!(context.events, ["validate", "allocate"]);
|
|
assert_eq!(context.requested_size, 123);
|
|
assert!(context.initialized.is_null());
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct CreateCDictAdvancedWrapperTestContext {
|
|
events: Vec<&'static str>,
|
|
init_context: *mut c_void,
|
|
init_cctx_params: *mut c_void,
|
|
init_c_params: *const ZSTD_compressionParameters,
|
|
init_custom_mem: *const c_void,
|
|
init_dict_size: usize,
|
|
init_dict_content_type: c_int,
|
|
create_context: *mut c_void,
|
|
create_dict: *const c_void,
|
|
create_dict_size: usize,
|
|
create_dict_load_method: c_int,
|
|
create_dict_content_type: c_int,
|
|
create_cctx_params: *const c_void,
|
|
create_custom_mem: *const c_void,
|
|
result: *mut c_void,
|
|
}
|
|
|
|
unsafe extern "C" fn create_cdict_advanced_wrapper_test_init(
|
|
context: *mut c_void,
|
|
cctx_params: *mut c_void,
|
|
c_params: *const ZSTD_compressionParameters,
|
|
custom_mem: *const c_void,
|
|
dict_size: usize,
|
|
dict_content_type: c_int,
|
|
) {
|
|
let context = unsafe { &mut *context.cast::<CreateCDictAdvancedWrapperTestContext>() };
|
|
context.events.push("init");
|
|
context.init_context = context as *mut _ as *mut c_void;
|
|
context.init_cctx_params = cctx_params;
|
|
context.init_c_params = c_params;
|
|
context.init_custom_mem = custom_mem;
|
|
context.init_dict_size = dict_size;
|
|
context.init_dict_content_type = dict_content_type;
|
|
}
|
|
|
|
unsafe extern "C" fn create_cdict_advanced_wrapper_test_create(
|
|
context: *mut c_void,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
dict_load_method: c_int,
|
|
dict_content_type: c_int,
|
|
cctx_params: *const c_void,
|
|
custom_mem: *const c_void,
|
|
) -> *mut c_void {
|
|
let context = unsafe { &mut *context.cast::<CreateCDictAdvancedWrapperTestContext>() };
|
|
context.events.push("create");
|
|
context.create_context = context as *mut _ as *mut c_void;
|
|
context.create_dict = dict;
|
|
context.create_dict_size = dict_size;
|
|
context.create_dict_load_method = dict_load_method;
|
|
context.create_dict_content_type = dict_content_type;
|
|
context.create_cctx_params = cctx_params;
|
|
context.create_custom_mem = custom_mem;
|
|
context.result
|
|
}
|
|
|
|
fn create_cdict_advanced_wrapper_test_state(
|
|
context: &mut CreateCDictAdvancedWrapperTestContext,
|
|
cctx_params: *mut c_void,
|
|
c_params: *const ZSTD_compressionParameters,
|
|
custom_mem: *const c_void,
|
|
) -> ZSTD_rust_createCDictAdvancedWrapperState {
|
|
ZSTD_rust_createCDictAdvancedWrapperState {
|
|
callback_context: context as *mut _ as *mut c_void,
|
|
cctx_params,
|
|
c_params,
|
|
custom_mem,
|
|
init_params: create_cdict_advanced_wrapper_test_init,
|
|
create: create_cdict_advanced_wrapper_test_create,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn create_cdict_advanced_wrapper_preserves_prepare_then_create_order() {
|
|
let mut context = CreateCDictAdvancedWrapperTestContext {
|
|
result: ptr::dangling_mut(),
|
|
..Default::default()
|
|
};
|
|
let cctx_params = ptr::dangling_mut::<c_void>();
|
|
let c_params = ptr::dangling::<ZSTD_compressionParameters>();
|
|
let custom_mem = ptr::dangling::<c_void>();
|
|
let dict = ptr::dangling::<u8>().cast::<c_void>();
|
|
let state = create_cdict_advanced_wrapper_test_state(
|
|
&mut context,
|
|
cctx_params,
|
|
c_params,
|
|
custom_mem,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_createCDictAdvancedWrapper(&state, dict, 123, 1, 2) };
|
|
|
|
assert_eq!(result, context.result);
|
|
assert_eq!(context.events, ["init", "create"]);
|
|
assert_eq!(context.init_context, state.callback_context);
|
|
assert_eq!(context.init_cctx_params, cctx_params);
|
|
assert_eq!(context.init_c_params, c_params);
|
|
assert_eq!(context.init_custom_mem, custom_mem);
|
|
assert_eq!(context.init_dict_size, 123);
|
|
assert_eq!(context.init_dict_content_type, 2);
|
|
assert_eq!(context.create_context, state.callback_context);
|
|
assert_eq!(context.create_dict, dict);
|
|
assert_eq!(context.create_dict_size, 123);
|
|
assert_eq!(context.create_dict_load_method, 1);
|
|
assert_eq!(context.create_dict_content_type, 2);
|
|
assert_eq!(context.create_cctx_params, cctx_params);
|
|
assert_eq!(context.create_custom_mem, custom_mem);
|
|
}
|
|
|
|
#[test]
|
|
fn create_cdict_advanced_wrapper_rejects_missing_parameter_storage() {
|
|
let mut context = CreateCDictAdvancedWrapperTestContext::default();
|
|
let c_params = ptr::dangling::<ZSTD_compressionParameters>();
|
|
let custom_mem = ptr::dangling::<c_void>();
|
|
let state = create_cdict_advanced_wrapper_test_state(
|
|
&mut context,
|
|
ptr::null_mut(),
|
|
c_params,
|
|
custom_mem,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_createCDictAdvancedWrapper(&state, ptr::null(), 0, 0, 0) };
|
|
|
|
assert!(result.is_null());
|
|
assert!(context.events.is_empty());
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct InitCCtxTestContext {
|
|
events: Vec<&'static str>,
|
|
reset_result: usize,
|
|
}
|
|
|
|
unsafe fn init_cctx_test_context(context: *mut c_void) -> &'static mut InitCCtxTestContext {
|
|
unsafe { &mut *context.cast::<InitCCtxTestContext>() }
|
|
}
|
|
|
|
unsafe extern "C" fn init_cctx_test_zero(context: *mut c_void) {
|
|
unsafe { init_cctx_test_context(context) }
|
|
.events
|
|
.push("zero");
|
|
}
|
|
|
|
unsafe extern "C" fn init_cctx_test_set_custom_mem(
|
|
context: *mut c_void,
|
|
_custom_mem: *const c_void,
|
|
) {
|
|
unsafe { init_cctx_test_context(context) }
|
|
.events
|
|
.push("set-custom-mem");
|
|
}
|
|
|
|
unsafe extern "C" fn init_cctx_test_set_bmi2(context: *mut c_void) {
|
|
unsafe { init_cctx_test_context(context) }
|
|
.events
|
|
.push("set-bmi2");
|
|
}
|
|
|
|
unsafe extern "C" fn init_cctx_test_reset(context: *mut c_void) -> usize {
|
|
let context = unsafe { init_cctx_test_context(context) };
|
|
context.events.push("reset");
|
|
context.reset_result
|
|
}
|
|
|
|
fn init_cctx_test_state(context: &mut InitCCtxTestContext) -> ZSTD_rust_initCCtxState {
|
|
ZSTD_rust_initCCtxState {
|
|
callback_context: (context as *mut InitCCtxTestContext).cast(),
|
|
custom_mem: ptr::dangling(),
|
|
zero: init_cctx_test_zero,
|
|
set_custom_mem: init_cctx_test_set_custom_mem,
|
|
set_bmi2: init_cctx_test_set_bmi2,
|
|
reset: init_cctx_test_reset,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn init_cctx_runs_private_initialization_steps_in_order() {
|
|
let mut context = InitCCtxTestContext::default();
|
|
let state = init_cctx_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_initCCtx(&state) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(
|
|
context.events,
|
|
["zero", "set-custom-mem", "set-bmi2", "reset"]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn init_cctx_propagates_reset_errors_after_initialization_steps() {
|
|
let mut context = InitCCtxTestContext {
|
|
reset_result: ERROR(ZstdErrorCode::MemoryAllocation),
|
|
..Default::default()
|
|
};
|
|
let state = init_cctx_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_initCCtx(&state) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::MemoryAllocation));
|
|
assert_eq!(
|
|
context.events,
|
|
["zero", "set-custom-mem", "set-bmi2", "reset"]
|
|
);
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct InitStaticCCtxTestContext {
|
|
events: Vec<&'static str>,
|
|
reserved_cctx: *mut c_void,
|
|
check_available_result: c_int,
|
|
reserve_block_state_failure: Option<usize>,
|
|
reserve_block_state_count: usize,
|
|
reserve_tmp_workspace_result: *mut c_void,
|
|
}
|
|
|
|
unsafe fn init_static_cctx_test_context(
|
|
context: *mut c_void,
|
|
) -> &'static mut InitStaticCCtxTestContext {
|
|
unsafe { &mut *context.cast::<InitStaticCCtxTestContext>() }
|
|
}
|
|
|
|
unsafe extern "C" fn init_static_cctx_test_create_workspace(context: *mut c_void) {
|
|
unsafe { init_static_cctx_test_context(context) }
|
|
.events
|
|
.push("create-workspace");
|
|
}
|
|
|
|
unsafe extern "C" fn init_static_cctx_test_reserve_object(context: *mut c_void) -> *mut c_void {
|
|
let context = unsafe { init_static_cctx_test_context(context) };
|
|
context.events.push("reserve-object");
|
|
context.reserved_cctx
|
|
}
|
|
|
|
unsafe extern "C" fn init_static_cctx_test_zero(context: *mut c_void, _cctx: *mut c_void) {
|
|
unsafe { init_static_cctx_test_context(context) }
|
|
.events
|
|
.push("zero");
|
|
}
|
|
|
|
unsafe extern "C" fn init_static_cctx_test_move_workspace(
|
|
context: *mut c_void,
|
|
_cctx: *mut c_void,
|
|
) {
|
|
unsafe { init_static_cctx_test_context(context) }
|
|
.events
|
|
.push("move-workspace");
|
|
}
|
|
|
|
unsafe extern "C" fn init_static_cctx_test_check_available(context: *mut c_void) -> c_int {
|
|
let context = unsafe { init_static_cctx_test_context(context) };
|
|
context.events.push("check-available");
|
|
context.check_available_result
|
|
}
|
|
|
|
unsafe extern "C" fn init_static_cctx_test_reserve_block_state(
|
|
context: *mut c_void,
|
|
block_state: c_int,
|
|
) -> *mut c_void {
|
|
let context = unsafe { &mut *context.cast::<InitStaticCCtxTestContext>() };
|
|
context.events.push(match block_state {
|
|
INIT_STATIC_CCTX_PREV_CBLOCK => "reserve-prev-block-state",
|
|
INIT_STATIC_CCTX_NEXT_CBLOCK => "reserve-next-block-state",
|
|
_ => "reserve-unknown-block-state",
|
|
});
|
|
let result =
|
|
if context.reserve_block_state_failure == Some(context.reserve_block_state_count) {
|
|
ptr::null_mut()
|
|
} else {
|
|
ptr::dangling_mut()
|
|
};
|
|
context.reserve_block_state_count += 1;
|
|
result
|
|
}
|
|
|
|
unsafe extern "C" fn init_static_cctx_test_reserve_tmp_workspace(
|
|
context: *mut c_void,
|
|
) -> *mut c_void {
|
|
let context = unsafe { init_static_cctx_test_context(context) };
|
|
context.events.push("reserve-tmp-workspace");
|
|
context.reserve_tmp_workspace_result
|
|
}
|
|
|
|
unsafe extern "C" fn init_static_cctx_test_set_bmi2(context: *mut c_void) {
|
|
unsafe { init_static_cctx_test_context(context) }
|
|
.events
|
|
.push("set-bmi2");
|
|
}
|
|
|
|
fn init_static_cctx_test_state(
|
|
context: &mut InitStaticCCtxTestContext,
|
|
workspace: *mut c_void,
|
|
workspace_size: usize,
|
|
cctx_size: usize,
|
|
) -> ZSTD_rust_initStaticCCtxState {
|
|
ZSTD_rust_initStaticCCtxState {
|
|
callback_context: (context as *mut InitStaticCCtxTestContext).cast(),
|
|
workspace,
|
|
workspace_size,
|
|
cctx_size,
|
|
create_workspace: init_static_cctx_test_create_workspace,
|
|
reserve_object: init_static_cctx_test_reserve_object,
|
|
zero: init_static_cctx_test_zero,
|
|
move_workspace: init_static_cctx_test_move_workspace,
|
|
check_available: init_static_cctx_test_check_available,
|
|
reserve_block_state: init_static_cctx_test_reserve_block_state,
|
|
reserve_tmp_workspace: init_static_cctx_test_reserve_tmp_workspace,
|
|
set_bmi2: init_static_cctx_test_set_bmi2,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn init_static_cctx_runs_private_construction_steps_in_order() {
|
|
let mut context = InitStaticCCtxTestContext {
|
|
reserved_cctx: ptr::dangling_mut(),
|
|
check_available_result: 1,
|
|
reserve_tmp_workspace_result: ptr::dangling_mut(),
|
|
..Default::default()
|
|
};
|
|
let mut workspace = [0usize; 1];
|
|
let state = init_static_cctx_test_state(
|
|
&mut context,
|
|
workspace.as_mut_ptr().cast(),
|
|
size_of_val(&workspace),
|
|
size_of_val(&workspace) - 1,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_initStaticCCtx(&state) };
|
|
|
|
assert_eq!(result, context.reserved_cctx);
|
|
assert_eq!(
|
|
context.events,
|
|
[
|
|
"create-workspace",
|
|
"reserve-object",
|
|
"zero",
|
|
"move-workspace",
|
|
"check-available",
|
|
"reserve-prev-block-state",
|
|
"reserve-next-block-state",
|
|
"reserve-tmp-workspace",
|
|
"set-bmi2",
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn init_static_cctx_stops_after_cctx_reservation_failure() {
|
|
let mut context = InitStaticCCtxTestContext {
|
|
check_available_result: 1,
|
|
reserve_tmp_workspace_result: ptr::dangling_mut(),
|
|
..Default::default()
|
|
};
|
|
let mut workspace = [0usize; 1];
|
|
let state = init_static_cctx_test_state(
|
|
&mut context,
|
|
workspace.as_mut_ptr().cast(),
|
|
size_of_val(&workspace),
|
|
size_of_val(&workspace),
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_initStaticCCtx(&state) };
|
|
|
|
assert!(result.is_null());
|
|
assert_eq!(context.events, ["create-workspace", "reserve-object"]);
|
|
}
|
|
|
|
#[test]
|
|
fn init_static_cctx_stops_after_workspace_capacity_failure() {
|
|
let mut context = InitStaticCCtxTestContext {
|
|
reserved_cctx: ptr::dangling_mut(),
|
|
reserve_tmp_workspace_result: ptr::dangling_mut(),
|
|
..Default::default()
|
|
};
|
|
let mut workspace = [0usize; 1];
|
|
let state = init_static_cctx_test_state(
|
|
&mut context,
|
|
workspace.as_mut_ptr().cast(),
|
|
size_of_val(&workspace),
|
|
size_of_val(&workspace) - 1,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_initStaticCCtx(&state) };
|
|
|
|
assert!(result.is_null());
|
|
assert_eq!(
|
|
context.events,
|
|
[
|
|
"create-workspace",
|
|
"reserve-object",
|
|
"zero",
|
|
"move-workspace",
|
|
"check-available",
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn init_static_cctx_stops_after_block_state_reservation_failure() {
|
|
let mut context = InitStaticCCtxTestContext {
|
|
reserved_cctx: ptr::dangling_mut(),
|
|
check_available_result: 1,
|
|
reserve_block_state_failure: Some(1),
|
|
reserve_tmp_workspace_result: ptr::dangling_mut(),
|
|
..Default::default()
|
|
};
|
|
let mut workspace = [0usize; 1];
|
|
let state = init_static_cctx_test_state(
|
|
&mut context,
|
|
workspace.as_mut_ptr().cast(),
|
|
size_of_val(&workspace),
|
|
size_of_val(&workspace) - 1,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_initStaticCCtx(&state) };
|
|
|
|
assert!(result.is_null());
|
|
assert_eq!(
|
|
context.events,
|
|
[
|
|
"create-workspace",
|
|
"reserve-object",
|
|
"zero",
|
|
"move-workspace",
|
|
"check-available",
|
|
"reserve-prev-block-state",
|
|
"reserve-next-block-state",
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn init_static_cctx_stops_after_tmp_workspace_reservation_failure() {
|
|
let mut context = InitStaticCCtxTestContext {
|
|
reserved_cctx: ptr::dangling_mut(),
|
|
check_available_result: 1,
|
|
reserve_tmp_workspace_result: ptr::null_mut(),
|
|
..Default::default()
|
|
};
|
|
let mut workspace = [0usize; 1];
|
|
let state = init_static_cctx_test_state(
|
|
&mut context,
|
|
workspace.as_mut_ptr().cast(),
|
|
size_of_val(&workspace),
|
|
size_of_val(&workspace) - 1,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_initStaticCCtx(&state) };
|
|
|
|
assert!(result.is_null());
|
|
assert_eq!(
|
|
context.events,
|
|
[
|
|
"create-workspace",
|
|
"reserve-object",
|
|
"zero",
|
|
"move-workspace",
|
|
"check-available",
|
|
"reserve-prev-block-state",
|
|
"reserve-next-block-state",
|
|
"reserve-tmp-workspace",
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn init_static_cctx_rejects_a_null_state() {
|
|
let result = unsafe { ZSTD_rust_initStaticCCtx(ptr::null()) };
|
|
|
|
assert!(result.is_null());
|
|
}
|
|
|
|
#[test]
|
|
fn init_static_cctx_rejects_a_null_workspace() {
|
|
let mut context = InitStaticCCtxTestContext {
|
|
reserved_cctx: ptr::dangling_mut(),
|
|
check_available_result: 1,
|
|
reserve_tmp_workspace_result: ptr::dangling_mut(),
|
|
..Default::default()
|
|
};
|
|
let state = init_static_cctx_test_state(&mut context, ptr::null_mut(), usize::MAX, 0);
|
|
|
|
let result = unsafe { ZSTD_rust_initStaticCCtx(&state) };
|
|
|
|
assert!(result.is_null());
|
|
assert!(context.events.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn init_static_cctx_rejects_an_unaligned_workspace_before_callback() {
|
|
let mut context = InitStaticCCtxTestContext::default();
|
|
let mut workspace = [0usize; 2];
|
|
let unaligned_workspace = workspace.as_mut_ptr().cast::<u8>().wrapping_add(1).cast();
|
|
let state = init_static_cctx_test_state(&mut context, unaligned_workspace, usize::MAX, 0);
|
|
|
|
let result = unsafe { ZSTD_rust_initStaticCCtx(&state) };
|
|
|
|
assert!(result.is_null());
|
|
assert!(context.events.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn init_static_cctx_rejects_a_small_workspace_before_callback() {
|
|
let mut context = InitStaticCCtxTestContext::default();
|
|
let mut workspace = [0usize; 1];
|
|
let state = init_static_cctx_test_state(
|
|
&mut context,
|
|
workspace.as_mut_ptr().cast(),
|
|
size_of_val(&workspace),
|
|
size_of_val(&workspace),
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_initStaticCCtx(&state) };
|
|
|
|
assert!(result.is_null());
|
|
assert!(context.events.is_empty());
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct FreeCCtxTestContext {
|
|
events: Vec<&'static str>,
|
|
}
|
|
|
|
unsafe extern "C" fn free_cctx_test_content(context: *mut c_void) {
|
|
let context = unsafe { &mut *context.cast::<FreeCCtxTestContext>() };
|
|
context.events.push("content");
|
|
}
|
|
|
|
unsafe extern "C" fn free_cctx_test_object(context: *mut c_void) {
|
|
let context = unsafe { &mut *context.cast::<FreeCCtxTestContext>() };
|
|
context.events.push("object");
|
|
}
|
|
|
|
fn free_cctx_test_state(
|
|
context: *mut c_void,
|
|
static_size: usize,
|
|
cctx_in_workspace: c_int,
|
|
) -> ZSTD_rust_freeCCtxState {
|
|
ZSTD_rust_freeCCtxState {
|
|
callback_context: context,
|
|
static_size,
|
|
cctx_in_workspace,
|
|
free_content: free_cctx_test_content,
|
|
free_object: free_cctx_test_object,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn free_cctx_frees_content_before_an_external_object() {
|
|
let mut context = FreeCCtxTestContext::default();
|
|
let state = free_cctx_test_state((&mut context as *mut FreeCCtxTestContext).cast(), 0, 0);
|
|
|
|
let result = unsafe { ZSTD_rust_freeCCtx(&state) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(context.events, ["content", "object"]);
|
|
}
|
|
|
|
#[test]
|
|
fn free_cctx_skips_the_external_object_when_embedded_in_workspace() {
|
|
let mut context = FreeCCtxTestContext::default();
|
|
let state = free_cctx_test_state((&mut context as *mut FreeCCtxTestContext).cast(), 0, 1);
|
|
|
|
let result = unsafe { ZSTD_rust_freeCCtx(&state) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(context.events, ["content"]);
|
|
}
|
|
|
|
#[test]
|
|
fn free_cctx_rejects_static_contexts_before_any_callback() {
|
|
let mut context = FreeCCtxTestContext::default();
|
|
let state = free_cctx_test_state((&mut context as *mut FreeCCtxTestContext).cast(), 1, 0);
|
|
|
|
let result = unsafe { ZSTD_rust_freeCCtx(&state) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::MemoryAllocation));
|
|
assert!(context.events.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn free_cctx_accepts_a_null_context() {
|
|
let state = free_cctx_test_state(ptr::null_mut(), 0, 0);
|
|
|
|
let result = unsafe { ZSTD_rust_freeCCtx(&state) };
|
|
|
|
assert_eq!(result, 0);
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct SetCParamsTestContext {
|
|
events: Vec<&'static str>,
|
|
parameter_calls: Vec<(c_int, c_int)>,
|
|
check_result: usize,
|
|
set_result: usize,
|
|
fail_at: usize,
|
|
}
|
|
|
|
unsafe fn set_cparams_test_context(context: *mut c_void) -> &'static mut SetCParamsTestContext {
|
|
unsafe { &mut *context.cast::<SetCParamsTestContext>() }
|
|
}
|
|
|
|
unsafe extern "C" fn set_cparams_test_check(
|
|
context: *mut c_void,
|
|
_cparams: ZSTD_compressionParameters,
|
|
) -> usize {
|
|
let context = unsafe { set_cparams_test_context(context) };
|
|
context.events.push("check");
|
|
context.check_result
|
|
}
|
|
|
|
unsafe extern "C" fn set_cparams_test_set_parameter(
|
|
context: *mut c_void,
|
|
param: c_int,
|
|
value: c_int,
|
|
) -> usize {
|
|
let context = unsafe { set_cparams_test_context(context) };
|
|
context.events.push("set");
|
|
context.parameter_calls.push((param, value));
|
|
if context.fail_at != 0 && context.parameter_calls.len() == context.fail_at {
|
|
context.set_result
|
|
} else {
|
|
0
|
|
}
|
|
}
|
|
|
|
fn set_cparams_test_state(context: &mut SetCParamsTestContext) -> ZSTD_rust_setCParamsState {
|
|
ZSTD_rust_setCParamsState {
|
|
callback_context: (context as *mut SetCParamsTestContext).cast(),
|
|
check_c_params: set_cparams_test_check,
|
|
set_parameter: set_cparams_test_set_parameter,
|
|
}
|
|
}
|
|
|
|
fn set_cparams_test_params() -> ZSTD_compressionParameters {
|
|
ZSTD_compressionParameters {
|
|
windowLog: 10,
|
|
chainLog: 11,
|
|
hashLog: 12,
|
|
searchLog: 13,
|
|
minMatch: 4,
|
|
targetLength: 5,
|
|
strategy: 6,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn set_cparams_preserves_validation_and_parameter_order() {
|
|
let cparams = set_cparams_test_params();
|
|
let mut context = SetCParamsTestContext::default();
|
|
let state = set_cparams_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_setCParams(&state, cparams) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(
|
|
context.events,
|
|
["check", "set", "set", "set", "set", "set", "set", "set"]
|
|
);
|
|
assert_eq!(
|
|
context.parameter_calls,
|
|
[
|
|
(ZSTD_C_WINDOW_LOG, 10),
|
|
(ZSTD_C_CHAIN_LOG, 11),
|
|
(ZSTD_C_HASH_LOG, 12),
|
|
(ZSTD_C_SEARCH_LOG, 13),
|
|
(ZSTD_C_MIN_MATCH, 4),
|
|
(ZSTD_C_TARGET_LENGTH, 5),
|
|
(ZSTD_C_STRATEGY, 6),
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn set_cparams_stops_before_updates_after_validation_error() {
|
|
let cparams = set_cparams_test_params();
|
|
let mut context = SetCParamsTestContext {
|
|
check_result: ERROR(ZstdErrorCode::ParameterOutOfBound),
|
|
..SetCParamsTestContext::default()
|
|
};
|
|
let state = set_cparams_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_setCParams(&state, cparams) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::ParameterOutOfBound));
|
|
assert_eq!(context.events, ["check"]);
|
|
assert!(context.parameter_calls.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn set_cparams_propagates_parameter_error_after_prior_updates() {
|
|
let cparams = set_cparams_test_params();
|
|
let mut context = SetCParamsTestContext {
|
|
set_result: ERROR(ZstdErrorCode::StageWrong),
|
|
fail_at: 4,
|
|
..SetCParamsTestContext::default()
|
|
};
|
|
let state = set_cparams_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_setCParams(&state, cparams) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::StageWrong));
|
|
assert_eq!(context.events, ["check", "set", "set", "set", "set"]);
|
|
assert_eq!(context.parameter_calls.len(), 4);
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct SetFParamsTestContext {
|
|
parameter_calls: Vec<(c_int, c_int)>,
|
|
set_result: usize,
|
|
fail_at: usize,
|
|
}
|
|
|
|
unsafe fn set_fparams_test_context(context: *mut c_void) -> &'static mut SetFParamsTestContext {
|
|
unsafe { &mut *context.cast::<SetFParamsTestContext>() }
|
|
}
|
|
|
|
unsafe extern "C" fn set_fparams_test_set_parameter(
|
|
context: *mut c_void,
|
|
param: c_int,
|
|
value: c_int,
|
|
) -> usize {
|
|
let context = unsafe { set_fparams_test_context(context) };
|
|
context.parameter_calls.push((param, value));
|
|
if context.fail_at != 0 && context.parameter_calls.len() == context.fail_at {
|
|
context.set_result
|
|
} else {
|
|
0
|
|
}
|
|
}
|
|
|
|
fn set_fparams_test_state(context: &mut SetFParamsTestContext) -> ZSTD_rust_setFParamsState {
|
|
ZSTD_rust_setFParamsState {
|
|
callback_context: (context as *mut SetFParamsTestContext).cast(),
|
|
set_parameter: set_fparams_test_set_parameter,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn set_fparams_preserves_parameter_order_and_boolean_conversions() {
|
|
let fparams = ZSTD_frameParameters {
|
|
contentSizeFlag: 0,
|
|
checksumFlag: 2,
|
|
noDictIDFlag: 7,
|
|
};
|
|
let mut context = SetFParamsTestContext::default();
|
|
let state = set_fparams_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_setFParams(&state, fparams) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(
|
|
context.parameter_calls,
|
|
[
|
|
(ZSTD_C_CONTENT_SIZE_FLAG, 0),
|
|
(ZSTD_C_CHECKSUM_FLAG, 1),
|
|
(ZSTD_C_DICT_ID_FLAG, 0),
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn set_fparams_stops_after_the_first_parameter_error() {
|
|
let fparams = ZSTD_frameParameters {
|
|
contentSizeFlag: 1,
|
|
checksumFlag: 1,
|
|
noDictIDFlag: 0,
|
|
};
|
|
let mut context = SetFParamsTestContext {
|
|
set_result: ERROR(ZstdErrorCode::StageWrong),
|
|
fail_at: 1,
|
|
..SetFParamsTestContext::default()
|
|
};
|
|
let state = set_fparams_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_setFParams(&state, fparams) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::StageWrong));
|
|
assert_eq!(context.parameter_calls, [(ZSTD_C_CONTENT_SIZE_FLAG, 1)]);
|
|
}
|
|
|
|
#[test]
|
|
fn set_fparams_propagates_a_later_parameter_error_without_following_updates() {
|
|
let fparams = ZSTD_frameParameters {
|
|
contentSizeFlag: 1,
|
|
checksumFlag: 0,
|
|
noDictIDFlag: 0,
|
|
};
|
|
let mut context = SetFParamsTestContext {
|
|
set_result: ERROR(ZstdErrorCode::ParameterOutOfBound),
|
|
fail_at: 2,
|
|
..SetFParamsTestContext::default()
|
|
};
|
|
let state = set_fparams_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_setFParams(&state, fparams) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::ParameterOutOfBound));
|
|
assert_eq!(
|
|
context.parameter_calls,
|
|
[(ZSTD_C_CONTENT_SIZE_FLAG, 1), (ZSTD_C_CHECKSUM_FLAG, 0),]
|
|
);
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct SetParamsTestContext {
|
|
events: Vec<&'static str>,
|
|
received_cparams: Option<ZSTD_compressionParameters>,
|
|
received_fparams: Option<ZSTD_frameParameters>,
|
|
check_result: usize,
|
|
f_params_result: usize,
|
|
c_params_result: usize,
|
|
}
|
|
|
|
unsafe fn set_params_test_context(context: *mut c_void) -> &'static mut SetParamsTestContext {
|
|
unsafe { &mut *context.cast::<SetParamsTestContext>() }
|
|
}
|
|
|
|
unsafe extern "C" fn set_params_test_check(
|
|
context: *mut c_void,
|
|
cparams: ZSTD_compressionParameters,
|
|
) -> usize {
|
|
let context = unsafe { set_params_test_context(context) };
|
|
context.events.push("check");
|
|
context.received_cparams = Some(cparams);
|
|
context.check_result
|
|
}
|
|
|
|
unsafe extern "C" fn set_params_test_set_f_params(
|
|
context: *mut c_void,
|
|
fparams: ZSTD_frameParameters,
|
|
) -> usize {
|
|
let context = unsafe { set_params_test_context(context) };
|
|
context.events.push("f-params");
|
|
context.received_fparams = Some(fparams);
|
|
context.f_params_result
|
|
}
|
|
|
|
unsafe extern "C" fn set_params_test_set_c_params(
|
|
context: *mut c_void,
|
|
cparams: ZSTD_compressionParameters,
|
|
) -> usize {
|
|
let context = unsafe { set_params_test_context(context) };
|
|
context.events.push("c-params");
|
|
context.received_cparams = Some(cparams);
|
|
context.c_params_result
|
|
}
|
|
|
|
fn set_params_test_state(context: &mut SetParamsTestContext) -> ZSTD_rust_setParamsState {
|
|
ZSTD_rust_setParamsState {
|
|
callback_context: (context as *mut SetParamsTestContext).cast(),
|
|
check_c_params: set_params_test_check,
|
|
set_f_params: set_params_test_set_f_params,
|
|
set_c_params: set_params_test_set_c_params,
|
|
}
|
|
}
|
|
|
|
fn set_params_test_params() -> ZSTD_parameters {
|
|
ZSTD_parameters {
|
|
cParams: ZSTD_compressionParameters {
|
|
windowLog: 20,
|
|
chainLog: 19,
|
|
hashLog: 18,
|
|
searchLog: 5,
|
|
minMatch: 4,
|
|
targetLength: 32,
|
|
strategy: 3,
|
|
},
|
|
fParams: ZSTD_frameParameters {
|
|
contentSizeFlag: 1,
|
|
checksumFlag: 0,
|
|
noDictIDFlag: 1,
|
|
},
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn set_params_preserves_check_frame_then_compression_order() {
|
|
let params = set_params_test_params();
|
|
let mut context = SetParamsTestContext::default();
|
|
let state = set_params_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_setParams(&state, params) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(context.events, ["check", "f-params", "c-params"]);
|
|
assert_eq!(context.received_cparams, Some(params.cParams));
|
|
assert_eq!(context.received_fparams, Some(params.fParams));
|
|
}
|
|
|
|
#[test]
|
|
fn set_params_stops_before_frame_and_compression_after_check_error() {
|
|
let params = set_params_test_params();
|
|
let mut context = SetParamsTestContext {
|
|
check_result: ERROR(ZstdErrorCode::ParameterOutOfBound),
|
|
..SetParamsTestContext::default()
|
|
};
|
|
let state = set_params_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_setParams(&state, params) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::ParameterOutOfBound));
|
|
assert_eq!(context.events, ["check"]);
|
|
assert!(context.received_fparams.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn set_params_stops_before_compression_after_frame_error() {
|
|
let params = set_params_test_params();
|
|
let mut context = SetParamsTestContext {
|
|
f_params_result: ERROR(ZstdErrorCode::StageWrong),
|
|
..SetParamsTestContext::default()
|
|
};
|
|
let state = set_params_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_setParams(&state, params) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::StageWrong));
|
|
assert_eq!(context.events, ["check", "f-params"]);
|
|
assert!(context.received_cparams.is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn set_params_propagates_compression_error_after_prior_stages() {
|
|
let params = set_params_test_params();
|
|
let mut context = SetParamsTestContext {
|
|
c_params_result: ERROR(ZstdErrorCode::StageWrong),
|
|
..SetParamsTestContext::default()
|
|
};
|
|
let state = set_params_test_state(&mut context);
|
|
|
|
let result = unsafe { ZSTD_rust_setParams(&state, params) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::StageWrong));
|
|
assert_eq!(context.events, ["check", "f-params", "c-params"]);
|
|
}
|
|
|
|
#[test]
|
|
fn pledged_src_size_writes_the_init_stage_value_plus_one() {
|
|
let mut pledged_src_size_plus_one = 0;
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_setPledgedSrcSize(
|
|
ZSTD_CSTREAM_STAGE_INIT,
|
|
123,
|
|
&mut pledged_src_size_plus_one,
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(pledged_src_size_plus_one, 124);
|
|
}
|
|
|
|
#[test]
|
|
fn pledged_src_size_wraps_at_u64_max() {
|
|
let mut pledged_src_size_plus_one = 123;
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_setPledgedSrcSize(
|
|
ZSTD_CSTREAM_STAGE_INIT,
|
|
u64::MAX,
|
|
&mut pledged_src_size_plus_one,
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(pledged_src_size_plus_one, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn pledged_src_size_rejects_non_init_stage_with_stage_wrong() {
|
|
let mut pledged_src_size_plus_one = 123;
|
|
|
|
let result = unsafe { ZSTD_rust_setPledgedSrcSize(1, 456, &mut pledged_src_size_plus_one) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::StageWrong));
|
|
assert_eq!(
|
|
crate::errors::ERR_getErrorCode(result),
|
|
ZstdErrorCode::StageWrong as i32
|
|
);
|
|
assert_eq!(pledged_src_size_plus_one, 123);
|
|
}
|
|
|
|
#[test]
|
|
fn sizeof_local_dict_ignores_size_without_a_buffer() {
|
|
assert_eq!(sizeof_local_dict(0, 37, 11), 11);
|
|
assert_eq!(ZSTD_rust_sizeofLocalDict(0, 37, 11), 11);
|
|
}
|
|
|
|
#[test]
|
|
fn sizeof_local_dict_adds_present_buffer_and_cdict_sizes() {
|
|
assert_eq!(sizeof_local_dict(1, 37, 11), 48);
|
|
assert_eq!(sizeof_local_dict(1, 0, 11), 11);
|
|
assert_eq!(ZSTD_rust_sizeofLocalDict(1, 37, 11), 48);
|
|
}
|
|
|
|
#[test]
|
|
fn sizeof_local_dict_wraps_like_c_size_t_addition() {
|
|
assert_eq!(sizeof_local_dict(1, usize::MAX, 1), 0);
|
|
assert_eq!(sizeof_local_dict(0, usize::MAX, usize::MAX), usize::MAX);
|
|
}
|
|
|
|
#[test]
|
|
fn sizeof_cdict_handles_zero_components() {
|
|
assert_eq!(sizeof_cdict(0, 0), 0);
|
|
assert_eq!(ZSTD_rust_sizeofCDict(0, 0), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn sizeof_cdict_adds_components_in_order() {
|
|
assert_eq!(sizeof_cdict(17, 25), 42);
|
|
assert_eq!(ZSTD_rust_sizeofCDict(17, 25), 42);
|
|
}
|
|
|
|
#[test]
|
|
fn sizeof_cdict_wraps_like_c_size_t_addition() {
|
|
assert_eq!(sizeof_cdict(usize::MAX, 1), 0);
|
|
assert_eq!(ZSTD_rust_sizeofCDict(usize::MAX, 1), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn sizeof_cctx_handles_zero_components() {
|
|
assert_eq!(sizeof_cctx(0, 0, 0, 0), 0);
|
|
assert_eq!(ZSTD_rust_sizeofCCtx(0, 0, 0, 0), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn sizeof_cctx_adds_components_in_order() {
|
|
assert_eq!(sizeof_cctx(1, 2, 3, 4), 10);
|
|
assert_eq!(ZSTD_rust_sizeofCCtx(1, 2, 3, 4), 10);
|
|
}
|
|
|
|
#[test]
|
|
fn sizeof_cctx_wraps_like_c_size_t_addition() {
|
|
assert_eq!(sizeof_cctx(usize::MAX, 1, 2, 3), 5);
|
|
assert_eq!(ZSTD_rust_sizeofCCtx(usize::MAX, 1, 2, 3), 5);
|
|
}
|
|
|
|
#[test]
|
|
fn estimate_workspace_size_handles_zero_components() {
|
|
assert_eq!(estimate_workspace_size(0, 0, 0, 0, 0, 0, 0, 0, 0), 0);
|
|
assert_eq!(
|
|
ZSTD_rust_estimateWorkspaceSize(0, 0, 0, 0, 0, 0, 0, 0, 0),
|
|
0
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn estimate_workspace_size_adds_components_in_order() {
|
|
assert_eq!(estimate_workspace_size(1, 2, 3, 4, 5, 6, 7, 8, 9), 45);
|
|
assert_eq!(
|
|
ZSTD_rust_estimateWorkspaceSize(1, 2, 3, 4, 5, 6, 7, 8, 9),
|
|
45
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn estimate_workspace_size_wraps_at_multiple_operand_positions() {
|
|
assert_eq!(
|
|
estimate_workspace_size(usize::MAX, 1, 0, 0, 0, 0, 0, 0, 0),
|
|
0
|
|
);
|
|
assert_eq!(
|
|
estimate_workspace_size(0, usize::MAX, 1, 0, 0, 0, 0, 0, 0),
|
|
0
|
|
);
|
|
assert_eq!(
|
|
estimate_workspace_size(0, 0, 0, 0, 0, usize::MAX, 1, 0, 0),
|
|
0
|
|
);
|
|
assert_eq!(
|
|
ZSTD_rust_estimateWorkspaceSize(0, 0, 0, 0, 0, 0, 0, usize::MAX, 1),
|
|
0
|
|
);
|
|
}
|
|
|
|
fn cctx_workspace_test_sizing() -> ZSTD_rustCCtxWorkspaceSizing {
|
|
ZSTD_rustCCtxWorkspaceSizing {
|
|
cctxSize: 32,
|
|
compressedBlockStateSize: 24,
|
|
seqDefSize: 8,
|
|
rawSeqSize: 12,
|
|
externalSequenceSize: 16,
|
|
tmpWorkspaceSize: 64,
|
|
wildcopyOverlength: 32,
|
|
matchTSize: 4,
|
|
optimalTSize: 8,
|
|
asanRedzoneSize: 0,
|
|
}
|
|
}
|
|
|
|
fn cctx_workspace_test_cparams() -> ZSTD_compressionParameters {
|
|
ZSTD_compressionParameters {
|
|
windowLog: 10,
|
|
chainLog: 6,
|
|
hashLog: 6,
|
|
searchLog: 1,
|
|
minMatch: 4,
|
|
targetLength: 16,
|
|
strategy: 3,
|
|
}
|
|
}
|
|
|
|
fn cctx_workspace_test_estimate(
|
|
sizing: &ZSTD_rustCCtxWorkspaceSizing,
|
|
ldm_enable: c_int,
|
|
ldm_hash_log: u32,
|
|
ldm_bucket_size_log: u32,
|
|
ldm_min_match_length: u32,
|
|
is_static: c_int,
|
|
buff_in_size: usize,
|
|
buff_out_size: usize,
|
|
use_sequence_producer: c_int,
|
|
) -> usize {
|
|
unsafe {
|
|
ZSTD_rust_estimateCCtxWorkspaceSize(
|
|
cctx_workspace_test_cparams(),
|
|
ldm_enable,
|
|
ldm_hash_log,
|
|
ldm_bucket_size_log,
|
|
ldm_min_match_length,
|
|
is_static,
|
|
ZSTD_RUST_PS_DISABLE,
|
|
buff_in_size,
|
|
buff_out_size,
|
|
1000,
|
|
use_sequence_producer,
|
|
0,
|
|
sizing,
|
|
)
|
|
}
|
|
}
|
|
|
|
fn cctx_reset_test_state(
|
|
sizing: &ZSTD_rustCCtxWorkspaceSizing,
|
|
plan: &mut ZSTD_rustCCtxResetPlan,
|
|
pledged_src_size: u64,
|
|
max_block_size: usize,
|
|
ldm_enable: c_int,
|
|
ldm_min_match_length: u32,
|
|
in_buffer_buffered: c_int,
|
|
out_buffer_buffered: c_int,
|
|
use_sequence_producer: c_int,
|
|
initialized: c_int,
|
|
index_too_close: c_int,
|
|
dict_too_big: c_int,
|
|
) -> ZSTD_rustCCtxResetState {
|
|
ZSTD_rustCCtxResetState {
|
|
cParams: cctx_workspace_test_cparams(),
|
|
ldmEnable: ldm_enable,
|
|
ldmHashLog: 4,
|
|
ldmBucketSizeLog: 2,
|
|
ldmMinMatchLength: ldm_min_match_length,
|
|
isStatic: 0,
|
|
useRowMatchFinder: ZSTD_RUST_PS_DISABLE,
|
|
inBufferBuffered: in_buffer_buffered,
|
|
outBufferBuffered: out_buffer_buffered,
|
|
useSequenceProducer: use_sequence_producer,
|
|
initialized,
|
|
indexTooClose: index_too_close,
|
|
dictTooBig: dict_too_big,
|
|
pledgedSrcSize: pledged_src_size,
|
|
maxBlockSize: max_block_size,
|
|
sizing,
|
|
plan,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn cctx_reset_plan_computes_window_buffers_and_sequence_capacities() {
|
|
let sizing = cctx_workspace_test_sizing();
|
|
let mut plan = ZSTD_rustCCtxResetPlan::default();
|
|
let state = cctx_reset_test_state(
|
|
&sizing,
|
|
&mut plan,
|
|
1000,
|
|
512,
|
|
ZSTD_RUST_PS_DISABLE,
|
|
0,
|
|
1,
|
|
1,
|
|
1,
|
|
1,
|
|
0,
|
|
0,
|
|
);
|
|
|
|
assert_eq!(unsafe { ZSTD_rust_planCCtxReset(&state) }, 0);
|
|
assert_eq!(plan.windowSize, 1000);
|
|
assert_eq!(plan.blockSize, 512);
|
|
assert_eq!(plan.maxNbSeq, 512 / 3);
|
|
assert_eq!(plan.buffInSize, 1000 + 512);
|
|
assert_eq!(
|
|
plan.buffOutSize,
|
|
crate::zstd_compress_api::ZSTD_compressBound(512) + 1
|
|
);
|
|
assert_eq!(
|
|
plan.maxNbExternalSeq,
|
|
crate::zstd_compress_api::ZSTD_sequenceBound(512)
|
|
);
|
|
assert_eq!(plan.maxNbLdmSeq, 0);
|
|
assert_eq!(plan.needsIndexReset, 0);
|
|
assert!(!ERR_isError(plan.neededSpace));
|
|
assert_ne!(plan.neededSpace, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn cctx_reset_plan_tracks_ldm_and_index_reset_policy() {
|
|
let sizing = cctx_workspace_test_sizing();
|
|
let mut plan = ZSTD_rustCCtxResetPlan::default();
|
|
let mut state = cctx_reset_test_state(
|
|
&sizing,
|
|
&mut plan,
|
|
u64::MAX,
|
|
2048,
|
|
ZSTD_RUST_PS_ENABLE,
|
|
64,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
);
|
|
|
|
assert_eq!(unsafe { ZSTD_rust_planCCtxReset(&state) }, 0);
|
|
assert_eq!(plan.windowSize, 1 << 10);
|
|
assert_eq!(plan.blockSize, 1 << 10);
|
|
assert_eq!(plan.maxNbLdmSeq, (1 << 10) / 64);
|
|
assert_eq!(plan.buffInSize, 0);
|
|
assert_eq!(plan.buffOutSize, 0);
|
|
assert_eq!(plan.needsIndexReset, 1);
|
|
|
|
state.initialized = 1;
|
|
state.indexTooClose = 1;
|
|
assert_eq!(unsafe { ZSTD_rust_planCCtxReset(&state) }, 0);
|
|
assert_eq!(plan.needsIndexReset, 1);
|
|
|
|
state.indexTooClose = 0;
|
|
state.dictTooBig = 1;
|
|
assert_eq!(unsafe { ZSTD_rust_planCCtxReset(&state) }, 0);
|
|
assert_eq!(plan.needsIndexReset, 1);
|
|
}
|
|
|
|
#[test]
|
|
fn cctx_reset_plan_rejects_unadjusted_enabled_ldm() {
|
|
let sizing = cctx_workspace_test_sizing();
|
|
let mut plan = ZSTD_rustCCtxResetPlan::default();
|
|
let state = cctx_reset_test_state(
|
|
&sizing,
|
|
&mut plan,
|
|
1000,
|
|
512,
|
|
ZSTD_RUST_PS_ENABLE,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
1,
|
|
0,
|
|
0,
|
|
);
|
|
|
|
assert_eq!(
|
|
unsafe { ZSTD_rust_planCCtxReset(&state) },
|
|
ERROR(ZstdErrorCode::Generic)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn cctx_reset_policy_preserves_plan_workspace_tail_order() {
|
|
let events = std::cell::RefCell::new(Vec::new());
|
|
let result = run_cctx_reset_policy(
|
|
|| {
|
|
events.borrow_mut().push("plan");
|
|
0
|
|
},
|
|
|| events.borrow_mut().push("prepare-workspace"),
|
|
|| {
|
|
events.borrow_mut().push("workspace");
|
|
0
|
|
},
|
|
|| events.borrow_mut().push("prepare-tail"),
|
|
|| {
|
|
events.borrow_mut().push("tail");
|
|
0
|
|
},
|
|
);
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(
|
|
events.into_inner(),
|
|
[
|
|
"plan",
|
|
"prepare-workspace",
|
|
"workspace",
|
|
"prepare-tail",
|
|
"tail"
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn cctx_reset_policy_propagates_workspace_error_before_tail() {
|
|
let events = std::cell::RefCell::new(Vec::new());
|
|
let workspace_error = ERROR(ZstdErrorCode::MemoryAllocation);
|
|
let result = run_cctx_reset_policy(
|
|
|| {
|
|
events.borrow_mut().push("plan");
|
|
0
|
|
},
|
|
|| events.borrow_mut().push("prepare-workspace"),
|
|
|| {
|
|
events.borrow_mut().push("workspace");
|
|
workspace_error
|
|
},
|
|
|| events.borrow_mut().push("prepare-tail"),
|
|
|| {
|
|
events.borrow_mut().push("tail");
|
|
0
|
|
},
|
|
);
|
|
|
|
assert_eq!(result, workspace_error);
|
|
assert_eq!(
|
|
events.into_inner(),
|
|
["plan", "prepare-workspace", "workspace"]
|
|
);
|
|
}
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
enum ResetCCtxStorageTestEvent {
|
|
Reserve(c_int, usize),
|
|
Pointer(c_int),
|
|
Size(c_int, usize),
|
|
Int(c_int, c_int),
|
|
Zero(usize),
|
|
WindowInit,
|
|
ResetExternalSequences,
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct ResetCCtxStorageTestContext {
|
|
events: Vec<ResetCCtxStorageTestEvent>,
|
|
reserve_count: usize,
|
|
reserve_failure: bool,
|
|
}
|
|
|
|
unsafe fn reset_cctx_storage_test_context(
|
|
context: *mut c_void,
|
|
) -> &'static mut ResetCCtxStorageTestContext {
|
|
unsafe { &mut *context.cast::<ResetCCtxStorageTestContext>() }
|
|
}
|
|
|
|
unsafe extern "C" fn reset_cctx_storage_test_set_pointer(
|
|
context: *mut c_void,
|
|
kind: c_int,
|
|
_pointer: *mut c_void,
|
|
) {
|
|
let context = unsafe { reset_cctx_storage_test_context(context) };
|
|
context
|
|
.events
|
|
.push(ResetCCtxStorageTestEvent::Pointer(kind));
|
|
}
|
|
|
|
unsafe extern "C" fn reset_cctx_storage_test_set_size(
|
|
context: *mut c_void,
|
|
kind: c_int,
|
|
value: usize,
|
|
) {
|
|
let context = unsafe { reset_cctx_storage_test_context(context) };
|
|
context
|
|
.events
|
|
.push(ResetCCtxStorageTestEvent::Size(kind, value));
|
|
}
|
|
|
|
unsafe extern "C" fn reset_cctx_storage_test_set_int(
|
|
context: *mut c_void,
|
|
kind: c_int,
|
|
value: c_int,
|
|
) {
|
|
let context = unsafe { reset_cctx_storage_test_context(context) };
|
|
context
|
|
.events
|
|
.push(ResetCCtxStorageTestEvent::Int(kind, value));
|
|
}
|
|
|
|
unsafe extern "C" fn reset_cctx_storage_test_reserve(
|
|
context: *mut c_void,
|
|
kind: c_int,
|
|
size: usize,
|
|
) -> *mut c_void {
|
|
let context = unsafe { reset_cctx_storage_test_context(context) };
|
|
context
|
|
.events
|
|
.push(ResetCCtxStorageTestEvent::Reserve(kind, size));
|
|
let pointer = (0x1000usize + context.reserve_count * 0x1000) as *mut c_void;
|
|
context.reserve_count += 1;
|
|
pointer
|
|
}
|
|
|
|
unsafe extern "C" fn reset_cctx_storage_test_reserve_failed(context: *mut c_void) -> c_int {
|
|
let context = unsafe { reset_cctx_storage_test_context(context) };
|
|
c_int::from(context.reserve_failure)
|
|
}
|
|
|
|
unsafe extern "C" fn reset_cctx_storage_test_zero(
|
|
context: *mut c_void,
|
|
_pointer: *mut c_void,
|
|
size: usize,
|
|
) {
|
|
let context = unsafe { reset_cctx_storage_test_context(context) };
|
|
context.events.push(ResetCCtxStorageTestEvent::Zero(size));
|
|
}
|
|
|
|
unsafe extern "C" fn reset_cctx_storage_test_window_init(context: *mut c_void) {
|
|
let context = unsafe { reset_cctx_storage_test_context(context) };
|
|
context.events.push(ResetCCtxStorageTestEvent::WindowInit);
|
|
}
|
|
|
|
unsafe extern "C" fn reset_cctx_storage_test_reset_external_sequences(context: *mut c_void) {
|
|
let context = unsafe { reset_cctx_storage_test_context(context) };
|
|
context
|
|
.events
|
|
.push(ResetCCtxStorageTestEvent::ResetExternalSequences);
|
|
}
|
|
|
|
fn reset_cctx_storage_test_state(
|
|
context: &mut ResetCCtxStorageTestContext,
|
|
ldm_enable: c_int,
|
|
has_ext_seq_prod: c_int,
|
|
block_size: usize,
|
|
max_nb_seq: usize,
|
|
max_nb_ldm_seq: usize,
|
|
max_nb_external_seq: usize,
|
|
buff_in_size: usize,
|
|
buff_out_size: usize,
|
|
) -> ZSTD_rust_resetCCtxStorageState {
|
|
ZSTD_rust_resetCCtxStorageState {
|
|
callbackContext: (context as *mut ResetCCtxStorageTestContext).cast(),
|
|
ldmEnable: ldm_enable,
|
|
hasExtSeqProd: has_ext_seq_prod,
|
|
hashLog: 4,
|
|
bucketSizeLog: 2,
|
|
blockSize: block_size,
|
|
maxNbSeq: max_nb_seq,
|
|
maxNbLdmSeq: max_nb_ldm_seq,
|
|
maxNbExternalSeq: max_nb_external_seq,
|
|
buffInSize: buff_in_size,
|
|
buffOutSize: buff_out_size,
|
|
seqDefSize: size_of::<SeqDef>(),
|
|
ldmEntrySize: 8,
|
|
rawSeqSize: 12,
|
|
externalSequenceSize: size_of::<ZSTD_Sequence>(),
|
|
byteSize: 1,
|
|
wildcopyOverlength: 8,
|
|
bufferedPolicy: ZSTD_BM_BUFFERED,
|
|
setPointer: Some(reset_cctx_storage_test_set_pointer),
|
|
setSize: Some(reset_cctx_storage_test_set_size),
|
|
setInt: Some(reset_cctx_storage_test_set_int),
|
|
reserve: Some(reset_cctx_storage_test_reserve),
|
|
reserveFailed: Some(reset_cctx_storage_test_reserve_failed),
|
|
zero: Some(reset_cctx_storage_test_zero),
|
|
windowInit: Some(reset_cctx_storage_test_window_init),
|
|
resetExternalSequences: Some(reset_cctx_storage_test_reset_external_sequences),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn cctx_storage_preserves_ordinary_reservation_and_publication_order() {
|
|
let mut context = ResetCCtxStorageTestContext::default();
|
|
let state = reset_cctx_storage_test_state(
|
|
&mut context,
|
|
ZSTD_RUST_PS_DISABLE,
|
|
0,
|
|
128,
|
|
4,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
);
|
|
|
|
assert_eq!(unsafe { ZSTD_rust_resetCCtxStorage(&state) }, 0);
|
|
assert_eq!(
|
|
context.events,
|
|
[
|
|
ResetCCtxStorageTestEvent::Reserve(
|
|
RESET_CCTX_RESERVE_ALIGNED64,
|
|
4 * size_of::<SeqDef>()
|
|
),
|
|
ResetCCtxStorageTestEvent::Pointer(RESET_CCTX_POINTER_SEQ_START),
|
|
ResetCCtxStorageTestEvent::Reserve(RESET_CCTX_RESERVE_BUFFER, 136),
|
|
ResetCCtxStorageTestEvent::Pointer(RESET_CCTX_POINTER_LITERALS),
|
|
ResetCCtxStorageTestEvent::Size(RESET_CCTX_SIZE_MAX_NB_LIT, 128),
|
|
ResetCCtxStorageTestEvent::Int(RESET_CCTX_INT_BUFFERED_POLICY, ZSTD_BM_BUFFERED),
|
|
ResetCCtxStorageTestEvent::Reserve(RESET_CCTX_RESERVE_BUFFER, 0),
|
|
ResetCCtxStorageTestEvent::Pointer(RESET_CCTX_POINTER_INPUT_BUFFER),
|
|
ResetCCtxStorageTestEvent::Size(RESET_CCTX_SIZE_INPUT_BUFFER, 0),
|
|
ResetCCtxStorageTestEvent::Reserve(RESET_CCTX_RESERVE_BUFFER, 0),
|
|
ResetCCtxStorageTestEvent::Pointer(RESET_CCTX_POINTER_OUTPUT_BUFFER),
|
|
ResetCCtxStorageTestEvent::Size(RESET_CCTX_SIZE_OUTPUT_BUFFER, 0),
|
|
ResetCCtxStorageTestEvent::ResetExternalSequences,
|
|
ResetCCtxStorageTestEvent::Size(RESET_CCTX_SIZE_MAX_NB_SEQ, 4),
|
|
ResetCCtxStorageTestEvent::Reserve(RESET_CCTX_RESERVE_BUFFER, 4),
|
|
ResetCCtxStorageTestEvent::Pointer(RESET_CCTX_POINTER_LL_CODE),
|
|
ResetCCtxStorageTestEvent::Reserve(RESET_CCTX_RESERVE_BUFFER, 4),
|
|
ResetCCtxStorageTestEvent::Pointer(RESET_CCTX_POINTER_ML_CODE),
|
|
ResetCCtxStorageTestEvent::Reserve(RESET_CCTX_RESERVE_BUFFER, 4),
|
|
ResetCCtxStorageTestEvent::Pointer(RESET_CCTX_POINTER_OF_CODE),
|
|
ResetCCtxStorageTestEvent::Int(RESET_CCTX_INT_INITIALIZED, 1),
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn cctx_storage_preserves_ldm_and_external_sequence_order() {
|
|
let mut context = ResetCCtxStorageTestContext::default();
|
|
let state = reset_cctx_storage_test_state(
|
|
&mut context,
|
|
ZSTD_RUST_PS_ENABLE,
|
|
1,
|
|
128,
|
|
4,
|
|
2,
|
|
3,
|
|
17,
|
|
19,
|
|
);
|
|
let external_size = 3 * size_of::<ZSTD_Sequence>();
|
|
|
|
assert_eq!(unsafe { ZSTD_rust_resetCCtxStorage(&state) }, 0);
|
|
assert_eq!(
|
|
context.events,
|
|
[
|
|
ResetCCtxStorageTestEvent::Reserve(
|
|
RESET_CCTX_RESERVE_ALIGNED64,
|
|
4 * size_of::<SeqDef>()
|
|
),
|
|
ResetCCtxStorageTestEvent::Pointer(RESET_CCTX_POINTER_SEQ_START),
|
|
ResetCCtxStorageTestEvent::Reserve(RESET_CCTX_RESERVE_ALIGNED64, 16 * 8),
|
|
ResetCCtxStorageTestEvent::Pointer(RESET_CCTX_POINTER_LDM_HASH),
|
|
ResetCCtxStorageTestEvent::Zero(16 * 8),
|
|
ResetCCtxStorageTestEvent::Reserve(RESET_CCTX_RESERVE_ALIGNED64, 2 * 12),
|
|
ResetCCtxStorageTestEvent::Pointer(RESET_CCTX_POINTER_LDM_SEQUENCES),
|
|
ResetCCtxStorageTestEvent::Size(RESET_CCTX_SIZE_MAX_NB_LDM_SEQ, 2),
|
|
ResetCCtxStorageTestEvent::WindowInit,
|
|
ResetCCtxStorageTestEvent::Reserve(RESET_CCTX_RESERVE_ALIGNED64, external_size),
|
|
ResetCCtxStorageTestEvent::Pointer(RESET_CCTX_POINTER_EXTERNAL_SEQUENCES),
|
|
ResetCCtxStorageTestEvent::Size(RESET_CCTX_SIZE_EXTERNAL_SEQ_CAPACITY, 3),
|
|
ResetCCtxStorageTestEvent::Reserve(RESET_CCTX_RESERVE_BUFFER, 136),
|
|
ResetCCtxStorageTestEvent::Pointer(RESET_CCTX_POINTER_LITERALS),
|
|
ResetCCtxStorageTestEvent::Size(RESET_CCTX_SIZE_MAX_NB_LIT, 128),
|
|
ResetCCtxStorageTestEvent::Int(RESET_CCTX_INT_BUFFERED_POLICY, ZSTD_BM_BUFFERED),
|
|
ResetCCtxStorageTestEvent::Reserve(RESET_CCTX_RESERVE_BUFFER, 17),
|
|
ResetCCtxStorageTestEvent::Pointer(RESET_CCTX_POINTER_INPUT_BUFFER),
|
|
ResetCCtxStorageTestEvent::Size(RESET_CCTX_SIZE_INPUT_BUFFER, 17),
|
|
ResetCCtxStorageTestEvent::Reserve(RESET_CCTX_RESERVE_BUFFER, 19),
|
|
ResetCCtxStorageTestEvent::Pointer(RESET_CCTX_POINTER_OUTPUT_BUFFER),
|
|
ResetCCtxStorageTestEvent::Size(RESET_CCTX_SIZE_OUTPUT_BUFFER, 19),
|
|
ResetCCtxStorageTestEvent::Reserve(RESET_CCTX_RESERVE_BUFFER, 4),
|
|
ResetCCtxStorageTestEvent::Pointer(RESET_CCTX_POINTER_LDM_BUCKETS),
|
|
ResetCCtxStorageTestEvent::Zero(4),
|
|
ResetCCtxStorageTestEvent::ResetExternalSequences,
|
|
ResetCCtxStorageTestEvent::Size(RESET_CCTX_SIZE_MAX_NB_SEQ, 4),
|
|
ResetCCtxStorageTestEvent::Reserve(RESET_CCTX_RESERVE_BUFFER, 4),
|
|
ResetCCtxStorageTestEvent::Pointer(RESET_CCTX_POINTER_LL_CODE),
|
|
ResetCCtxStorageTestEvent::Reserve(RESET_CCTX_RESERVE_BUFFER, 4),
|
|
ResetCCtxStorageTestEvent::Pointer(RESET_CCTX_POINTER_ML_CODE),
|
|
ResetCCtxStorageTestEvent::Reserve(RESET_CCTX_RESERVE_BUFFER, 4),
|
|
ResetCCtxStorageTestEvent::Pointer(RESET_CCTX_POINTER_OF_CODE),
|
|
ResetCCtxStorageTestEvent::Int(RESET_CCTX_INT_INITIALIZED, 1),
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn cctx_storage_propagates_reservation_failure_before_publication() {
|
|
let mut context = ResetCCtxStorageTestContext {
|
|
reserve_failure: true,
|
|
..ResetCCtxStorageTestContext::default()
|
|
};
|
|
let state = reset_cctx_storage_test_state(
|
|
&mut context,
|
|
ZSTD_RUST_PS_DISABLE,
|
|
0,
|
|
128,
|
|
4,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
);
|
|
|
|
assert_eq!(
|
|
unsafe { ZSTD_rust_resetCCtxStorage(&state) },
|
|
ERROR(ZstdErrorCode::MemoryAllocation)
|
|
);
|
|
assert_eq!(
|
|
context.events,
|
|
[ResetCCtxStorageTestEvent::Reserve(
|
|
RESET_CCTX_RESERVE_ALIGNED64,
|
|
4 * size_of::<SeqDef>()
|
|
)]
|
|
);
|
|
}
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
enum ResetCCtxWorkspaceTestEvent {
|
|
BumpOversizedDuration,
|
|
FreeWorkspace,
|
|
CreateWorkspace(usize),
|
|
ReserveObject(usize),
|
|
Pointer(c_int),
|
|
Size(c_int, usize),
|
|
ClearWorkspace,
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct ResetCCtxWorkspaceTestContext {
|
|
events: Vec<ResetCCtxWorkspaceTestEvent>,
|
|
reserve_count: usize,
|
|
create_result: usize,
|
|
}
|
|
|
|
unsafe fn reset_cctx_workspace_test_context(
|
|
context: *mut c_void,
|
|
) -> &'static mut ResetCCtxWorkspaceTestContext {
|
|
unsafe { &mut *context.cast::<ResetCCtxWorkspaceTestContext>() }
|
|
}
|
|
|
|
unsafe extern "C" fn reset_cctx_workspace_test_bump(context: *mut c_void) {
|
|
let context = unsafe { reset_cctx_workspace_test_context(context) };
|
|
context
|
|
.events
|
|
.push(ResetCCtxWorkspaceTestEvent::BumpOversizedDuration);
|
|
}
|
|
|
|
unsafe extern "C" fn reset_cctx_workspace_test_free(context: *mut c_void) {
|
|
let context = unsafe { reset_cctx_workspace_test_context(context) };
|
|
context
|
|
.events
|
|
.push(ResetCCtxWorkspaceTestEvent::FreeWorkspace);
|
|
}
|
|
|
|
unsafe extern "C" fn reset_cctx_workspace_test_create(
|
|
context: *mut c_void,
|
|
needed_space: usize,
|
|
) -> usize {
|
|
let context = unsafe { reset_cctx_workspace_test_context(context) };
|
|
context
|
|
.events
|
|
.push(ResetCCtxWorkspaceTestEvent::CreateWorkspace(needed_space));
|
|
context.create_result
|
|
}
|
|
|
|
unsafe extern "C" fn reset_cctx_workspace_test_reserve_object(
|
|
context: *mut c_void,
|
|
size: usize,
|
|
) -> *mut c_void {
|
|
let context = unsafe { reset_cctx_workspace_test_context(context) };
|
|
context
|
|
.events
|
|
.push(ResetCCtxWorkspaceTestEvent::ReserveObject(size));
|
|
let pointer = (0x1000usize + context.reserve_count * 0x1000) as *mut c_void;
|
|
context.reserve_count += 1;
|
|
pointer
|
|
}
|
|
|
|
unsafe extern "C" fn reset_cctx_workspace_test_set_pointer(
|
|
context: *mut c_void,
|
|
kind: c_int,
|
|
_pointer: *mut c_void,
|
|
) {
|
|
let context = unsafe { reset_cctx_workspace_test_context(context) };
|
|
context
|
|
.events
|
|
.push(ResetCCtxWorkspaceTestEvent::Pointer(kind));
|
|
}
|
|
|
|
unsafe extern "C" fn reset_cctx_workspace_test_set_size(
|
|
context: *mut c_void,
|
|
kind: c_int,
|
|
value: usize,
|
|
) {
|
|
let context = unsafe { reset_cctx_workspace_test_context(context) };
|
|
context
|
|
.events
|
|
.push(ResetCCtxWorkspaceTestEvent::Size(kind, value));
|
|
}
|
|
|
|
unsafe extern "C" fn reset_cctx_workspace_test_clear(context: *mut c_void) {
|
|
let context = unsafe { reset_cctx_workspace_test_context(context) };
|
|
context
|
|
.events
|
|
.push(ResetCCtxWorkspaceTestEvent::ClearWorkspace);
|
|
}
|
|
|
|
fn reset_cctx_workspace_test_state(
|
|
context: &mut ResetCCtxWorkspaceTestContext,
|
|
is_static: c_int,
|
|
workspace_too_small: c_int,
|
|
workspace_wasteful: c_int,
|
|
needed_space: usize,
|
|
compressed_block_state_size: usize,
|
|
tmp_workspace_size: usize,
|
|
needs_index_reset: &mut c_int,
|
|
) -> ZSTD_rust_resetCCtxWorkspaceState {
|
|
ZSTD_rust_resetCCtxWorkspaceState {
|
|
callbackContext: (context as *mut ResetCCtxWorkspaceTestContext).cast(),
|
|
isStatic: is_static,
|
|
workspaceTooSmall: workspace_too_small,
|
|
workspaceWasteful: workspace_wasteful,
|
|
neededSpace: needed_space,
|
|
compressedBlockStateSize: compressed_block_state_size,
|
|
tmpWorkspaceSize: tmp_workspace_size,
|
|
needsIndexReset: needs_index_reset,
|
|
bumpOversizedDuration: Some(reset_cctx_workspace_test_bump),
|
|
freeWorkspace: Some(reset_cctx_workspace_test_free),
|
|
createWorkspace: Some(reset_cctx_workspace_test_create),
|
|
reserveObject: Some(reset_cctx_workspace_test_reserve_object),
|
|
setPointer: Some(reset_cctx_workspace_test_set_pointer),
|
|
setSize: Some(reset_cctx_workspace_test_set_size),
|
|
clearWorkspace: Some(reset_cctx_workspace_test_clear),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn cctx_workspace_preserves_existing_workspace_and_clear_order() {
|
|
let mut context = ResetCCtxWorkspaceTestContext::default();
|
|
let mut needs_index_reset = 0;
|
|
let state = reset_cctx_workspace_test_state(
|
|
&mut context,
|
|
0,
|
|
0,
|
|
0,
|
|
4096,
|
|
128,
|
|
64,
|
|
&mut needs_index_reset,
|
|
);
|
|
|
|
assert_eq!(unsafe { ZSTD_rust_resetCCtxWorkspace(&state) }, 0);
|
|
assert_eq!(needs_index_reset, 0);
|
|
assert_eq!(
|
|
context.events,
|
|
[
|
|
ResetCCtxWorkspaceTestEvent::BumpOversizedDuration,
|
|
ResetCCtxWorkspaceTestEvent::ClearWorkspace,
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn cctx_workspace_preserves_resize_reservation_and_publication_order() {
|
|
let mut context = ResetCCtxWorkspaceTestContext::default();
|
|
let mut needs_index_reset = 0;
|
|
let state = reset_cctx_workspace_test_state(
|
|
&mut context,
|
|
0,
|
|
1,
|
|
0,
|
|
4096,
|
|
128,
|
|
64,
|
|
&mut needs_index_reset,
|
|
);
|
|
|
|
assert_eq!(unsafe { ZSTD_rust_resetCCtxWorkspace(&state) }, 0);
|
|
assert_eq!(needs_index_reset, RESET_CCTX_INDEX_RESET);
|
|
assert_eq!(
|
|
context.events,
|
|
[
|
|
ResetCCtxWorkspaceTestEvent::BumpOversizedDuration,
|
|
ResetCCtxWorkspaceTestEvent::FreeWorkspace,
|
|
ResetCCtxWorkspaceTestEvent::CreateWorkspace(4096),
|
|
ResetCCtxWorkspaceTestEvent::ReserveObject(128),
|
|
ResetCCtxWorkspaceTestEvent::Pointer(RESET_CCTX_POINTER_PREV_CBLOCK),
|
|
ResetCCtxWorkspaceTestEvent::ReserveObject(128),
|
|
ResetCCtxWorkspaceTestEvent::Pointer(RESET_CCTX_POINTER_NEXT_CBLOCK),
|
|
ResetCCtxWorkspaceTestEvent::ReserveObject(64),
|
|
ResetCCtxWorkspaceTestEvent::Pointer(RESET_CCTX_POINTER_TMP_WORKSPACE),
|
|
ResetCCtxWorkspaceTestEvent::Size(RESET_CCTX_SIZE_TMP_WORKSPACE, 64),
|
|
ResetCCtxWorkspaceTestEvent::ClearWorkspace,
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn cctx_workspace_rejects_static_resize_before_mutating_workspace() {
|
|
let mut context = ResetCCtxWorkspaceTestContext::default();
|
|
let mut needs_index_reset = 0;
|
|
let state = reset_cctx_workspace_test_state(
|
|
&mut context,
|
|
1,
|
|
1,
|
|
0,
|
|
4096,
|
|
128,
|
|
64,
|
|
&mut needs_index_reset,
|
|
);
|
|
|
|
assert_eq!(
|
|
unsafe { ZSTD_rust_resetCCtxWorkspace(&state) },
|
|
ERROR(ZstdErrorCode::MemoryAllocation)
|
|
);
|
|
assert_eq!(needs_index_reset, 0);
|
|
assert!(context.events.is_empty());
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct ResetCCtxTailTestContext {
|
|
events: Vec<&'static str>,
|
|
match_result: usize,
|
|
storage_result: usize,
|
|
}
|
|
|
|
unsafe fn reset_cctx_tail_test_context(
|
|
context: *mut c_void,
|
|
) -> &'static mut ResetCCtxTailTestContext {
|
|
unsafe { &mut *context.cast::<ResetCCtxTailTestContext>() }
|
|
}
|
|
|
|
unsafe extern "C" fn reset_cctx_tail_test_initialize(context: *mut c_void) {
|
|
let context = unsafe { reset_cctx_tail_test_context(context) };
|
|
context.events.push("initialize");
|
|
}
|
|
|
|
unsafe extern "C" fn reset_cctx_tail_test_reset_match_state(context: *mut c_void) -> usize {
|
|
let context = unsafe { reset_cctx_tail_test_context(context) };
|
|
context.events.push("reset-match-state");
|
|
context.match_result
|
|
}
|
|
|
|
unsafe extern "C" fn reset_cctx_tail_test_reset_storage(context: *mut c_void) -> usize {
|
|
let context = unsafe { reset_cctx_tail_test_context(context) };
|
|
context.events.push("reset-storage");
|
|
context.storage_result
|
|
}
|
|
|
|
fn reset_cctx_tail_test_state(
|
|
context: &mut ResetCCtxTailTestContext,
|
|
compressed_block_state: *mut ZSTD_compressedBlockState_t,
|
|
) -> ZSTD_rust_resetCCtxTailState {
|
|
ZSTD_rust_resetCCtxTailState {
|
|
callbackContext: (context as *mut ResetCCtxTailTestContext).cast(),
|
|
initialize: Some(reset_cctx_tail_test_initialize),
|
|
compressedBlockState: compressed_block_state,
|
|
resetMatchState: Some(reset_cctx_tail_test_reset_match_state),
|
|
resetStorage: Some(reset_cctx_tail_test_reset_storage),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn cctx_reset_tail_preserves_post_workspace_callback_order() {
|
|
let mut context = ResetCCtxTailTestContext::default();
|
|
let mut block_state =
|
|
unsafe { MaybeUninit::<ZSTD_compressedBlockState_t>::zeroed().assume_init() };
|
|
let state = reset_cctx_tail_test_state(&mut context, &mut block_state);
|
|
|
|
assert_eq!(unsafe { ZSTD_rust_resetCCtxTail(&state) }, 0);
|
|
assert_eq!(
|
|
context.events,
|
|
["initialize", "reset-match-state", "reset-storage"]
|
|
);
|
|
assert_eq!(block_state.rep, [1, 4, 8]);
|
|
assert_eq!(block_state.entropy.huf.repeatMode, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn cctx_reset_tail_stops_before_storage_after_match_state_error() {
|
|
let mut context = ResetCCtxTailTestContext {
|
|
match_result: ERROR(ZstdErrorCode::StageWrong),
|
|
..ResetCCtxTailTestContext::default()
|
|
};
|
|
let mut block_state =
|
|
unsafe { MaybeUninit::<ZSTD_compressedBlockState_t>::zeroed().assume_init() };
|
|
let state = reset_cctx_tail_test_state(&mut context, &mut block_state);
|
|
|
|
assert_eq!(
|
|
unsafe { ZSTD_rust_resetCCtxTail(&state) },
|
|
ERROR(ZstdErrorCode::StageWrong)
|
|
);
|
|
assert_eq!(context.events, ["initialize", "reset-match-state"]);
|
|
}
|
|
|
|
#[test]
|
|
fn cctx_reset_tail_propagates_storage_error_after_match_state() {
|
|
let mut context = ResetCCtxTailTestContext {
|
|
storage_result: ERROR(ZstdErrorCode::MemoryAllocation),
|
|
..ResetCCtxTailTestContext::default()
|
|
};
|
|
let mut block_state =
|
|
unsafe { MaybeUninit::<ZSTD_compressedBlockState_t>::zeroed().assume_init() };
|
|
let state = reset_cctx_tail_test_state(&mut context, &mut block_state);
|
|
|
|
assert_eq!(
|
|
unsafe { ZSTD_rust_resetCCtxTail(&state) },
|
|
ERROR(ZstdErrorCode::MemoryAllocation)
|
|
);
|
|
assert_eq!(
|
|
context.events,
|
|
["initialize", "reset-match-state", "reset-storage"]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn estimate_cctx_workspace_size_keeps_static_and_buffer_components_separate() {
|
|
let sizing = cctx_workspace_test_sizing();
|
|
let ordinary = cctx_workspace_test_estimate(&sizing, 0, 0, 0, 0, 0, 0, 0, 0);
|
|
let static_context = cctx_workspace_test_estimate(&sizing, 0, 0, 0, 0, 1, 0, 0, 0);
|
|
let buffered = cctx_workspace_test_estimate(&sizing, 0, 0, 0, 0, 0, 17, 23, 0);
|
|
let external = cctx_workspace_test_estimate(&sizing, 0, 0, 0, 0, 0, 0, 0, 1);
|
|
|
|
assert_eq!(static_context - ordinary, sizing.cctxSize);
|
|
assert_eq!(buffered - ordinary, 17 + 23);
|
|
assert_eq!(external - ordinary, 6265);
|
|
}
|
|
|
|
#[test]
|
|
fn estimate_cctx_workspace_size_adds_ldm_table_and_sequence_space_only_when_enabled() {
|
|
let sizing = cctx_workspace_test_sizing();
|
|
let ordinary = cctx_workspace_test_estimate(&sizing, 0, 4, 2, 64, 0, 0, 0, 0);
|
|
let ldm = cctx_workspace_test_estimate(&sizing, ZSTD_RUST_PS_ENABLE, 4, 2, 64, 0, 0, 0, 0);
|
|
|
|
assert_eq!(ldm - ordinary, 324);
|
|
}
|
|
|
|
#[test]
|
|
fn max_estimate_cctx_size_handles_zero_values() {
|
|
assert_eq!(max_estimate_cctx_size(0, 0, 0, 0), 0);
|
|
assert_eq!(ZSTD_rust_maxEstimateCCtxSize(0, 0, 0, 0), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn max_estimate_cctx_size_selects_the_largest_ordinary_value() {
|
|
assert_eq!(max_estimate_cctx_size(17, 42, 9, 31), 42);
|
|
assert_eq!(ZSTD_rust_maxEstimateCCtxSize(17, 42, 9, 31), 42);
|
|
}
|
|
|
|
#[test]
|
|
fn max_estimate_cctx_size_preserves_equal_values() {
|
|
assert_eq!(max_estimate_cctx_size(42, 42, 42, 42), 42);
|
|
assert_eq!(ZSTD_rust_maxEstimateCCtxSize(42, 42, 42, 42), 42);
|
|
}
|
|
|
|
#[test]
|
|
fn max_estimate_cctx_size_accepts_size_max() {
|
|
assert_eq!(max_estimate_cctx_size(1, usize::MAX, 3, 2), usize::MAX);
|
|
assert_eq!(
|
|
ZSTD_rust_maxEstimateCCtxSize(1, usize::MAX, 3, 2),
|
|
usize::MAX
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn max_estimate_cctx_size_compares_error_like_raw_values() {
|
|
let smaller_error = ERROR(ZstdErrorCode::DstSizeTooSmall);
|
|
let larger_error = ERROR(ZstdErrorCode::ParameterUnsupported);
|
|
|
|
assert_eq!(
|
|
max_estimate_cctx_size(128, smaller_error, 256, larger_error),
|
|
larger_error
|
|
);
|
|
assert_eq!(
|
|
ZSTD_rust_maxEstimateCCtxSize(128, smaller_error, 256, larger_error),
|
|
larger_error
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn estimate_cctx_size_internal_preserves_tier_order_and_max_policy() {
|
|
let mut context = EstimateCCtxSizeTestContext {
|
|
compression_levels: Vec::new(),
|
|
source_size_hints: Vec::new(),
|
|
parameter_tiers: Vec::new(),
|
|
};
|
|
let state = ZSTD_rust_estimateCCtxSizeState {
|
|
callback_context: (&mut context as *mut EstimateCCtxSizeTestContext).cast(),
|
|
compression_level: 7,
|
|
get_c_params: estimate_cctx_size_test_get_c_params,
|
|
estimate: estimate_cctx_size_test_estimate,
|
|
};
|
|
|
|
assert_eq!(unsafe { ZSTD_rust_estimateCCtxSizeInternal(&state) }, 91);
|
|
assert_eq!(context.compression_levels, vec![7; 4]);
|
|
assert_eq!(
|
|
context.source_size_hints,
|
|
vec![16 * 1024, 128 * 1024, 256 * 1024, ZSTD_CONTENTSIZE_UNKNOWN]
|
|
);
|
|
assert_eq!(context.parameter_tiers, vec![1, 2, 3, 4]);
|
|
}
|
|
|
|
#[test]
|
|
fn estimate_cctx_size_internal_rejects_a_null_state() {
|
|
assert_eq!(
|
|
unsafe { ZSTD_rust_estimateCCtxSizeInternal(ptr::null()) },
|
|
ERROR(ZstdErrorCode::Generic)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn public_one_shot_abi_is_c_compatible() {
|
|
let entry: unsafe extern "C" fn(*mut c_void, usize, *const c_void, usize, c_int) -> usize =
|
|
ZSTD_compress;
|
|
let context_entry: unsafe extern "C" fn(
|
|
*mut c_void,
|
|
*mut c_void,
|
|
usize,
|
|
*const c_void,
|
|
usize,
|
|
c_int,
|
|
) -> usize = ZSTD_compressCCtx;
|
|
assert_eq!(size_of::<usize>(), size_of::<*const c_void>());
|
|
let _ = entry;
|
|
let _ = context_entry;
|
|
}
|
|
|
|
#[test]
|
|
fn explicit_context_simple_api_matches_one_shot_path() {
|
|
let input = b"explicit context compression remains a simple API";
|
|
let capacity = crate::zstd_compress_api::ZSTD_compressBound(input.len());
|
|
let mut output = vec![0u8; capacity];
|
|
let written = unsafe {
|
|
ZSTD_compressCCtx(
|
|
std::ptr::dangling_mut::<c_void>(),
|
|
output.as_mut_ptr().cast(),
|
|
output.len(),
|
|
input.as_ptr().cast(),
|
|
input.len(),
|
|
3,
|
|
)
|
|
};
|
|
assert!(!ERR_isError(written));
|
|
output.truncate(written);
|
|
let one_shot = compress_input(input, 3);
|
|
assert_eq!(output, one_shot);
|
|
if let Some(restored) = system_round_trip(&output) {
|
|
assert_eq!(restored, input);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn one_shot_round_trip_across_block_boundaries() {
|
|
let mut input = Vec::with_capacity(128 * 1024 + 37);
|
|
for index in 0..(128 * 1024 + 37) {
|
|
input.push(((index * 17) ^ (index / 31)) as u8);
|
|
}
|
|
|
|
let compressed = compress_input(&input, 3);
|
|
if let Some(restored) = system_round_trip(&compressed) {
|
|
assert_eq!(restored, input);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn empty_and_short_inputs_have_valid_frames() {
|
|
for input in [b"".as_slice(), b"a", b"abcdefg", b"abcdefgh"] {
|
|
let compressed = compress_input(input, 1);
|
|
if let Some(restored) = system_round_trip(&compressed) {
|
|
assert_eq!(restored, input);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn one_shot_promotes_nonfirst_rle_blocks() {
|
|
let mut input = vec![b'B'; 256 * 1024 - 2];
|
|
input.extend(std::iter::repeat_n(b'A', 100 * 1024));
|
|
|
|
let compressed = compress_input(&input, 1);
|
|
assert!(compressed.len() <= 46);
|
|
if let Some(restored) = system_round_trip(&compressed) {
|
|
assert_eq!(restored, input);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn public_error_paths_match_size_t_error_contract() {
|
|
let mut output = [0u8; 64];
|
|
let source = [1u8; 8];
|
|
assert_eq!(
|
|
unsafe {
|
|
ZSTD_compress(
|
|
output.as_mut_ptr().cast(),
|
|
0,
|
|
source.as_ptr().cast(),
|
|
source.len(),
|
|
3,
|
|
)
|
|
},
|
|
ERROR(ZstdErrorCode::DstSizeTooSmall)
|
|
);
|
|
assert_eq!(
|
|
unsafe { ZSTD_compress(ptr::null_mut(), 1, source.as_ptr().cast(), source.len(), 3) },
|
|
ERROR(ZstdErrorCode::DstBufferNull)
|
|
);
|
|
assert_eq!(
|
|
unsafe { ZSTD_compress(output.as_mut_ptr().cast(), output.len(), ptr::null(), 1, 3) },
|
|
ERROR(ZstdErrorCode::SrcSizeWrong)
|
|
);
|
|
}
|
|
}
|