feat(compress): move frame epilogue serialization to Rust
Port the scalar frame-finalization policy and byte serialization into the Rust compression leaf while keeping checksum finalization, tracing, and the compression context in C. Preserve the init-to-ongoing transition on partial failure and reset the stage only after a successful epilogue. Test Plan: - cargo test --manifest-path rust/Cargo.toml --no-default-features --features compression - cargo clippy --manifest-path rust/Cargo.toml - cargo clippy --manifest-path rust/Cargo.toml --benches - cargo clippy --manifest-path rust/Cargo.toml --tests - make -B -C lib -j2 lib - make -B -C tests -j2 test-zstream (84 deterministic, 6737 and 9012 fuzzer cases)
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
//! five scalar frame parameters they need, so no `ZSTD_CCtx_params` layout
|
||||
//! crosses the language boundary.
|
||||
|
||||
use crate::errors::{ZstdErrorCode, ERROR};
|
||||
use crate::errors::{ERR_isError, ZstdErrorCode, ERROR};
|
||||
use crate::zstd_presplit::ZSTD_splitBlock;
|
||||
use std::ffi::c_void;
|
||||
use std::os::raw::{c_int, c_longlong, c_uint};
|
||||
@@ -26,6 +26,10 @@ const ZSTD_BLOCK_SIZE: usize = 128 << 10;
|
||||
const ZSTD_FAST: c_int = 1;
|
||||
const ZSTD_BTULTRA2: c_int = 9;
|
||||
const SPLIT_LEVELS: [c_int; 10] = [0, 0, 1, 2, 2, 3, 3, 4, 4, 4];
|
||||
const ZSTDCS_CREATED: c_int = 0;
|
||||
const ZSTDCS_INIT: c_int = 1;
|
||||
const ZSTDCS_ONGOING: c_int = 2;
|
||||
const ZSTDCS_ENDING: c_int = 3;
|
||||
|
||||
#[inline]
|
||||
unsafe fn write_le16(dst: *mut u8, value: u16) {
|
||||
@@ -250,6 +254,82 @@ pub unsafe extern "C" fn ZSTD_rust_optimalBlockSize(
|
||||
unsafe { ZSTD_splitBlock(src, block_size_max, split_level, workspace, workspace_size) }
|
||||
}
|
||||
|
||||
/// Rust implementation of the private `ZSTD_writeEpilogue()` serializer.
|
||||
///
|
||||
/// The C caller owns the compression context and passes its stage by scalar
|
||||
/// pointer so the transition to `ongoing` after an empty-frame header remains
|
||||
/// visible even when a later write fails. The checksum is already finalized
|
||||
/// by C; Rust only serializes its low 32 bits.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ZSTD_rust_writeEpilogue(
|
||||
dst: *mut c_void,
|
||||
dst_capacity: usize,
|
||||
stage: *mut c_int,
|
||||
no_dict_id_flag: c_int,
|
||||
checksum_flag: c_int,
|
||||
content_size_flag: c_int,
|
||||
format: c_int,
|
||||
window_log: c_uint,
|
||||
checksum: u32,
|
||||
) -> usize {
|
||||
debug_assert!(!stage.is_null());
|
||||
if stage.is_null() {
|
||||
return ERROR(ZstdErrorCode::StageWrong);
|
||||
}
|
||||
|
||||
let mut stage_value = unsafe { stage.read() };
|
||||
if stage_value == ZSTDCS_CREATED {
|
||||
return ERROR(ZstdErrorCode::StageWrong);
|
||||
}
|
||||
|
||||
let start = dst.cast::<u8>();
|
||||
let mut output = start;
|
||||
let mut capacity = dst_capacity;
|
||||
|
||||
if stage_value == ZSTDCS_INIT {
|
||||
let header_size = unsafe {
|
||||
ZSTD_rust_writeFrameHeader(
|
||||
output.cast(),
|
||||
capacity,
|
||||
no_dict_id_flag,
|
||||
checksum_flag,
|
||||
content_size_flag,
|
||||
format,
|
||||
window_log,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
};
|
||||
if ERR_isError(header_size) {
|
||||
return header_size;
|
||||
}
|
||||
output = unsafe { output.add(header_size) };
|
||||
capacity -= header_size;
|
||||
stage_value = ZSTDCS_ONGOING;
|
||||
unsafe { stage.write(stage_value) };
|
||||
}
|
||||
|
||||
if stage_value != ZSTDCS_ENDING {
|
||||
let block_size = unsafe { ZSTD_writeLastEmptyBlock(output.cast(), capacity) };
|
||||
if ERR_isError(block_size) {
|
||||
return block_size;
|
||||
}
|
||||
output = unsafe { output.add(block_size) };
|
||||
capacity -= block_size;
|
||||
}
|
||||
|
||||
if checksum_flag != 0 {
|
||||
if capacity < 4 {
|
||||
return ERROR(ZstdErrorCode::DstSizeTooSmall);
|
||||
}
|
||||
unsafe { write_le32(output, checksum) };
|
||||
output = unsafe { output.add(4) };
|
||||
}
|
||||
|
||||
unsafe { stage.write(ZSTDCS_CREATED) };
|
||||
output as usize - start as usize
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -530,4 +610,96 @@ mod tests {
|
||||
assert!(result > 0);
|
||||
assert!(result <= ZSTD_BLOCK_SIZE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn epilogue_rejects_created_stage() {
|
||||
let mut output = [0u8; ZSTD_FRAMEHEADERSIZE_MAX];
|
||||
let mut stage = ZSTDCS_CREATED;
|
||||
let result = unsafe {
|
||||
ZSTD_rust_writeEpilogue(
|
||||
output.as_mut_ptr().cast(),
|
||||
output.len(),
|
||||
&mut stage,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
ZSTD_F_ZSTD1,
|
||||
20,
|
||||
0,
|
||||
)
|
||||
};
|
||||
assert_eq!(ERR_getErrorCode(result), ZstdErrorCode::StageWrong as i32);
|
||||
assert_eq!(stage, ZSTDCS_CREATED);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn epilogue_writes_empty_frame_and_resets_stage() {
|
||||
let mut output = [0u8; 18];
|
||||
let mut stage = ZSTDCS_INIT;
|
||||
let result = unsafe {
|
||||
ZSTD_rust_writeEpilogue(
|
||||
output.as_mut_ptr().cast(),
|
||||
output.len(),
|
||||
&mut stage,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
ZSTD_F_ZSTD1,
|
||||
20,
|
||||
0,
|
||||
)
|
||||
};
|
||||
assert_eq!(result, 9);
|
||||
assert_eq!(
|
||||
&output[..result],
|
||||
&[0x28, 0xB5, 0x2F, 0xFD, 0x20, 0x00, 1, 0, 0]
|
||||
);
|
||||
assert_eq!(stage, ZSTDCS_CREATED);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn epilogue_ending_stage_writes_only_checksum() {
|
||||
let mut output = [0u8; 4];
|
||||
let mut stage = ZSTDCS_ENDING;
|
||||
let result = unsafe {
|
||||
ZSTD_rust_writeEpilogue(
|
||||
output.as_mut_ptr().cast(),
|
||||
output.len(),
|
||||
&mut stage,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
ZSTD_F_ZSTD1,
|
||||
10,
|
||||
0x1234_5678,
|
||||
)
|
||||
};
|
||||
assert_eq!(result, 4);
|
||||
assert_eq!(output, [0x78, 0x56, 0x34, 0x12]);
|
||||
assert_eq!(stage, ZSTDCS_CREATED);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn epilogue_preserves_ongoing_stage_when_checksum_does_not_fit() {
|
||||
let mut output = [0u8; 6];
|
||||
let mut stage = ZSTDCS_ONGOING;
|
||||
let result = unsafe {
|
||||
ZSTD_rust_writeEpilogue(
|
||||
output.as_mut_ptr().cast(),
|
||||
output.len(),
|
||||
&mut stage,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
ZSTD_F_ZSTD1,
|
||||
10,
|
||||
0x1234_5678,
|
||||
)
|
||||
};
|
||||
assert_eq!(
|
||||
ERR_getErrorCode(result),
|
||||
ZstdErrorCode::DstSizeTooSmall as i32
|
||||
);
|
||||
assert_eq!(stage, ZSTDCS_ONGOING);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user