#![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::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_writeFrameHeader, ZSTD_writeLastEmptyBlock, }; use crate::zstd_compress_params::{ ZSTD_rust_params_adjustCParams, ZSTD_rust_params_maxNbSeq, ZSTD_rust_params_selectCParams, ZSTD_RUST_CPM_NO_ATTACH_DICT, ZSTD_RUST_PS_DISABLE, }; use crate::zstd_compress_sequences::SeqDef; use crate::zstd_compress_stats::{SeqStore_t, ZSTD_compressedBlockState_t}; use crate::zstd_compress_superblock::ZSTD_rust_compressSuperBlock; use std::ffi::c_void; use std::mem::{size_of, MaybeUninit}; use std::os::raw::c_int; use std::ptr; #[cfg(not(test))] unsafe extern "C" { 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) -> c_int; fn ZSTD_rust_simpleCompressStream2Level(cctx: *const c_void) -> 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_BLOCKSIZE_MAX: usize = 1 << 17; const ZSTD_CONTENTSIZE_UNKNOWN: u64 = u64::MAX; const ZSTD_ROWSIZE: usize = 16; const ZSTD_WINDOW_START_INDEX: u32 = 2; const ZSTD_DUBT_UNSORTED_MARK: u32 = 1; const ZSTD_INDEXOVERFLOW_MARGIN: usize = 16usize << 20; const ZSTD_CURRENT_MAX: usize = if size_of::() == 8 { 3500usize << 20 } else { 2000usize << 20 }; const ZSTD_CHUNKSIZE_MAX: usize = u32::MAX as usize - ZSTD_CURRENT_MAX; #[cfg(not(test))] const ZSTD_E_END: c_int = 2; #[cfg(not(test))] #[repr(C)] pub struct ZSTD_inBuffer { src: *const c_void, size: usize, pos: usize, } #[cfg(not(test))] #[repr(C)] pub struct ZSTD_outBuffer { dst: *mut c_void, size: usize, pos: usize, } /* 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 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 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 wrappers select the ordinary or btlazy2 policy by passing a clear /// zero/one `preserve_mark` value; `ZSTD_reduceIndex` remains C-owned. #[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); } #[inline] fn zeroed_state() -> ZSTD_compressedBlockState_t { /* The state contains only integer arrays and enum fields. */ unsafe { MaybeUninit::::zeroed().assume_init() } } #[inline] fn checked_table_size(log: u32) -> Option { 1usize.checked_shl(log) } #[inline] fn ceil_log2(size: usize) -> u32 { if size <= 1 { 0 } else { usize::BITS - (size - 1).leading_zeros() } } /// Compress one frame using the already migrated block leaves. /// /// This path deliberately starts a fresh match table for each 128 KiB block. /// That keeps the Rust-owned context independent from the still-private C /// `ZSTD_MatchState_t` window while producing ordinary zstd blocks that any /// decoder can consume. 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::().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); /* A table larger than the block cannot contain a useful index for this * independent-block path. This also keeps one-shot small inputs from * allocating the table selected for a huge source-size hint. */ let matcher_hash_log = cparams.hashLog.min(ceil_log2(first_block_size).max(6)); 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::()]; 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::(); let output = dst.cast::(); 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) }; 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(), block_src, 0, 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(), block_src, 0, 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); } } next_block.rep = reps; let last_block = u32::from(input_offset + block_size == src_size); let remaining_capacity = dst_capacity.saturating_sub(output_offset); let mut written = unsafe { ZSTD_rust_compressSuperBlock( (seq_store as *mut SeqStore_t).cast(), (&prev_block as *const ZSTD_compressedBlockState_t).cast(), (&mut next_block as *mut ZSTD_compressedBlockState_t).cast(), cparams.strategy, disable_literal_compression, workspace.as_mut_ptr().cast(), TMP_WORKSPACE_SIZE, 0, /* BMI2 is optional; portable Rust leaf path */ cparams.windowLog, 0, /* no target compressed block size */ output.add(output_offset).cast(), remaining_capacity, block_src.cast(), block_size, last_block, ) }; if ERR_isError(written) { return written; } if written == 0 { written = unsafe { write_raw_block( output.add(output_offset), remaining_capacity, block_src, block_size, last_block, ) }; if ERR_isError(written) { return written; } } if written > remaining_capacity { return ERROR(ZstdErrorCode::DstSizeTooSmall); } output_offset += written; input_offset += block_size; std::mem::swap(&mut prev_block, &mut next_block); hash_table.fill(0); chain_table.fill(0); } 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 { 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; the frame compressor itself is entirely /// Rust-owned and does not inspect the context. #[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 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()) }; 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); } if end_op == ZSTD_E_END { let level = unsafe { ZSTD_rust_simpleCompressStream2Level(cctx.cast_const()) }; if level != c_int::MIN { let src_size = input_ref.size - input_ref.pos; 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::().add(output_ref.pos).cast() } }; let src = if input_ref.src.is_null() { ptr::null() } else { unsafe { input_ref.src.cast::().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 std::io::Write; use std::process::{Command, Stdio}; fn system_round_trip(compressed: &[u8]) -> Option> { 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 { 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 } #[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 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 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 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::(), 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::(), 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 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) ); } }