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:
@@ -37,6 +37,10 @@ size_t ZSTD_rust_writeFrameHeader(void* dst, size_t dstCapacity,
|
|||||||
int contentSizeFlag, int format,
|
int contentSizeFlag, int format,
|
||||||
U32 windowLog, U64 pledgedSrcSize,
|
U32 windowLog, U64 pledgedSrcSize,
|
||||||
U32 dictID);
|
U32 dictID);
|
||||||
|
size_t ZSTD_rust_writeEpilogue(void* dst, size_t dstCapacity, int* stage,
|
||||||
|
int noDictIDFlag, int checksumFlag,
|
||||||
|
int contentSizeFlag, int format, U32 windowLog,
|
||||||
|
U32 checksum);
|
||||||
size_t ZSTD_rust_resetCCtxForSimpleCompression(void* cctx);
|
size_t ZSTD_rust_resetCCtxForSimpleCompression(void* cctx);
|
||||||
size_t ZSTD_rust_prepareCCtxForSimpleCompression(void* cctx,
|
size_t ZSTD_rust_prepareCCtxForSimpleCompression(void* cctx,
|
||||||
size_t srcSize,
|
size_t srcSize,
|
||||||
@@ -3938,41 +3942,20 @@ size_t ZSTD_compressBegin(ZSTD_CCtx* cctx, int compressionLevel)
|
|||||||
* @return : nb of bytes written into dst (or an error code) */
|
* @return : nb of bytes written into dst (or an error code) */
|
||||||
static size_t ZSTD_writeEpilogue(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity)
|
static size_t ZSTD_writeEpilogue(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity)
|
||||||
{
|
{
|
||||||
BYTE* const ostart = (BYTE*)dst;
|
U32 const checksum = cctx->appliedParams.fParams.checksumFlag
|
||||||
BYTE* op = ostart;
|
? (U32)XXH64_digest(&cctx->xxhState)
|
||||||
|
: 0;
|
||||||
DEBUGLOG(4, "ZSTD_writeEpilogue");
|
DEBUGLOG(4, "ZSTD_writeEpilogue");
|
||||||
RETURN_ERROR_IF(cctx->stage == ZSTDcs_created, stage_wrong, "init missing");
|
if (cctx->appliedParams.fParams.checksumFlag)
|
||||||
|
|
||||||
/* special case : empty frame */
|
|
||||||
if (cctx->stage == ZSTDcs_init) {
|
|
||||||
size_t fhSize = ZSTD_writeFrameHeader(dst, dstCapacity, &cctx->appliedParams, 0, 0);
|
|
||||||
FORWARD_IF_ERROR(fhSize, "ZSTD_writeFrameHeader failed");
|
|
||||||
dstCapacity -= fhSize;
|
|
||||||
op += fhSize;
|
|
||||||
cctx->stage = ZSTDcs_ongoing;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cctx->stage != ZSTDcs_ending) {
|
|
||||||
/* write one last empty block, make it the "last" block */
|
|
||||||
U32 const cBlockHeader24 = 1 /* last block */ + (((U32)bt_raw)<<1) + 0;
|
|
||||||
ZSTD_STATIC_ASSERT(ZSTD_BLOCKHEADERSIZE == 3);
|
|
||||||
RETURN_ERROR_IF(dstCapacity<3, dstSize_tooSmall, "no room for epilogue");
|
|
||||||
MEM_writeLE24(op, cBlockHeader24);
|
|
||||||
op += ZSTD_blockHeaderSize;
|
|
||||||
dstCapacity -= ZSTD_blockHeaderSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cctx->appliedParams.fParams.checksumFlag) {
|
|
||||||
U32 const checksum = (U32) XXH64_digest(&cctx->xxhState);
|
|
||||||
RETURN_ERROR_IF(dstCapacity<4, dstSize_tooSmall, "no room for checksum");
|
|
||||||
DEBUGLOG(4, "ZSTD_writeEpilogue: write checksum : %08X", (unsigned)checksum);
|
DEBUGLOG(4, "ZSTD_writeEpilogue: write checksum : %08X", (unsigned)checksum);
|
||||||
MEM_writeLE32(op, checksum);
|
return ZSTD_rust_writeEpilogue(
|
||||||
op += 4;
|
dst, dstCapacity, (int*)&cctx->stage,
|
||||||
}
|
cctx->appliedParams.fParams.noDictIDFlag,
|
||||||
|
cctx->appliedParams.fParams.checksumFlag,
|
||||||
cctx->stage = ZSTDcs_created; /* return to "created but no init" status */
|
cctx->appliedParams.fParams.contentSizeFlag,
|
||||||
return (size_t)(op-ostart);
|
(int)cctx->appliedParams.format,
|
||||||
|
cctx->appliedParams.cParams.windowLog,
|
||||||
|
checksum);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ZSTD_CCtx_trace(ZSTD_CCtx* cctx, size_t extraCSize)
|
void ZSTD_CCtx_trace(ZSTD_CCtx* cctx, size_t extraCSize)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
//! five scalar frame parameters they need, so no `ZSTD_CCtx_params` layout
|
//! five scalar frame parameters they need, so no `ZSTD_CCtx_params` layout
|
||||||
//! crosses the language boundary.
|
//! crosses the language boundary.
|
||||||
|
|
||||||
use crate::errors::{ZstdErrorCode, ERROR};
|
use crate::errors::{ERR_isError, ZstdErrorCode, ERROR};
|
||||||
use crate::zstd_presplit::ZSTD_splitBlock;
|
use crate::zstd_presplit::ZSTD_splitBlock;
|
||||||
use std::ffi::c_void;
|
use std::ffi::c_void;
|
||||||
use std::os::raw::{c_int, c_longlong, c_uint};
|
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_FAST: c_int = 1;
|
||||||
const ZSTD_BTULTRA2: c_int = 9;
|
const ZSTD_BTULTRA2: c_int = 9;
|
||||||
const SPLIT_LEVELS: [c_int; 10] = [0, 0, 1, 2, 2, 3, 3, 4, 4, 4];
|
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]
|
#[inline]
|
||||||
unsafe fn write_le16(dst: *mut u8, value: u16) {
|
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) }
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -530,4 +610,96 @@ mod tests {
|
|||||||
assert!(result > 0);
|
assert!(result > 0);
|
||||||
assert!(result <= ZSTD_BLOCK_SIZE);
|
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