From cea0a54ace33c338f6865ca5421a96f938007052 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Mon, 20 Jul 2026 19:04:55 +0200 Subject: [PATCH] refactor(compress): move stream buffer policy to Rust Project the public ZSTD_compressStream2 buffer positions into Rust and preserve the original output-first classification. C retains the dstSize_tooSmall and srcSize_wrong mappings, exact diagnostics, private context, and streaming state machine. Test Plan: - `ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings` -- passed - `ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo clippy --manifest-path rust/cli/Cargo.toml --all-targets -- -D warnings` -- passed - `ulimit -v 41943040; cargo +nightly fmt --manifest-path rust/Cargo.toml --all -- --check` -- passed - `ulimit -v 41943040; CARGO_BUILD_JOBS=1 make -j1` -- passed - `ulimit -v 41943040; CARGO_BUILD_JOBS=1 ./tests/rustLibSmoke` -- passed - `ulimit -v 41943040; CARGO_BUILD_JOBS=1 make -j1 -C tests test` -- passed, including large streaming, native, fuzzer, and zstream phases --- lib/compress/zstd_compress.c | 34 ++++++++++++- rust/src/zstd_compress.rs | 97 ++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 2 deletions(-) diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 0e485ee66..a858fb47a 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -769,6 +769,30 @@ size_t ZSTD_compressStream2_c(ZSTD_CCtx* cctx, ZSTD_outBuffer* output, ZSTD_inBuffer* input, ZSTD_EndDirective endOp); +typedef struct { + size_t outputPos; + size_t outputSize; + size_t inputPos; + size_t inputSize; +} ZSTD_rust_compressStream2BufferPolicyState; +enum { + ZSTD_RUST_COMPRESS_STREAM2_BUFFER_VALID = 0, + ZSTD_RUST_COMPRESS_STREAM2_BUFFER_OUTPUT_INVALID = 1, + ZSTD_RUST_COMPRESS_STREAM2_BUFFER_INPUT_INVALID = 2 +}; +int ZSTD_rust_compressStream2BufferPolicy( + const ZSTD_rust_compressStream2BufferPolicyState* state); +typedef char ZSTD_rust_compress_stream2_buffer_policy_state_layout[ + (offsetof(ZSTD_rust_compressStream2BufferPolicyState, outputPos) == 0 + && offsetof(ZSTD_rust_compressStream2BufferPolicyState, outputSize) + == sizeof(size_t) + && offsetof(ZSTD_rust_compressStream2BufferPolicyState, inputPos) + == 2 * sizeof(size_t) + && offsetof(ZSTD_rust_compressStream2BufferPolicyState, inputSize) + == 3 * sizeof(size_t) + && sizeof(ZSTD_rust_compressStream2BufferPolicyState) + == 4 * sizeof(size_t)) + ? 1 : -1]; typedef struct { int endOp; } ZSTD_rust_compressStream2PolicyState; @@ -8273,8 +8297,14 @@ size_t ZSTD_compressStream2_c( ZSTD_CCtx* cctx, { DEBUGLOG(5, "ZSTD_compressStream2, endOp=%u ", (unsigned)endOp); /* check conditions */ - RETURN_ERROR_IF(output->pos > output->size, dstSize_tooSmall, "invalid output buffer"); - RETURN_ERROR_IF(input->pos > input->size, srcSize_wrong, "invalid input buffer"); + { ZSTD_rust_compressStream2BufferPolicyState const state = { + output->pos, output->size, input->pos, input->size}; + int const bufferPolicy = ZSTD_rust_compressStream2BufferPolicy(&state); + RETURN_ERROR_IF(bufferPolicy == ZSTD_RUST_COMPRESS_STREAM2_BUFFER_OUTPUT_INVALID, + dstSize_tooSmall, "invalid output buffer"); + RETURN_ERROR_IF(bufferPolicy == ZSTD_RUST_COMPRESS_STREAM2_BUFFER_INPUT_INVALID, + srcSize_wrong, "invalid input buffer"); + } { ZSTD_rust_compressStream2PolicyState const state = {(int)endOp}; RETURN_ERROR_IF(!ZSTD_rust_compressStream2Policy(&state), parameter_outOfBound, "invalid endDirective"); diff --git a/rust/src/zstd_compress.rs b/rust/src/zstd_compress.rs index 0c3acab9d..ff951ab48 100644 --- a/rust/src/zstd_compress.rs +++ b/rust/src/zstd_compress.rs @@ -164,6 +164,61 @@ const ZSTD_E_END: c_int = 2; const ZSTD_E_CONTINUE: c_int = 0; const ZSTD_E_FLUSH: c_int = 1; +const ZSTD_RUST_COMPRESS_STREAM2_BUFFER_VALID: c_int = 0; +const ZSTD_RUST_COMPRESS_STREAM2_BUFFER_OUTPUT_INVALID: c_int = 1; +const ZSTD_RUST_COMPRESS_STREAM2_BUFFER_INPUT_INVALID: c_int = 2; + +/// Scalar projection for the public `ZSTD_compressStream2` buffer preconditions. +/// +/// C retains the public error mappings and diagnostics, while Rust owns the +/// output-first classification of the scalar position/size pairs. The C +/// context and streaming state machine remain outside this projection. +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct ZSTD_rust_compressStream2BufferPolicyState { + pub output_pos: usize, + pub output_size: usize, + pub input_pos: usize, + pub input_size: usize, +} + +const _: () = { + assert!(offset_of!(ZSTD_rust_compressStream2BufferPolicyState, output_pos) == 0); + assert!( + offset_of!(ZSTD_rust_compressStream2BufferPolicyState, output_size) == size_of::() + ); + assert!( + offset_of!(ZSTD_rust_compressStream2BufferPolicyState, input_pos) == 2 * size_of::() + ); + assert!( + offset_of!(ZSTD_rust_compressStream2BufferPolicyState, input_size) + == 3 * size_of::() + ); + assert!(size_of::() == 4 * size_of::()); +}; + +#[inline] +fn compress_stream2_buffer_policy(state: &ZSTD_rust_compressStream2BufferPolicyState) -> c_int { + if state.output_pos > state.output_size { + return ZSTD_RUST_COMPRESS_STREAM2_BUFFER_OUTPUT_INVALID; + } + if state.input_pos > state.input_size { + return ZSTD_RUST_COMPRESS_STREAM2_BUFFER_INPUT_INVALID; + } + ZSTD_RUST_COMPRESS_STREAM2_BUFFER_VALID +} + +/// Classify stream buffer positions before C enters its private state machine. +#[no_mangle] +pub unsafe extern "C" fn ZSTD_rust_compressStream2BufferPolicy( + state: *const ZSTD_rust_compressStream2BufferPolicyState, +) -> c_int { + let Some(state) = (unsafe { state.as_ref() }) else { + return ZSTD_RUST_COMPRESS_STREAM2_BUFFER_OUTPUT_INVALID; + }; + compress_stream2_buffer_policy(state) +} + /// Scalar projection for the public `ZSTD_compressStream2` directive policy. /// /// The C fallback retains the private context and streaming implementation; @@ -14071,6 +14126,48 @@ mod tests { assert_eq!(context.calls, 0); } + #[test] + fn compress_stream2_buffer_policy_accepts_valid_positions() { + let state = ZSTD_rust_compressStream2BufferPolicyState { + output_pos: 3, + output_size: 3, + input_pos: 7, + input_size: 7, + }; + assert_eq!( + compress_stream2_buffer_policy(&state), + ZSTD_RUST_COMPRESS_STREAM2_BUFFER_VALID + ); + } + + #[test] + fn compress_stream2_buffer_policy_rejects_invalid_output_positions() { + let state = ZSTD_rust_compressStream2BufferPolicyState { + output_pos: 4, + output_size: 3, + input_pos: 7, + input_size: 7, + }; + assert_eq!( + compress_stream2_buffer_policy(&state), + ZSTD_RUST_COMPRESS_STREAM2_BUFFER_OUTPUT_INVALID + ); + } + + #[test] + fn compress_stream2_buffer_policy_rejects_invalid_input_positions() { + let state = ZSTD_rust_compressStream2BufferPolicyState { + output_pos: 3, + output_size: 3, + input_pos: 8, + input_size: 7, + }; + assert_eq!( + compress_stream2_buffer_policy(&state), + ZSTD_RUST_COMPRESS_STREAM2_BUFFER_INPUT_INVALID + ); + } + #[test] fn compress_stream2_policy_accepts_all_public_directives() { for end_op in [ZSTD_E_CONTINUE, ZSTD_E_FLUSH, ZSTD_E_END] {