From 0fbc9ff37bd15f4972a994c45da6d730cd7aefde Mon Sep 17 00:00:00 2001 From: ddidderr Date: Sun, 19 Jul 2026 15:46:54 +0200 Subject: [PATCH] feat(compress): move usingCDict wrapper policy into Rust Move the public ZSTD_compress_usingCDict frame-policy construction and begin-then-end sequencing behind a Rust-owned boundary. Keep the existing C CDict parameter selection, private context initialization, and end-of-frame operations behind narrow callbacks, while preserving the dictionary error short-circuit and hardcoded frame flags. Test Plan: - ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo test --manifest-path rust/Cargo.toml --release compress_using_cdict -- --nocapture - ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo test --manifest-path rust/Cargo.toml --release - ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo clippy --manifest-path rust/Cargo.toml --release --all-targets -- -D warnings - ulimit -v 41943040; make -B -C programs -j1 zstd - ulimit -v 41943040; make -C tests -j1 test-zstream ZSTREAM_TESTTIME=-T1s --- lib/compress/zstd_compress.c | 52 +++++++- rust/src/zstd_compress_dictionary.rs | 170 +++++++++++++++++++++++++++ 2 files changed, 220 insertions(+), 2 deletions(-) diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index b99ef37a6..860bc2888 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -1341,6 +1341,32 @@ typedef char ZSTD_rust_compress_begin_using_cdict_state_layout[ && sizeof(ZSTD_rust_compressBeginUsingCDictState) == 12 * sizeof(void*)) ? 1 : -1]; +typedef size_t (*ZSTD_rust_compressUsingCDictBegin_f)( + void* context, const void* cdict, + const ZSTD_frameParameters* fParams, U64 pledgedSrcSize); +typedef size_t (*ZSTD_rust_compressUsingCDictEnd_f)( + void* context, void* dst, size_t dstCapacity, + const void* src, size_t srcSize); +typedef struct { + void* callbackContext; + const void* cdict; + ZSTD_rust_compressUsingCDictBegin_f begin; + ZSTD_rust_compressUsingCDictEnd_f end; +} ZSTD_rust_compressUsingCDictState; +size_t ZSTD_rust_compressUsingCDict( + const ZSTD_rust_compressUsingCDictState* state, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize); +typedef char ZSTD_rust_compress_using_cdict_state_layout[ + (offsetof(ZSTD_rust_compressUsingCDictState, callbackContext) == 0 + && offsetof(ZSTD_rust_compressUsingCDictState, cdict) + == sizeof(void*) + && offsetof(ZSTD_rust_compressUsingCDictState, begin) + == 2 * sizeof(void*) + && offsetof(ZSTD_rust_compressUsingCDictState, end) + == 3 * sizeof(void*) + && sizeof(ZSTD_rust_compressUsingCDictState) == 4 * sizeof(void*)) + ? 1 : -1]; typedef struct { const ZSTD_compressionParameters* cParams; const unsigned* dictID; @@ -5507,6 +5533,23 @@ static size_t ZSTD_compress_usingCDict_internal(ZSTD_CCtx* cctx, return ZSTD_compressEnd_public(cctx, dst, dstCapacity, src, srcSize); } +static size_t ZSTD_rust_compressUsingCDict_begin( + void* context, const void* cdict, + const ZSTD_frameParameters* fParams, U64 pledgedSrcSize) +{ + return ZSTD_compressBegin_usingCDict_internal( + (ZSTD_CCtx*)context, (const ZSTD_CDict*)cdict, + *fParams, pledgedSrcSize); +} + +static size_t ZSTD_rust_compressUsingCDict_end( + void* context, void* dst, size_t dstCapacity, + const void* src, size_t srcSize) +{ + return ZSTD_compressEnd_public( + (ZSTD_CCtx*)context, dst, dstCapacity, src, srcSize); +} + /*! ZSTD_compress_usingCDict_advanced(): * This function is DEPRECATED. */ @@ -5528,8 +5571,13 @@ size_t ZSTD_compress_usingCDict(ZSTD_CCtx* cctx, const void* src, size_t srcSize, const ZSTD_CDict* cdict) { - ZSTD_frameParameters const fParams = { 1 /*content*/, 0 /*checksum*/, 0 /*noDictID*/ }; - return ZSTD_compress_usingCDict_internal(cctx, dst, dstCapacity, src, srcSize, cdict, fParams); + ZSTD_rust_compressUsingCDictState state; + state.callbackContext = cctx; + state.cdict = cdict; + state.begin = ZSTD_rust_compressUsingCDict_begin; + state.end = ZSTD_rust_compressUsingCDict_end; + return ZSTD_rust_compressUsingCDict( + &state, dst, dstCapacity, src, srcSize); } diff --git a/rust/src/zstd_compress_dictionary.rs b/rust/src/zstd_compress_dictionary.rs index ac6f45490..e27867ada 100644 --- a/rust/src/zstd_compress_dictionary.rs +++ b/rust/src/zstd_compress_dictionary.rs @@ -232,6 +232,71 @@ pub unsafe extern "C" fn ZSTD_rust_compressBeginUsingCDict( } } +type CompressUsingCDictBeginFn = + unsafe extern "C" fn(*mut c_void, *const c_void, *const ZSTD_frameParameters, u64) -> usize; +type CompressUsingCDictEndFn = + unsafe extern "C" fn(*mut c_void, *mut c_void, usize, *const c_void, usize) -> usize; + +/// Explicit projection for the public `ZSTD_compress_usingCDict` wrapper. +/// +/// Rust owns the hardcoded frame policy and begin-then-end ordering. C +/// retains the private CDict begin path and end-of-frame implementation behind +/// callbacks. +#[repr(C)] +pub struct ZSTD_rust_compressUsingCDictState { + callback_context: *mut c_void, + cdict: *const c_void, + begin: CompressUsingCDictBeginFn, + end: CompressUsingCDictEndFn, +} + +const _: () = { + assert!(offset_of!(ZSTD_rust_compressUsingCDictState, callback_context) == 0); + assert!(offset_of!(ZSTD_rust_compressUsingCDictState, cdict) == size_of::()); + assert!(offset_of!(ZSTD_rust_compressUsingCDictState, begin) == 2 * size_of::()); + assert!(offset_of!(ZSTD_rust_compressUsingCDictState, end) == 3 * size_of::()); + assert!(size_of::() == size_of::<[usize; 4]>()); +}; + +/// Start a CDict frame and finish it through the existing C-owned leaves. +#[no_mangle] +pub unsafe extern "C" fn ZSTD_rust_compressUsingCDict( + state: *const ZSTD_rust_compressUsingCDictState, + dst: *mut c_void, + dst_capacity: usize, + src: *const c_void, + src_size: usize, +) -> usize { + if state.is_null() { + return ERROR(ZstdErrorCode::Generic); + } + let state = unsafe { &*state }; + if state.callback_context.is_null() { + return ERROR(ZstdErrorCode::Generic); + } + if state.cdict.is_null() { + return ERROR(ZstdErrorCode::DictionaryWrong); + } + + let f_params = ZSTD_frameParameters { + contentSizeFlag: 1, + checksumFlag: 0, + noDictIDFlag: 0, + }; + let begin_result = unsafe { + (state.begin)( + state.callback_context, + state.cdict, + &f_params, + src_size as u64, + ) + }; + if ERR_isError(begin_result) { + return begin_result; + } + unsafe { (state.end)(state.callback_context, dst, dst_capacity, src, src_size) } +} + type InitCDictAssignContentFn = unsafe extern "C" fn(*mut c_void, *const c_void, usize, c_int) -> usize; type InitCDictReserveEntropyFn = unsafe extern "C" fn(*mut c_void) -> *mut c_void; @@ -2312,6 +2377,111 @@ mod tests { assert_eq!(probe.min_window_logs, [19]); } + #[derive(Default)] + struct CompressUsingCDictProbe { + events: Vec<&'static str>, + cdict: *const c_void, + frame_params: ZSTD_frameParameters, + pledged_src_size: u64, + dst: *mut c_void, + dst_capacity: usize, + src: *const c_void, + src_size: usize, + begin_result: usize, + end_result: usize, + } + + unsafe extern "C" fn compress_using_cdict_test_begin( + context: *mut c_void, + cdict: *const c_void, + frame_params: *const ZSTD_frameParameters, + pledged_src_size: u64, + ) -> usize { + let probe = unsafe { &mut *context.cast::() }; + probe.events.push("begin"); + probe.cdict = cdict; + probe.frame_params = unsafe { *frame_params }; + probe.pledged_src_size = pledged_src_size; + probe.begin_result + } + + unsafe extern "C" fn compress_using_cdict_test_end( + context: *mut c_void, + dst: *mut c_void, + dst_capacity: usize, + src: *const c_void, + src_size: usize, + ) -> usize { + let probe = unsafe { &mut *context.cast::() }; + probe.events.push("end"); + probe.dst = dst; + probe.dst_capacity = dst_capacity; + probe.src = src; + probe.src_size = src_size; + probe.end_result + } + + fn compress_using_cdict_test_state( + probe: &mut CompressUsingCDictProbe, + cdict: *const c_void, + ) -> ZSTD_rust_compressUsingCDictState { + ZSTD_rust_compressUsingCDictState { + callback_context: (probe as *mut CompressUsingCDictProbe).cast(), + cdict, + begin: compress_using_cdict_test_begin, + end: compress_using_cdict_test_end, + } + } + + #[test] + fn compress_using_cdict_preserves_frame_policy_and_begin_end_order() { + let mut probe = CompressUsingCDictProbe { + end_result: 37, + ..Default::default() + }; + let cdict = 0x4000usize as *const c_void; + let state = compress_using_cdict_test_state(&mut probe, cdict); + let mut dst = [0u8; 8]; + let src = [1u8, 2, 3]; + + let result = unsafe { + ZSTD_rust_compressUsingCDict( + &state, + dst.as_mut_ptr().cast(), + dst.len(), + src.as_ptr().cast(), + src.len(), + ) + }; + + assert_eq!(result, probe.end_result); + assert_eq!(probe.events, ["begin", "end"]); + assert_eq!(probe.cdict, cdict); + assert_eq!(probe.frame_params.contentSizeFlag, 1); + assert_eq!(probe.frame_params.checksumFlag, 0); + assert_eq!(probe.frame_params.noDictIDFlag, 0); + assert_eq!(probe.pledged_src_size, src.len() as u64); + assert_eq!(probe.dst, dst.as_mut_ptr().cast()); + assert_eq!(probe.dst_capacity, dst.len()); + assert_eq!(probe.src, src.as_ptr().cast()); + assert_eq!(probe.src_size, src.len()); + } + + #[test] + fn compress_using_cdict_stops_before_end_after_begin_error() { + let mut probe = CompressUsingCDictProbe { + begin_result: ERROR(ZstdErrorCode::MemoryAllocation), + ..Default::default() + }; + let state = compress_using_cdict_test_state(&mut probe, ptr::dangling()); + + let result = + unsafe { ZSTD_rust_compressUsingCDict(&state, ptr::null_mut(), 0, ptr::null(), 0) }; + + assert_eq!(result, probe.begin_result); + assert_eq!(probe.events, ["begin"]); + } + fn assert_dictionary_corrupted(result: usize) { assert!(ERR_isError(result)); assert_eq!(