Move public ZSTD_copyCCtx pledge normalization and frame-parameter policy into Rust while retaining the private workspace and match-table copy implementation behind a C callback. Test Plan: - cargo test --manifest-path rust/Cargo.toml --lib - cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings - make -B -C programs -j1 zstd - make -C tests -j1 test-zstream ZSTREAM_TESTTIME=-T1s - focused copy_cctx unit tests
12036 lines
404 KiB
Rust
12036 lines
404 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; the public context lifecycle
|
|
//! remains C-owned because it shares the configuration-dependent private
|
|
//! `ZSTD_CCtx_s` layout. `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};
|
|
#[cfg(not(test))]
|
|
use crate::zstd_compress_api::ZSTD_compressBound;
|
|
use crate::zstd_compress_frame::{
|
|
write_raw_block, ZSTD_rust_noCompressBlock, ZSTD_rust_rleCompressBlock,
|
|
ZSTD_rust_writeBlockHeader, ZSTD_rust_writeFrameHeader, ZSTD_writeLastEmptyBlock,
|
|
};
|
|
use crate::zstd_compress_literals::min_gain;
|
|
use crate::zstd_compress_params::{
|
|
ZSTD_compressionParameters, ZSTD_frameParameters, ZSTD_parameters,
|
|
ZSTD_rust_params_adjustCParams, ZSTD_rust_params_maxNbSeq, ZSTD_rust_params_selectCParams,
|
|
ZSTD_RUST_CPM_NO_ATTACH_DICT, ZSTD_RUST_PS_AUTO, ZSTD_RUST_PS_DISABLE,
|
|
};
|
|
use crate::zstd_compress_params_api::{
|
|
ZSTD_CCtxParams_setParameter, ZSTD_CCtx_params, 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_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_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_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;
|
|
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 FrameChunkPrepareFn = unsafe extern "C" fn(*mut c_void, *const c_void, usize);
|
|
type FrameChunkCompressFn =
|
|
unsafe extern "C" fn(*mut c_void, *mut c_void, usize, *const c_void, usize, c_uint) -> usize;
|
|
type FrameChunkChecksumFn = unsafe extern "C" fn(*mut c_void, *const c_void, 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 BuildSeqStoreSelectFn = unsafe extern "C" fn(
|
|
*mut c_void,
|
|
*mut SeqStore_t,
|
|
*mut u32,
|
|
*const c_void,
|
|
usize,
|
|
*mut c_int,
|
|
) -> usize;
|
|
|
|
/// 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,
|
|
select_sequences: BuildSeqStoreSelectFn,
|
|
}
|
|
|
|
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, select_sequences)
|
|
== 6 * size_of::<usize>() + size_of::<c_uint>() + size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
size_of::<ZSTD_rust_buildSeqStoreState>()
|
|
== 7 * size_of::<usize>() + size_of::<c_uint>() + 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;
|
|
}
|
|
}
|
|
|
|
/// 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 c_void,
|
|
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_block: FrameChunkPrepareFn,
|
|
compress_target: FrameChunkCompressFn,
|
|
compress_split: FrameChunkCompressFn,
|
|
compress_internal: FrameChunkCompressFn,
|
|
update_checksum: FrameChunkChecksumFn,
|
|
}
|
|
|
|
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_block)
|
|
== 7 * size_of::<usize>() + size_of::<c_longlong>() + 6 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
size_of::<ZSTD_rust_frameChunkState>()
|
|
== 12 * size_of::<usize>() + size_of::<c_longlong>() + 6 * size_of::<c_int>()
|
|
);
|
|
};
|
|
|
|
/// Rust implementation of `ZSTD_compress_frameChunk`.
|
|
///
|
|
/// C still prepares match-state windows and invokes the selected block body
|
|
/// through callbacks. Rust owns the block-size heuristic call, output
|
|
/// framing, savings accounting, checksum sequencing, and frame-state update.
|
|
#[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() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
if state.checksum_flag != 0 && src_size != 0 {
|
|
if state.checksum_state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
unsafe { (state.update_checksum)(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 { (state.prepare_block)(state.callback_context, ip.cast(), block_size) };
|
|
|
|
let c_size = if state.use_target_c_block_size != 0 {
|
|
unsafe {
|
|
(state.compress_target)(
|
|
state.callback_context,
|
|
op.cast(),
|
|
remaining_capacity,
|
|
ip.cast(),
|
|
block_size,
|
|
last_block,
|
|
)
|
|
}
|
|
} else if state.block_splitter_enabled != 0 {
|
|
unsafe {
|
|
(state.compress_split)(
|
|
state.callback_context,
|
|
op.cast(),
|
|
remaining_capacity,
|
|
ip.cast(),
|
|
block_size,
|
|
last_block,
|
|
)
|
|
}
|
|
} else {
|
|
let compressed_size = unsafe {
|
|
(state.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 CompressContinueHeaderFn = unsafe extern "C" fn(*mut c_void, *mut c_void, usize) -> usize;
|
|
type CompressContinueWindowFn = unsafe extern "C" fn(*mut c_void, *const c_void, usize);
|
|
type CompressContinueBlockFn =
|
|
unsafe extern "C" fn(*mut c_void, *mut c_void, usize, *const c_void, usize, c_uint) -> 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
|
|
/// opaque callback context remains in C, where callbacks update the private
|
|
/// match-state windows and invoke the C-owned context-sensitive operations.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_compressContinueState {
|
|
callback_context: *mut c_void,
|
|
write_frame_header: CompressContinueHeaderFn,
|
|
update_window: CompressContinueWindowFn,
|
|
correct_overflow: CompressContinueWindowFn,
|
|
compress_frame_chunk: CompressContinueBlockFn,
|
|
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,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_compressContinueState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_compressContinueState, write_frame_header) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_compressContinueState, update_window) == 2 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressContinueState, correct_overflow) == 3 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressContinueState, compress_frame_chunk) == 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)
|
|
== 9 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressContinueState, block_size_max)
|
|
== 9 * size_of::<usize>() + size_of::<u64>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressContinueState, check_block_size)
|
|
== 9 * size_of::<usize>() + size_of::<u64>() + size_of::<usize>()
|
|
);
|
|
assert!(
|
|
size_of::<ZSTD_rust_compressContinueState>()
|
|
== if size_of::<usize>() == 8 { 96 } else { 52 }
|
|
);
|
|
};
|
|
|
|
const ZSTD_COMPRESSION_STAGE_CREATED: c_int = 0;
|
|
const ZSTD_COMPRESSION_STAGE_INIT: c_int = 1;
|
|
const ZSTD_COMPRESSION_STAGE_ONGOING: c_int = 2;
|
|
|
|
#[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 { (state.write_frame_header)(state.callback_context, dst, dst_capacity) };
|
|
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;
|
|
}
|
|
|
|
unsafe { (state.update_window)(state.callback_context, src, src_size) };
|
|
if frame == 0 {
|
|
unsafe { (state.correct_overflow)(state.callback_context, src, src_size) };
|
|
}
|
|
|
|
let compressed_size = if frame != 0 {
|
|
unsafe {
|
|
(state.compress_frame_chunk)(
|
|
state.callback_context,
|
|
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 CompressEndContinueFn = unsafe extern "C" fn(
|
|
*mut c_void,
|
|
*mut c_void,
|
|
usize,
|
|
*const c_void,
|
|
usize,
|
|
c_uint,
|
|
c_uint,
|
|
) -> usize;
|
|
type CompressEndEpilogueFn = unsafe extern "C" fn(*mut c_void, *mut c_void, usize) -> usize;
|
|
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 opaque callback context retains the private
|
|
/// CCtx-dependent continue, epilogue, checksum, and trace operations in C.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_compressEndState {
|
|
callback_context: *mut c_void,
|
|
compress_continue: CompressEndContinueFn,
|
|
write_epilogue: CompressEndEpilogueFn,
|
|
trace: CompressEndTraceFn,
|
|
consumed_src_size: *const u64,
|
|
pledged_src_size_plus_one: u64,
|
|
content_size_flag: c_int,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_compressEndState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_compressEndState, compress_continue) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_compressEndState, write_epilogue) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_compressEndState, trace) == 3 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_compressEndState, consumed_src_size) == 4 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressEndState, pledged_src_size_plus_one) == 5 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressEndState, content_size_flag)
|
|
== 5 * size_of::<usize>() + size_of::<u64>()
|
|
);
|
|
assert!(
|
|
size_of::<ZSTD_rust_compressEndState>() == if size_of::<usize>() == 8 { 56 } else { 32 }
|
|
);
|
|
};
|
|
|
|
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.consumed_src_size.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
let c_size = unsafe {
|
|
(state.compress_continue)(
|
|
state.callback_context,
|
|
dst,
|
|
dst_capacity,
|
|
src,
|
|
src_size,
|
|
1,
|
|
1,
|
|
)
|
|
};
|
|
if ERR_isError(c_size) {
|
|
return c_size;
|
|
}
|
|
|
|
let end_result = unsafe {
|
|
(state.write_epilogue)(
|
|
state.callback_context,
|
|
dst.cast::<u8>().add(c_size).cast(),
|
|
dst_capacity.wrapping_sub(c_size),
|
|
)
|
|
};
|
|
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 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 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 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 CompressStreamBlockFn =
|
|
unsafe extern "C" fn(*mut c_void, *mut c_void, usize, *const c_void, usize) -> usize;
|
|
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. The opaque callback context stays in C; callbacks
|
|
/// retain access to `ZSTD_CCtx`, frame construction, and 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: CompressStreamBlockFn,
|
|
compress_end: CompressStreamBlockFn,
|
|
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)
|
|
== 12 * size_of::<usize>() + 2 * size_of::<c_int>() + 2 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressStreamState, compress_end)
|
|
== 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 CompressStreamInitLocalDictFn = unsafe extern "C" fn(*mut c_void) -> usize;
|
|
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);
|
|
|
|
/// 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,
|
|
init_local_dict: CompressStreamInitLocalDictFn,
|
|
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 { 256 } else { 128 }
|
|
);
|
|
};
|
|
|
|
#[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 { (state.init_local_dict)(state.callback_context) };
|
|
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()
|
|
{
|
|
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 {
|
|
(state.compress_end)(
|
|
state.callback_context,
|
|
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 {
|
|
(state.compress_end)(
|
|
state.callback_context,
|
|
output_dst,
|
|
output_size,
|
|
source,
|
|
input_size,
|
|
)
|
|
} else {
|
|
(state.compress_continue)(
|
|
state.callback_context,
|
|
output_dst,
|
|
output_size,
|
|
source,
|
|
input_size,
|
|
)
|
|
}
|
|
};
|
|
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;
|
|
type SequenceApiWriteFrameHeaderFn =
|
|
unsafe extern "C" fn(*mut c_void, *mut c_void, usize, usize) -> usize;
|
|
type SequenceApiUpdateChecksumFn = unsafe extern "C" fn(*mut c_void, *const c_void, usize);
|
|
type SequenceApiDigestChecksumFn = unsafe extern "C" fn(*mut c_void) -> c_uint;
|
|
type SequenceApiWriteChecksumFn = unsafe extern "C" fn(*mut c_void, *mut c_void, c_uint);
|
|
|
|
/// Explicit projection for the public sequence-compression API orchestration.
|
|
///
|
|
/// Rust owns validation ordering, frame-header/checksum sequencing, and
|
|
/// output accounting. C retains the private CCtx, sequence-store, block,
|
|
/// and checksum layouts through the two block-state projections and callbacks.
|
|
#[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,
|
|
write_frame_header: SequenceApiWriteFrameHeaderFn,
|
|
update_checksum: SequenceApiUpdateChecksumFn,
|
|
digest_checksum: SequenceApiDigestChecksumFn,
|
|
write_checksum: SequenceApiWriteChecksumFn,
|
|
checksum_flag: c_int,
|
|
block_delimiters: c_int,
|
|
validate_sequences: c_int,
|
|
}
|
|
|
|
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, write_frame_header) == 4 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_sequenceApiState, update_checksum) == 5 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_sequenceApiState, digest_checksum) == 6 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_sequenceApiState, write_checksum) == 7 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_sequenceApiState, checksum_flag) == size_of::<[usize; 8]>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_sequenceApiState, block_delimiters)
|
|
== size_of::<[usize; 8]>() + size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_sequenceApiState, validate_sequences)
|
|
== size_of::<[usize; 8]>() + 2 * size_of::<c_int>()
|
|
);
|
|
assert!(
|
|
size_of::<ZSTD_rust_sequenceApiState>() == if size_of::<usize>() == 8 { 80 } else { 44 }
|
|
);
|
|
};
|
|
|
|
#[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 and the no-compress fallback remain in C.
|
|
/// 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 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
|
|
|| state.select_sequences 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 {
|
|
(state.select_sequences)(
|
|
state.callback_context,
|
|
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 `ZSTD_deriveBlockSplits()` and the private `ZSTD_CCtx`; Rust
|
|
/// receives only the sequence-store views and block-emission state needed to
|
|
/// reproduce the original loop.
|
|
#[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 {
|
|
src: *const c_void,
|
|
size: usize,
|
|
pos: usize,
|
|
}
|
|
|
|
#[repr(C)]
|
|
pub struct ZSTD_outBuffer {
|
|
dst: *mut c_void,
|
|
size: usize,
|
|
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]
|
|
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)
|
|
}
|
|
|
|
#[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
|
|
}
|
|
|
|
#[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,
|
|
)
|
|
}
|
|
|
|
#[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)
|
|
}
|
|
|
|
#[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_rust_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_rust_dictTooBig(loaded_dict_size: usize) -> c_int {
|
|
dict_too_big(loaded_dict_size) as c_int
|
|
}
|
|
|
|
#[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,
|
|
)
|
|
}
|
|
|
|
#[inline]
|
|
fn max_estimate_cctx_size(
|
|
estimate0: usize,
|
|
estimate1: usize,
|
|
estimate2: usize,
|
|
estimate3: usize,
|
|
) -> usize {
|
|
estimate0.max(estimate1).max(estimate2).max(estimate3)
|
|
}
|
|
|
|
/// 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);
|
|
}
|
|
}
|
|
|
|
/// 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;
|
|
}
|
|
}
|
|
|
|
#[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.write_frame_header as usize == 0
|
|
|| state.update_checksum as usize == 0
|
|
|| state.digest_checksum as usize == 0
|
|
|| state.write_checksum as usize == 0
|
|
{
|
|
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 {
|
|
(state.write_frame_header)(
|
|
state.callback_context,
|
|
(*op).cast(),
|
|
*dst_capacity,
|
|
pledged_src_size,
|
|
)
|
|
};
|
|
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 { (state.digest_checksum)(state.callback_context) };
|
|
if dst_capacity < 4 {
|
|
return ERROR(ZstdErrorCode::DstSizeTooSmall);
|
|
}
|
|
unsafe {
|
|
(state.write_checksum)(state.callback_context, 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
|
|
/// callbacks for the private CCtx/header/checksum operations. 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 { (state.update_checksum)(state.callback_context, 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
|
|
}
|
|
|
|
/// 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;
|
|
|
|
#[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,
|
|
epilogue_result: usize,
|
|
epilogue_offset: usize,
|
|
epilogue_capacity: usize,
|
|
trace_extra: usize,
|
|
continue_frame: c_uint,
|
|
continue_last_frame_chunk: c_uint,
|
|
dst_base: usize,
|
|
}
|
|
|
|
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_continue(
|
|
context: *mut c_void,
|
|
_dst: *mut c_void,
|
|
_dst_capacity: usize,
|
|
_src: *const c_void,
|
|
_src_size: usize,
|
|
frame: c_uint,
|
|
last_frame_chunk: c_uint,
|
|
) -> usize {
|
|
let context = unsafe { compress_end_test_context(context) };
|
|
context.events.push("continue");
|
|
context.continue_frame = frame;
|
|
context.continue_last_frame_chunk = last_frame_chunk;
|
|
context.continue_result
|
|
}
|
|
|
|
unsafe extern "C" fn compress_end_test_epilogue(
|
|
context: *mut c_void,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
) -> usize {
|
|
let context = unsafe { compress_end_test_context(context) };
|
|
context.events.push("epilogue");
|
|
context.epilogue_offset = (dst as usize).wrapping_sub(context.dst_base);
|
|
context.epilogue_capacity = dst_capacity;
|
|
context.epilogue_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: &u64,
|
|
pledged_src_size_plus_one: u64,
|
|
content_size_flag: c_int,
|
|
) -> ZSTD_rust_compressEndState {
|
|
ZSTD_rust_compressEndState {
|
|
callback_context: (context as *mut CompressEndTestContext).cast(),
|
|
compress_continue: compress_end_test_continue,
|
|
write_epilogue: compress_end_test_epilogue,
|
|
trace: compress_end_test_trace,
|
|
consumed_src_size: consumed_src_size as *const u64,
|
|
pledged_src_size_plus_one,
|
|
content_size_flag,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn compress_end_preserves_callback_order_and_output_accounting() {
|
|
let mut dst = [0u8; 16];
|
|
let mut context = CompressEndTestContext {
|
|
continue_result: 3,
|
|
epilogue_result: 5,
|
|
dst_base: dst.as_mut_ptr() as usize,
|
|
..CompressEndTestContext::default()
|
|
};
|
|
let consumed_src_size = 7;
|
|
let state = compress_end_test_state(&mut context, &consumed_src_size, 8, 1);
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_compressEnd(&state, dst.as_mut_ptr().cast(), dst.len(), ptr::null(), 0)
|
|
};
|
|
|
|
assert_eq!(result, 8);
|
|
assert_eq!(context.events, ["continue", "epilogue", "trace"]);
|
|
assert_eq!(
|
|
(context.continue_frame, context.continue_last_frame_chunk),
|
|
(1, 1)
|
|
);
|
|
assert_eq!(
|
|
(context.epilogue_offset, context.epilogue_capacity),
|
|
(3, 13)
|
|
);
|
|
assert_eq!(context.trace_extra, 5);
|
|
}
|
|
|
|
#[test]
|
|
fn compress_end_stops_before_epilogue_when_continue_fails() {
|
|
let mut dst = [0u8; 16];
|
|
let mut context = CompressEndTestContext {
|
|
continue_result: ERROR(ZstdErrorCode::MemoryAllocation),
|
|
dst_base: dst.as_mut_ptr() as usize,
|
|
..CompressEndTestContext::default()
|
|
};
|
|
let consumed_src_size = 7;
|
|
let state = compress_end_test_state(&mut context, &consumed_src_size, 8, 0);
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_compressEnd(&state, dst.as_mut_ptr().cast(), dst.len(), ptr::null(), 0)
|
|
};
|
|
|
|
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; 16];
|
|
let mut context = CompressEndTestContext {
|
|
continue_result: 3,
|
|
epilogue_result: ERROR(ZstdErrorCode::MemoryAllocation),
|
|
dst_base: dst.as_mut_ptr() as usize,
|
|
..CompressEndTestContext::default()
|
|
};
|
|
let consumed_src_size = 7;
|
|
let state = compress_end_test_state(&mut context, &consumed_src_size, 99, 1);
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_compressEnd(&state, dst.as_mut_ptr().cast(), dst.len(), ptr::null(), 0)
|
|
};
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::MemoryAllocation));
|
|
assert_eq!(context.events, ["continue", "epilogue"]);
|
|
}
|
|
|
|
#[test]
|
|
fn compress_end_rejects_pledged_size_before_trace() {
|
|
let mut dst = [0u8; 16];
|
|
let mut context = CompressEndTestContext {
|
|
continue_result: 3,
|
|
epilogue_result: 5,
|
|
dst_base: dst.as_mut_ptr() as usize,
|
|
..CompressEndTestContext::default()
|
|
};
|
|
let consumed_src_size = 7;
|
|
let state = compress_end_test_state(&mut context, &consumed_src_size, 99, 1);
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_compressEnd(&state, dst.as_mut_ptr().cast(), dst.len(), ptr::null(), 0)
|
|
};
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::SrcSizeWrong));
|
|
assert_eq!(context.events, ["continue", "epilogue"]);
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct CompressStreamInitTestContext {
|
|
events: Vec<&'static str>,
|
|
local_result: usize,
|
|
create_result: usize,
|
|
mt_result: usize,
|
|
begin_result: usize,
|
|
pledged: u64,
|
|
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_local_dict(context: *mut c_void) -> usize {
|
|
let context = unsafe { compress_stream_init_test_context(context) };
|
|
context.events.push("local-dict");
|
|
context.local_result
|
|
}
|
|
|
|
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,
|
|
init_local_dict: compress_stream_init_test_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_stops_before_later_callbacks_on_error() {
|
|
let mut context = CompressStreamInitTestContext {
|
|
local_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"]);
|
|
}
|
|
|
|
#[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],
|
|
target_calls: usize,
|
|
split_calls: usize,
|
|
internal_calls: usize,
|
|
last_blocks: [c_uint; 4],
|
|
checksum_calls: usize,
|
|
checksum_size: usize,
|
|
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 extern "C" fn frame_chunk_test_prepare(
|
|
context: *mut c_void,
|
|
_src: *const c_void,
|
|
block_size: usize,
|
|
) {
|
|
let context = unsafe { frame_chunk_test_context(context) };
|
|
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_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
|
|
}
|
|
|
|
unsafe extern "C" fn frame_chunk_test_checksum(
|
|
context: *mut c_void,
|
|
_src: *const c_void,
|
|
src_size: usize,
|
|
) {
|
|
let context = unsafe { frame_chunk_test_context(context) };
|
|
context.checksum_calls += 1;
|
|
context.checksum_size = context.checksum_size.wrapping_add(src_size);
|
|
}
|
|
|
|
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 = context as *mut FrameChunkTestContext as *mut c_void;
|
|
ZSTD_rust_frameChunkState {
|
|
callback_context: context,
|
|
tmp_workspace: ptr::null_mut(),
|
|
checksum_state: context,
|
|
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_block: frame_chunk_test_prepare,
|
|
compress_target: frame_chunk_test_target,
|
|
compress_split: frame_chunk_test_split,
|
|
compress_internal: frame_chunk_test_internal,
|
|
update_checksum: frame_chunk_test_checksum,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn frame_chunk_internal_path_emits_blocks_and_updates_state_once() {
|
|
let mut context = FrameChunkTestContext {
|
|
internal_result: 2,
|
|
..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.internal_calls, 2);
|
|
assert_eq!(context.last_blocks[..2], [0, 1]);
|
|
assert_eq!(context.checksum_calls, 1);
|
|
assert_eq!(context.checksum_size, source.len());
|
|
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_calls, 1);
|
|
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 {
|
|
header_calls: usize,
|
|
update_window_calls: usize,
|
|
overflow_calls: usize,
|
|
frame_calls: usize,
|
|
block_calls: usize,
|
|
last_frame_chunk: c_uint,
|
|
header_result: usize,
|
|
frame_result: usize,
|
|
block_result: usize,
|
|
}
|
|
|
|
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_header(
|
|
context: *mut c_void,
|
|
_dst: *mut c_void,
|
|
_dst_capacity: usize,
|
|
) -> usize {
|
|
let context = unsafe { compress_continue_test_context(context) };
|
|
context.header_calls += 1;
|
|
context.header_result
|
|
}
|
|
|
|
unsafe extern "C" fn compress_continue_test_window(
|
|
context: *mut c_void,
|
|
_src: *const c_void,
|
|
_src_size: usize,
|
|
) {
|
|
let context = unsafe { compress_continue_test_context(context) };
|
|
context.update_window_calls += 1;
|
|
}
|
|
|
|
unsafe extern "C" fn compress_continue_test_overflow(
|
|
context: *mut c_void,
|
|
_src: *const c_void,
|
|
_src_size: usize,
|
|
) {
|
|
let context = unsafe { compress_continue_test_context(context) };
|
|
context.overflow_calls += 1;
|
|
}
|
|
|
|
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 {
|
|
ZSTD_rust_compressContinueState {
|
|
callback_context: (context as *mut CompressContinueTestContext).cast(),
|
|
write_frame_header: compress_continue_test_header,
|
|
update_window: compress_continue_test_window,
|
|
correct_overflow: compress_continue_test_overflow,
|
|
compress_frame_chunk: compress_continue_test_frame,
|
|
compress_block: compress_continue_test_block,
|
|
stage,
|
|
consumed_src_size,
|
|
produced_c_size,
|
|
pledged_src_size_plus_one,
|
|
block_size_max,
|
|
check_block_size,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn compress_continue_starts_frame_and_updates_progression() {
|
|
let mut context = CompressContinueTestContext {
|
|
header_result: 4,
|
|
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, 10);
|
|
assert_eq!(stage, ZSTD_COMPRESSION_STAGE_ONGOING);
|
|
assert_eq!(consumed, 12);
|
|
assert_eq!(produced, 21);
|
|
assert_eq!(context.header_calls, 1);
|
|
assert_eq!(context.update_window_calls, 1);
|
|
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);
|
|
}
|
|
|
|
#[test]
|
|
fn compress_continue_empty_frame_only_writes_header() {
|
|
let mut context = CompressContinueTestContext {
|
|
header_result: 3,
|
|
..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; 8];
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_compressContinue(
|
|
&state,
|
|
output.as_mut_ptr().cast(),
|
|
output.len(),
|
|
ptr::null(),
|
|
0,
|
|
1,
|
|
0,
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, 3);
|
|
assert_eq!(stage, ZSTD_COMPRESSION_STAGE_ONGOING);
|
|
assert_eq!((consumed, produced), (7, 11));
|
|
assert_eq!(context.header_calls, 1);
|
|
assert_eq!(context.update_window_calls, 0);
|
|
assert_eq!(context.frame_calls, 0);
|
|
assert_eq!(output, [0xa5; 8]);
|
|
}
|
|
|
|
#[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 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.update_window_calls, 0);
|
|
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.update_window_calls, 0);
|
|
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.update_window_calls, 1);
|
|
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.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,
|
|
}
|
|
|
|
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,
|
|
) -> 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,
|
|
) -> 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
|
|
}
|
|
|
|
#[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 {
|
|
ZSTD_rust_compressStreamState {
|
|
callback_context: (context as *mut CompressStreamTestContext).cast(),
|
|
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: compress_stream_test_block,
|
|
compress_end: compress_stream_test_end,
|
|
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
|
|
);
|
|
}
|
|
|
|
#[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 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
|
|
);
|
|
}
|
|
|
|
#[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_rust_indexTooCloseToMax(threshold), 0);
|
|
assert_eq!(ZSTD_rust_indexTooCloseToMax(threshold + 1), 1);
|
|
}
|
|
|
|
#[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 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_rust_dictTooBig(ZSTD_CHUNKSIZE_MAX), 0);
|
|
assert_eq!(ZSTD_rust_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);
|
|
}
|
|
|
|
#[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
|
|
);
|
|
}
|
|
|
|
#[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 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);
|
|
}
|
|
|
|
#[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 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 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
|
|
);
|
|
}
|
|
|
|
#[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 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)
|
|
);
|
|
}
|
|
}
|