The fileio zstd projection used to route its stream callback through a C implementation that constructed the public input and output buffer views, queried pending output, called the streaming codec, and copied four scalar results back to the Rust-owned loop. That left the central codec operation in the CLI C translation unit even though the surrounding stream loop was already in Rust. Move that callback into Rust while keeping the C CCtx opaque across the boundary. Rust now builds the public ZSTD_inBuffer and ZSTD_outBuffer views, invokes ZSTD_toFlushNow and ZSTD_compressStream2, publishes the original positions and result, and preserves the existing success diagnostic through a small C display callback. The C projection passes the real CCtx as codecOpaque and retains the adaptive iteration context and all private CLI state. The buffer structs are made public within the Rust crate so this seam can reuse the existing C-compatible definitions without duplicating them. Test Plan: - `cargo +nightly fmt --manifest-path rust/Cargo.toml --all` -- passed - `cc -fsyntax-only -Werror=incompatible-pointer-types -Ilib -Ilib/common -Ilib/compress -Ilib/decompress -Ilib/dict -Ilib/legacy programs/fileio.c` -- passed - `git diff --check` and `git diff --cached --check` -- passed - Full capped native and Rust verification remains to be run after this seam
20127 lines
693 KiB
Rust
20127 lines
693 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 InitStaticCCtxFn = unsafe extern "C" fn(*mut c_void) -> *mut c_void;
|
|
const ZSTD_STATIC_WORKSPACE_ALIGNMENT: usize = 8;
|
|
|
|
/// Explicit projection for the public `ZSTD_initStaticCCtx` wrapper.
|
|
///
|
|
/// Rust owns workspace pointer/alignment validation, minimum-size validation,
|
|
/// and callback ordering. C retains the private static-workspace layout and
|
|
/// context initialization behind one callback.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_initStaticCCtxState {
|
|
callback_context: *mut c_void,
|
|
workspace: *mut c_void,
|
|
workspace_size: usize,
|
|
cctx_size: usize,
|
|
init: InitStaticCCtxFn,
|
|
}
|
|
|
|
const _: () = {
|
|
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, init) == 4 * size_of::<usize>());
|
|
assert!(size_of::<ZSTD_rust_initStaticCCtxState>() == size_of::<[usize; 5]>());
|
|
};
|
|
|
|
/// Validate static CCtx workspace ownership before entering the C leaf.
|
|
#[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.init)(state.callback_context) }
|
|
}
|
|
|
|
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>,
|
|
result: *mut c_void,
|
|
}
|
|
|
|
unsafe extern "C" fn init_static_cctx_test_init(context: *mut c_void) -> *mut c_void {
|
|
let context = unsafe { &mut *context.cast::<InitStaticCCtxTestContext>() };
|
|
context.events.push("init");
|
|
context.result
|
|
}
|
|
|
|
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,
|
|
init: init_static_cctx_test_init,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn init_static_cctx_rejects_an_unaligned_workspace_before_callback() {
|
|
let mut context = InitStaticCCtxTestContext {
|
|
result: ptr::dangling_mut(),
|
|
..Default::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 {
|
|
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!(context.events.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn init_static_cctx_calls_the_initializer_after_workspace_validation() {
|
|
let mut context = InitStaticCCtxTestContext {
|
|
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.result);
|
|
assert_eq!(context.events, ["init"]);
|
|
}
|
|
|
|
#[test]
|
|
fn init_static_cctx_rejects_a_null_workspace() {
|
|
let mut context = InitStaticCCtxTestContext {
|
|
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());
|
|
}
|
|
|
|
#[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)
|
|
);
|
|
}
|
|
}
|