feat(compress): move continue orchestration into Rust
The public and deprecated block APIs previously entered a C-owned ZSTD_compressContinue_internal routine, which mixed frame-header sequencing, window maintenance, block dispatch, and frame-size accounting with the private CCtx match state. That made the high-level compression boundary both harder to test and easy to diverge from the already-ported frame-chunk body. Add an explicit C/Rust projection containing only scalar state and callback slots. Rust now owns stage validation and transitions, header sequencing, frame-versus-block dispatch, and consumed/produced progression, while C callbacks retain access to the private match state and context-sensitive block operations. The same path now covers compressContinue, deprecated block compression, and compressEnd without exposing the unstable CCtx layout. Test Plan: - `cargo test --manifest-path rust/Cargo.toml --all-targets -- --test-threads=1` -- 473 passed. - `cargo test --manifest-path rust/Cargo.toml --no-default-features --features compression,decompression,dict-builder,legacy-v01,legacy-v02,legacy-v03,legacy-v04,legacy-v05,legacy-v06,legacy-v07 --all-targets -- --test-threads=1` -- 528 passed. - Native `test-fuzzer`, `test-zstream`, `test-decodecorpus`, `test-cli-tests`, and `test-zstd` -- passed. - `cargo clippy` default, benches, and tests for library and CLI -- passed.
This commit is contained in:
+144
-72
@@ -147,6 +147,59 @@ typedef char ZSTD_rust_frame_chunk_state_layout[
|
|||||||
&& sizeof(ZSTD_rust_frameChunkState)
|
&& sizeof(ZSTD_rust_frameChunkState)
|
||||||
== 12 * sizeof(void*) + sizeof(S64) + 6 * sizeof(int))
|
== 12 * sizeof(void*) + sizeof(S64) + 6 * sizeof(int))
|
||||||
? 1 : -1];
|
? 1 : -1];
|
||||||
|
|
||||||
|
/* The high-level continue/block entry points are Rust-owned. This projection
|
||||||
|
* carries only mutable scalar state and C callbacks; the private CCtx and
|
||||||
|
* match-state layout never crosses the ABI. */
|
||||||
|
typedef size_t (*ZSTD_rust_compressContinueHeader_f)(void* context,
|
||||||
|
void* dst,
|
||||||
|
size_t dstCapacity);
|
||||||
|
typedef void (*ZSTD_rust_compressContinueWindow_f)(void* context,
|
||||||
|
const void* src,
|
||||||
|
size_t srcSize);
|
||||||
|
typedef size_t (*ZSTD_rust_compressContinueBlock_f)(void* context,
|
||||||
|
void* dst,
|
||||||
|
size_t dstCapacity,
|
||||||
|
const void* src,
|
||||||
|
size_t srcSize,
|
||||||
|
U32 lastFrameChunk);
|
||||||
|
typedef struct {
|
||||||
|
void* callbackContext;
|
||||||
|
ZSTD_rust_compressContinueHeader_f writeFrameHeader;
|
||||||
|
ZSTD_rust_compressContinueWindow_f updateWindow;
|
||||||
|
ZSTD_rust_compressContinueWindow_f correctOverflow;
|
||||||
|
ZSTD_rust_compressContinueBlock_f compressFrameChunk;
|
||||||
|
ZSTD_rust_compressContinueBlock_f compressBlock;
|
||||||
|
ZSTD_compressionStage_e* stage;
|
||||||
|
unsigned long long* consumedSrcSize;
|
||||||
|
unsigned long long* producedCSize;
|
||||||
|
U64 pledgedSrcSizePlusOne;
|
||||||
|
size_t blockSizeMax;
|
||||||
|
int checkBlockSize;
|
||||||
|
} ZSTD_rust_compressContinueState;
|
||||||
|
size_t ZSTD_rust_compressContinue(
|
||||||
|
const ZSTD_rust_compressContinueState* state,
|
||||||
|
void* dst, size_t dstCapacity,
|
||||||
|
const void* src, size_t srcSize,
|
||||||
|
U32 frame, U32 lastFrameChunk);
|
||||||
|
typedef char ZSTD_rust_compress_continue_state_layout[
|
||||||
|
(offsetof(ZSTD_rust_compressContinueState, callbackContext) == 0
|
||||||
|
&& offsetof(ZSTD_rust_compressContinueState, writeFrameHeader) == sizeof(void*)
|
||||||
|
&& offsetof(ZSTD_rust_compressContinueState, updateWindow) == 2 * sizeof(void*)
|
||||||
|
&& offsetof(ZSTD_rust_compressContinueState, correctOverflow) == 3 * sizeof(void*)
|
||||||
|
&& offsetof(ZSTD_rust_compressContinueState, compressFrameChunk) == 4 * sizeof(void*)
|
||||||
|
&& offsetof(ZSTD_rust_compressContinueState, compressBlock) == 5 * sizeof(void*)
|
||||||
|
&& offsetof(ZSTD_rust_compressContinueState, stage) == 6 * sizeof(void*)
|
||||||
|
&& offsetof(ZSTD_rust_compressContinueState, consumedSrcSize) == 7 * sizeof(void*)
|
||||||
|
&& offsetof(ZSTD_rust_compressContinueState, producedCSize) == 8 * sizeof(void*)
|
||||||
|
&& offsetof(ZSTD_rust_compressContinueState, pledgedSrcSizePlusOne) == 9 * sizeof(void*)
|
||||||
|
&& offsetof(ZSTD_rust_compressContinueState, blockSizeMax)
|
||||||
|
== 9 * sizeof(void*) + sizeof(U64)
|
||||||
|
&& offsetof(ZSTD_rust_compressContinueState, checkBlockSize)
|
||||||
|
== 9 * sizeof(void*) + sizeof(U64) + sizeof(size_t)
|
||||||
|
&& sizeof(ZSTD_rust_compressContinueState)
|
||||||
|
== (sizeof(void*) == 8 ? 96 : 52))
|
||||||
|
? 1 : -1];
|
||||||
/* The target-sized block body only needs this narrow projection of ZSTD_CCtx.
|
/* The target-sized block body only needs this narrow projection of ZSTD_CCtx.
|
||||||
* Matchfinder/window state, sequence-store construction, and outer repeat-mode
|
* Matchfinder/window state, sequence-store construction, and outer repeat-mode
|
||||||
* cleanup remain in C. */
|
* cleanup remain in C. */
|
||||||
@@ -3069,6 +3122,84 @@ static size_t ZSTD_writeFrameHeader(void* dst, size_t dstCapacity,
|
|||||||
pledgedSrcSize, dictID);
|
pledgedSrcSize, dictID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static size_t ZSTD_rust_compressContinue_writeFrameHeader(
|
||||||
|
void* context, void* dst, size_t dstCapacity)
|
||||||
|
{
|
||||||
|
ZSTD_CCtx* const cctx = (ZSTD_CCtx*)context;
|
||||||
|
return ZSTD_writeFrameHeader(
|
||||||
|
dst, dstCapacity, &cctx->appliedParams,
|
||||||
|
cctx->pledgedSrcSizePlusOne - 1, cctx->dictID);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ZSTD_rust_compressContinue_updateWindow(
|
||||||
|
void* context, const void* src, size_t srcSize)
|
||||||
|
{
|
||||||
|
ZSTD_CCtx* const cctx = (ZSTD_CCtx*)context;
|
||||||
|
ZSTD_MatchState_t* const ms = &cctx->blockState.matchState;
|
||||||
|
|
||||||
|
if (!ZSTD_window_update(&ms->window, src, srcSize, ms->forceNonContiguous)) {
|
||||||
|
ms->forceNonContiguous = 0;
|
||||||
|
ms->nextToUpdate = ms->window.dictLimit;
|
||||||
|
}
|
||||||
|
if (cctx->appliedParams.ldmParams.enableLdm == ZSTD_ps_enable) {
|
||||||
|
ZSTD_window_update(&cctx->ldmState.window, src, srcSize,
|
||||||
|
/* forceNonContiguous */ 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ZSTD_rust_compressContinue_correctOverflow(
|
||||||
|
void* context, const void* src, size_t srcSize)
|
||||||
|
{
|
||||||
|
ZSTD_CCtx* const cctx = (ZSTD_CCtx*)context;
|
||||||
|
ZSTD_MatchState_t* const ms = &cctx->blockState.matchState;
|
||||||
|
|
||||||
|
ZSTD_overflowCorrectIfNeeded(
|
||||||
|
ms, &cctx->workspace, &cctx->appliedParams,
|
||||||
|
src, (const BYTE*)src + srcSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t ZSTD_rust_compressContinue_frameChunk(
|
||||||
|
void* context, void* dst, size_t dstCapacity,
|
||||||
|
const void* src, size_t srcSize, U32 lastFrameChunk)
|
||||||
|
{
|
||||||
|
return ZSTD_compress_frameChunk(
|
||||||
|
(ZSTD_CCtx*)context, dst, dstCapacity, src, srcSize,
|
||||||
|
lastFrameChunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t ZSTD_rust_compressContinue_block(
|
||||||
|
void* context, void* dst, size_t dstCapacity,
|
||||||
|
const void* src, size_t srcSize, U32 lastFrameChunk)
|
||||||
|
{
|
||||||
|
(void)lastFrameChunk;
|
||||||
|
return ZSTD_compressBlock_internal(
|
||||||
|
(ZSTD_CCtx*)context, dst, dstCapacity, src, srcSize,
|
||||||
|
0 /* frame */);
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t ZSTD_compressContinue_dispatch(
|
||||||
|
ZSTD_CCtx* cctx, void* dst, size_t dstCapacity,
|
||||||
|
const void* src, size_t srcSize,
|
||||||
|
U32 frame, U32 lastFrameChunk,
|
||||||
|
size_t blockSizeMax, int checkBlockSize)
|
||||||
|
{
|
||||||
|
ZSTD_rust_compressContinueState state;
|
||||||
|
state.callbackContext = cctx;
|
||||||
|
state.writeFrameHeader = ZSTD_rust_compressContinue_writeFrameHeader;
|
||||||
|
state.updateWindow = ZSTD_rust_compressContinue_updateWindow;
|
||||||
|
state.correctOverflow = ZSTD_rust_compressContinue_correctOverflow;
|
||||||
|
state.compressFrameChunk = ZSTD_rust_compressContinue_frameChunk;
|
||||||
|
state.compressBlock = ZSTD_rust_compressContinue_block;
|
||||||
|
state.stage = &cctx->stage;
|
||||||
|
state.consumedSrcSize = &cctx->consumedSrcSize;
|
||||||
|
state.producedCSize = &cctx->producedCSize;
|
||||||
|
state.pledgedSrcSizePlusOne = cctx->pledgedSrcSizePlusOne;
|
||||||
|
state.blockSizeMax = blockSizeMax;
|
||||||
|
state.checkBlockSize = checkBlockSize;
|
||||||
|
return ZSTD_rust_compressContinue(
|
||||||
|
&state, dst, dstCapacity, src, srcSize, frame, lastFrameChunk);
|
||||||
|
}
|
||||||
|
|
||||||
void ZSTD_referenceExternalSequences(ZSTD_CCtx* cctx, rawSeq* seq, size_t nbSeq)
|
void ZSTD_referenceExternalSequences(ZSTD_CCtx* cctx, rawSeq* seq, size_t nbSeq)
|
||||||
{
|
{
|
||||||
assert(cctx->stage == ZSTDcs_init);
|
assert(cctx->stage == ZSTDcs_init);
|
||||||
@@ -3081,75 +3212,15 @@ void ZSTD_referenceExternalSequences(ZSTD_CCtx* cctx, rawSeq* seq, size_t nbSeq)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static size_t ZSTD_compressContinue_internal (ZSTD_CCtx* cctx,
|
|
||||||
void* dst, size_t dstCapacity,
|
|
||||||
const void* src, size_t srcSize,
|
|
||||||
U32 frame, U32 lastFrameChunk)
|
|
||||||
{
|
|
||||||
ZSTD_MatchState_t* const ms = &cctx->blockState.matchState;
|
|
||||||
size_t fhSize = 0;
|
|
||||||
|
|
||||||
DEBUGLOG(5, "ZSTD_compressContinue_internal, stage: %u, srcSize: %u",
|
|
||||||
cctx->stage, (unsigned)srcSize);
|
|
||||||
RETURN_ERROR_IF(cctx->stage==ZSTDcs_created, stage_wrong,
|
|
||||||
"missing init (ZSTD_compressBegin)");
|
|
||||||
|
|
||||||
if (frame && (cctx->stage==ZSTDcs_init)) {
|
|
||||||
fhSize = ZSTD_writeFrameHeader(dst, dstCapacity, &cctx->appliedParams,
|
|
||||||
cctx->pledgedSrcSizePlusOne-1, cctx->dictID);
|
|
||||||
FORWARD_IF_ERROR(fhSize, "ZSTD_writeFrameHeader failed");
|
|
||||||
assert(fhSize <= dstCapacity);
|
|
||||||
dstCapacity -= fhSize;
|
|
||||||
dst = (char*)dst + fhSize;
|
|
||||||
cctx->stage = ZSTDcs_ongoing;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!srcSize) return fhSize; /* do not generate an empty block if no input */
|
|
||||||
|
|
||||||
if (!ZSTD_window_update(&ms->window, src, srcSize, ms->forceNonContiguous)) {
|
|
||||||
ms->forceNonContiguous = 0;
|
|
||||||
ms->nextToUpdate = ms->window.dictLimit;
|
|
||||||
}
|
|
||||||
if (cctx->appliedParams.ldmParams.enableLdm == ZSTD_ps_enable) {
|
|
||||||
ZSTD_window_update(&cctx->ldmState.window, src, srcSize, /* forceNonContiguous */ 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!frame) {
|
|
||||||
/* overflow check and correction for block mode */
|
|
||||||
ZSTD_overflowCorrectIfNeeded(
|
|
||||||
ms, &cctx->workspace, &cctx->appliedParams,
|
|
||||||
src, (BYTE const*)src + srcSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
DEBUGLOG(5, "ZSTD_compressContinue_internal (blockSize=%u)", (unsigned)cctx->blockSizeMax);
|
|
||||||
{ size_t const cSize = frame ?
|
|
||||||
ZSTD_compress_frameChunk (cctx, dst, dstCapacity, src, srcSize, lastFrameChunk) :
|
|
||||||
ZSTD_compressBlock_internal (cctx, dst, dstCapacity, src, srcSize, 0 /* frame */);
|
|
||||||
int srcSizeWrong;
|
|
||||||
FORWARD_IF_ERROR(cSize, "%s", frame ? "ZSTD_compress_frameChunk failed" : "ZSTD_compressBlock_internal failed");
|
|
||||||
srcSizeWrong = ZSTD_rust_updateFrameProgression(
|
|
||||||
&cctx->consumedSrcSize, &cctx->producedCSize,
|
|
||||||
cctx->pledgedSrcSizePlusOne, srcSize, cSize, fhSize);
|
|
||||||
assert(!(cctx->appliedParams.fParams.contentSizeFlag && cctx->pledgedSrcSizePlusOne == 0));
|
|
||||||
if (cctx->pledgedSrcSizePlusOne != 0) { /* control src size */
|
|
||||||
ZSTD_STATIC_ASSERT(ZSTD_CONTENTSIZE_UNKNOWN == (unsigned long long)-1);
|
|
||||||
RETURN_ERROR_IF(
|
|
||||||
srcSizeWrong,
|
|
||||||
srcSize_wrong,
|
|
||||||
"error : pledgedSrcSize = %u, while realSrcSize >= %u",
|
|
||||||
(unsigned)cctx->pledgedSrcSizePlusOne-1,
|
|
||||||
(unsigned)cctx->consumedSrcSize);
|
|
||||||
}
|
|
||||||
return cSize + fhSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t ZSTD_compressContinue_public(ZSTD_CCtx* cctx,
|
size_t ZSTD_compressContinue_public(ZSTD_CCtx* cctx,
|
||||||
void* dst, size_t dstCapacity,
|
void* dst, size_t dstCapacity,
|
||||||
const void* src, size_t srcSize)
|
const void* src, size_t srcSize)
|
||||||
{
|
{
|
||||||
DEBUGLOG(5, "ZSTD_compressContinue (srcSize=%u)", (unsigned)srcSize);
|
DEBUGLOG(5, "ZSTD_compressContinue (srcSize=%u)", (unsigned)srcSize);
|
||||||
return ZSTD_compressContinue_internal(cctx, dst, dstCapacity, src, srcSize, 1 /* frame mode */, 0 /* last chunk */);
|
return ZSTD_compressContinue_dispatch(
|
||||||
|
cctx, dst, dstCapacity, src, srcSize,
|
||||||
|
1 /* frame mode */, 0 /* last chunk */,
|
||||||
|
cctx->blockSizeMax, 0 /* block size already selected */);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NOTE: Must just wrap ZSTD_compressContinue_public() */
|
/* NOTE: Must just wrap ZSTD_compressContinue_public() */
|
||||||
@@ -3177,10 +3248,10 @@ size_t ZSTD_getBlockSize(const ZSTD_CCtx* cctx)
|
|||||||
size_t ZSTD_compressBlock_deprecated(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize)
|
size_t ZSTD_compressBlock_deprecated(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize)
|
||||||
{
|
{
|
||||||
DEBUGLOG(5, "ZSTD_compressBlock: srcSize = %u", (unsigned)srcSize);
|
DEBUGLOG(5, "ZSTD_compressBlock: srcSize = %u", (unsigned)srcSize);
|
||||||
{ size_t const blockSizeMax = ZSTD_getBlockSize_deprecated(cctx);
|
return ZSTD_compressContinue_dispatch(
|
||||||
RETURN_ERROR_IF(srcSize > blockSizeMax, srcSize_wrong, "input is larger than a block"); }
|
cctx, dst, dstCapacity, src, srcSize,
|
||||||
|
0 /* block mode */, 0 /* last chunk */,
|
||||||
return ZSTD_compressContinue_internal(cctx, dst, dstCapacity, src, srcSize, 0 /* frame mode */, 0 /* last chunk */);
|
ZSTD_getBlockSize_deprecated(cctx), 1 /* validate block size */);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NOTE: Must just wrap ZSTD_compressBlock_deprecated() */
|
/* NOTE: Must just wrap ZSTD_compressBlock_deprecated() */
|
||||||
@@ -3581,10 +3652,11 @@ size_t ZSTD_compressEnd_public(ZSTD_CCtx* cctx,
|
|||||||
const void* src, size_t srcSize)
|
const void* src, size_t srcSize)
|
||||||
{
|
{
|
||||||
size_t endResult;
|
size_t endResult;
|
||||||
size_t const cSize = ZSTD_compressContinue_internal(cctx,
|
size_t const cSize = ZSTD_compressContinue_dispatch(
|
||||||
dst, dstCapacity, src, srcSize,
|
cctx, dst, dstCapacity, src, srcSize,
|
||||||
1 /* frame mode */, 1 /* last chunk */);
|
1 /* frame mode */, 1 /* last chunk */,
|
||||||
FORWARD_IF_ERROR(cSize, "ZSTD_compressContinue_internal failed");
|
cctx->blockSizeMax, 0 /* block size already selected */);
|
||||||
|
FORWARD_IF_ERROR(cSize, "ZSTD_compressContinue failed");
|
||||||
endResult = ZSTD_writeEpilogue(cctx, (char*)dst + cSize, dstCapacity-cSize);
|
endResult = ZSTD_writeEpilogue(cctx, (char*)dst + cSize, dstCapacity-cSize);
|
||||||
FORWARD_IF_ERROR(endResult, "ZSTD_writeEpilogue failed");
|
FORWARD_IF_ERROR(endResult, "ZSTD_writeEpilogue failed");
|
||||||
assert(!(cctx->appliedParams.fParams.contentSizeFlag && cctx->pledgedSrcSizePlusOne == 0));
|
assert(!(cctx->appliedParams.fParams.contentSizeFlag && cctx->pledgedSrcSizePlusOne == 0));
|
||||||
|
|||||||
@@ -340,6 +340,185 @@ pub unsafe extern "C" fn ZSTD_rust_compressFrameChunk(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CompressContinueHeaderFn = unsafe extern "C" fn(*mut c_void, *mut c_void, usize) -> usize;
|
||||||
|
type CompressContinueWindowFn = unsafe extern "C" fn(*mut c_void, *const c_void, usize);
|
||||||
|
type CompressContinueBlockFn =
|
||||||
|
unsafe extern "C" fn(*mut c_void, *mut c_void, usize, *const c_void, usize, c_uint) -> usize;
|
||||||
|
|
||||||
|
/// Explicit projection for the high-level continue and deprecated block APIs.
|
||||||
|
///
|
||||||
|
/// Rust owns stage transitions, frame-header sequencing, input progression,
|
||||||
|
/// and dispatch between the already-migrated frame-chunk/block bodies. The
|
||||||
|
/// opaque callback context remains in C, where callbacks update the private
|
||||||
|
/// match-state windows and invoke the C-owned context-sensitive operations.
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct ZSTD_rust_compressContinueState {
|
||||||
|
callback_context: *mut c_void,
|
||||||
|
write_frame_header: CompressContinueHeaderFn,
|
||||||
|
update_window: CompressContinueWindowFn,
|
||||||
|
correct_overflow: CompressContinueWindowFn,
|
||||||
|
compress_frame_chunk: CompressContinueBlockFn,
|
||||||
|
compress_block: CompressContinueBlockFn,
|
||||||
|
stage: *mut c_int,
|
||||||
|
consumed_src_size: *mut u64,
|
||||||
|
produced_c_size: *mut u64,
|
||||||
|
pledged_src_size_plus_one: u64,
|
||||||
|
block_size_max: usize,
|
||||||
|
check_block_size: c_int,
|
||||||
|
}
|
||||||
|
|
||||||
|
const _: () = {
|
||||||
|
assert!(offset_of!(ZSTD_rust_compressContinueState, callback_context) == 0);
|
||||||
|
assert!(offset_of!(ZSTD_rust_compressContinueState, write_frame_header) == size_of::<usize>());
|
||||||
|
assert!(offset_of!(ZSTD_rust_compressContinueState, update_window) == 2 * size_of::<usize>());
|
||||||
|
assert!(
|
||||||
|
offset_of!(ZSTD_rust_compressContinueState, correct_overflow) == 3 * size_of::<usize>()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
offset_of!(ZSTD_rust_compressContinueState, compress_frame_chunk) == 4 * size_of::<usize>()
|
||||||
|
);
|
||||||
|
assert!(offset_of!(ZSTD_rust_compressContinueState, compress_block) == 5 * size_of::<usize>());
|
||||||
|
assert!(offset_of!(ZSTD_rust_compressContinueState, stage) == 6 * size_of::<usize>());
|
||||||
|
assert!(
|
||||||
|
offset_of!(ZSTD_rust_compressContinueState, consumed_src_size) == 7 * size_of::<usize>()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
offset_of!(ZSTD_rust_compressContinueState, produced_c_size)
|
||||||
|
== 8 * (usize::BITS as usize / 8)
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
offset_of!(ZSTD_rust_compressContinueState, pledged_src_size_plus_one)
|
||||||
|
== 9 * size_of::<usize>()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
offset_of!(ZSTD_rust_compressContinueState, block_size_max)
|
||||||
|
== 9 * size_of::<usize>() + size_of::<u64>()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
offset_of!(ZSTD_rust_compressContinueState, check_block_size)
|
||||||
|
== 9 * size_of::<usize>() + size_of::<u64>() + size_of::<usize>()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
size_of::<ZSTD_rust_compressContinueState>()
|
||||||
|
== if size_of::<usize>() == 8 { 96 } else { 52 }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ZSTD_COMPRESSION_STAGE_CREATED: c_int = 0;
|
||||||
|
const ZSTD_COMPRESSION_STAGE_INIT: c_int = 1;
|
||||||
|
const ZSTD_COMPRESSION_STAGE_ONGOING: c_int = 2;
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
unsafe fn compress_continue_body_with(
|
||||||
|
state: &ZSTD_rust_compressContinueState,
|
||||||
|
mut dst: *mut c_void,
|
||||||
|
mut dst_capacity: usize,
|
||||||
|
src: *const c_void,
|
||||||
|
src_size: usize,
|
||||||
|
frame: c_uint,
|
||||||
|
last_frame_chunk: c_uint,
|
||||||
|
) -> usize {
|
||||||
|
if state.stage.is_null() || state.consumed_src_size.is_null() || state.produced_c_size.is_null()
|
||||||
|
{
|
||||||
|
return ERROR(ZstdErrorCode::Generic);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The deprecated block entry point performed this check before entering
|
||||||
|
* the old internal routine. Keep that ordering in the Rust orchestration. */
|
||||||
|
if state.check_block_size != 0 && src_size > state.block_size_max {
|
||||||
|
return ERROR(ZstdErrorCode::SrcSizeWrong);
|
||||||
|
}
|
||||||
|
|
||||||
|
if unsafe { *state.stage } == ZSTD_COMPRESSION_STAGE_CREATED {
|
||||||
|
return ERROR(ZstdErrorCode::StageWrong);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut frame_header_size = 0usize;
|
||||||
|
if frame != 0 && unsafe { *state.stage } == ZSTD_COMPRESSION_STAGE_INIT {
|
||||||
|
frame_header_size =
|
||||||
|
unsafe { (state.write_frame_header)(state.callback_context, dst, dst_capacity) };
|
||||||
|
if ERR_isError(frame_header_size) {
|
||||||
|
return frame_header_size;
|
||||||
|
}
|
||||||
|
if frame_header_size > dst_capacity {
|
||||||
|
return ERROR(ZstdErrorCode::DstSizeTooSmall);
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
*state.stage = ZSTD_COMPRESSION_STAGE_ONGOING;
|
||||||
|
}
|
||||||
|
dst = unsafe { dst.cast::<u8>().add(frame_header_size).cast() };
|
||||||
|
dst_capacity -= frame_header_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if src_size == 0 {
|
||||||
|
return frame_header_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe { (state.update_window)(state.callback_context, src, src_size) };
|
||||||
|
if frame == 0 {
|
||||||
|
unsafe { (state.correct_overflow)(state.callback_context, src, src_size) };
|
||||||
|
}
|
||||||
|
|
||||||
|
let compressed_size = if frame != 0 {
|
||||||
|
unsafe {
|
||||||
|
(state.compress_frame_chunk)(
|
||||||
|
state.callback_context,
|
||||||
|
dst,
|
||||||
|
dst_capacity,
|
||||||
|
src,
|
||||||
|
src_size,
|
||||||
|
last_frame_chunk,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unsafe {
|
||||||
|
(state.compress_block)(state.callback_context, dst, dst_capacity, src, src_size, 0)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if ERR_isError(compressed_size) {
|
||||||
|
return compressed_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
let source_size_wrong = update_frame_progression(
|
||||||
|
unsafe { &mut *state.consumed_src_size },
|
||||||
|
unsafe { &mut *state.produced_c_size },
|
||||||
|
state.pledged_src_size_plus_one,
|
||||||
|
src_size,
|
||||||
|
compressed_size,
|
||||||
|
frame_header_size,
|
||||||
|
);
|
||||||
|
if state.pledged_src_size_plus_one != 0 && source_size_wrong {
|
||||||
|
return ERROR(ZstdErrorCode::SrcSizeWrong);
|
||||||
|
}
|
||||||
|
compressed_size.wrapping_add(frame_header_size)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn ZSTD_rust_compressContinue(
|
||||||
|
state: *const ZSTD_rust_compressContinueState,
|
||||||
|
dst: *mut c_void,
|
||||||
|
dst_capacity: usize,
|
||||||
|
src: *const c_void,
|
||||||
|
src_size: usize,
|
||||||
|
frame: c_uint,
|
||||||
|
last_frame_chunk: c_uint,
|
||||||
|
) -> usize {
|
||||||
|
if state.is_null() {
|
||||||
|
return ERROR(ZstdErrorCode::Generic);
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
compress_continue_body_with(
|
||||||
|
&*state,
|
||||||
|
dst,
|
||||||
|
dst_capacity,
|
||||||
|
src,
|
||||||
|
src_size,
|
||||||
|
frame,
|
||||||
|
last_frame_chunk,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Explicit projection of the state used by `ZSTD_compressSequences_internal`.
|
/// Explicit projection of the state used by `ZSTD_compressSequences_internal`.
|
||||||
///
|
///
|
||||||
/// The C context and its function-pointer-bearing parameter structure remain
|
/// The C context and its function-pointer-bearing parameter structure remain
|
||||||
@@ -3161,6 +3340,327 @@ mod tests {
|
|||||||
assert_eq!(output, [0xa5; 5]);
|
assert_eq!(output, [0xa5; 5]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct CompressContinueTestContext {
|
||||||
|
header_calls: usize,
|
||||||
|
update_window_calls: usize,
|
||||||
|
overflow_calls: usize,
|
||||||
|
frame_calls: usize,
|
||||||
|
block_calls: usize,
|
||||||
|
last_frame_chunk: c_uint,
|
||||||
|
header_result: usize,
|
||||||
|
frame_result: usize,
|
||||||
|
block_result: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn compress_continue_test_context(
|
||||||
|
context: *mut c_void,
|
||||||
|
) -> &'static mut CompressContinueTestContext {
|
||||||
|
unsafe { &mut *context.cast::<CompressContinueTestContext>() }
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn compress_continue_test_header(
|
||||||
|
context: *mut c_void,
|
||||||
|
_dst: *mut c_void,
|
||||||
|
_dst_capacity: usize,
|
||||||
|
) -> usize {
|
||||||
|
let context = unsafe { compress_continue_test_context(context) };
|
||||||
|
context.header_calls += 1;
|
||||||
|
context.header_result
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn compress_continue_test_window(
|
||||||
|
context: *mut c_void,
|
||||||
|
_src: *const c_void,
|
||||||
|
_src_size: usize,
|
||||||
|
) {
|
||||||
|
let context = unsafe { compress_continue_test_context(context) };
|
||||||
|
context.update_window_calls += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn compress_continue_test_overflow(
|
||||||
|
context: *mut c_void,
|
||||||
|
_src: *const c_void,
|
||||||
|
_src_size: usize,
|
||||||
|
) {
|
||||||
|
let context = unsafe { compress_continue_test_context(context) };
|
||||||
|
context.overflow_calls += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn compress_continue_test_frame(
|
||||||
|
context: *mut c_void,
|
||||||
|
_dst: *mut c_void,
|
||||||
|
_dst_capacity: usize,
|
||||||
|
_src: *const c_void,
|
||||||
|
_src_size: usize,
|
||||||
|
last_frame_chunk: c_uint,
|
||||||
|
) -> usize {
|
||||||
|
let context = unsafe { compress_continue_test_context(context) };
|
||||||
|
context.frame_calls += 1;
|
||||||
|
context.last_frame_chunk = last_frame_chunk;
|
||||||
|
context.frame_result
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn compress_continue_test_block(
|
||||||
|
context: *mut c_void,
|
||||||
|
_dst: *mut c_void,
|
||||||
|
_dst_capacity: usize,
|
||||||
|
_src: *const c_void,
|
||||||
|
_src_size: usize,
|
||||||
|
_last_frame_chunk: c_uint,
|
||||||
|
) -> usize {
|
||||||
|
let context = unsafe { compress_continue_test_context(context) };
|
||||||
|
context.block_calls += 1;
|
||||||
|
context.block_result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compress_continue_test_state(
|
||||||
|
context: &mut CompressContinueTestContext,
|
||||||
|
stage: &mut c_int,
|
||||||
|
consumed_src_size: &mut u64,
|
||||||
|
produced_c_size: &mut u64,
|
||||||
|
pledged_src_size_plus_one: u64,
|
||||||
|
block_size_max: usize,
|
||||||
|
check_block_size: c_int,
|
||||||
|
) -> ZSTD_rust_compressContinueState {
|
||||||
|
ZSTD_rust_compressContinueState {
|
||||||
|
callback_context: (context as *mut CompressContinueTestContext).cast(),
|
||||||
|
write_frame_header: compress_continue_test_header,
|
||||||
|
update_window: compress_continue_test_window,
|
||||||
|
correct_overflow: compress_continue_test_overflow,
|
||||||
|
compress_frame_chunk: compress_continue_test_frame,
|
||||||
|
compress_block: compress_continue_test_block,
|
||||||
|
stage,
|
||||||
|
consumed_src_size,
|
||||||
|
produced_c_size,
|
||||||
|
pledged_src_size_plus_one,
|
||||||
|
block_size_max,
|
||||||
|
check_block_size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compress_continue_starts_frame_and_updates_progression() {
|
||||||
|
let mut context = CompressContinueTestContext {
|
||||||
|
header_result: 4,
|
||||||
|
frame_result: 6,
|
||||||
|
..CompressContinueTestContext::default()
|
||||||
|
};
|
||||||
|
let mut stage = ZSTD_COMPRESSION_STAGE_INIT;
|
||||||
|
let mut consumed = 7;
|
||||||
|
let mut produced = 11;
|
||||||
|
let state = compress_continue_test_state(
|
||||||
|
&mut context,
|
||||||
|
&mut stage,
|
||||||
|
&mut consumed,
|
||||||
|
&mut produced,
|
||||||
|
0,
|
||||||
|
16,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
let source = [0x11u8; 5];
|
||||||
|
let mut output = [0xa5u8; 32];
|
||||||
|
|
||||||
|
let result = unsafe {
|
||||||
|
ZSTD_rust_compressContinue(
|
||||||
|
&state,
|
||||||
|
output.as_mut_ptr().cast(),
|
||||||
|
output.len(),
|
||||||
|
source.as_ptr().cast(),
|
||||||
|
source.len(),
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(result, 10);
|
||||||
|
assert_eq!(stage, ZSTD_COMPRESSION_STAGE_ONGOING);
|
||||||
|
assert_eq!(consumed, 12);
|
||||||
|
assert_eq!(produced, 21);
|
||||||
|
assert_eq!(context.header_calls, 1);
|
||||||
|
assert_eq!(context.update_window_calls, 1);
|
||||||
|
assert_eq!(context.overflow_calls, 0);
|
||||||
|
assert_eq!(context.frame_calls, 1);
|
||||||
|
assert_eq!(context.block_calls, 0);
|
||||||
|
assert_eq!(context.last_frame_chunk, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compress_continue_empty_frame_only_writes_header() {
|
||||||
|
let mut context = CompressContinueTestContext {
|
||||||
|
header_result: 3,
|
||||||
|
..CompressContinueTestContext::default()
|
||||||
|
};
|
||||||
|
let mut stage = ZSTD_COMPRESSION_STAGE_INIT;
|
||||||
|
let mut consumed = 7;
|
||||||
|
let mut produced = 11;
|
||||||
|
let state = compress_continue_test_state(
|
||||||
|
&mut context,
|
||||||
|
&mut stage,
|
||||||
|
&mut consumed,
|
||||||
|
&mut produced,
|
||||||
|
0,
|
||||||
|
16,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
let mut output = [0xa5u8; 8];
|
||||||
|
|
||||||
|
let result = unsafe {
|
||||||
|
ZSTD_rust_compressContinue(
|
||||||
|
&state,
|
||||||
|
output.as_mut_ptr().cast(),
|
||||||
|
output.len(),
|
||||||
|
ptr::null(),
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(result, 3);
|
||||||
|
assert_eq!(stage, ZSTD_COMPRESSION_STAGE_ONGOING);
|
||||||
|
assert_eq!((consumed, produced), (7, 11));
|
||||||
|
assert_eq!(context.header_calls, 1);
|
||||||
|
assert_eq!(context.update_window_calls, 0);
|
||||||
|
assert_eq!(context.frame_calls, 0);
|
||||||
|
assert_eq!(output, [0xa5; 8]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compress_continue_rejects_invalid_stage_and_oversized_block_before_callbacks() {
|
||||||
|
let mut context = CompressContinueTestContext::default();
|
||||||
|
let mut stage = ZSTD_COMPRESSION_STAGE_INIT;
|
||||||
|
let mut consumed = 4;
|
||||||
|
let mut produced = 9;
|
||||||
|
let mut state = compress_continue_test_state(
|
||||||
|
&mut context,
|
||||||
|
&mut stage,
|
||||||
|
&mut consumed,
|
||||||
|
&mut produced,
|
||||||
|
0,
|
||||||
|
4,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
let source = [0x22u8; 5];
|
||||||
|
let mut output = [0xa5u8; 16];
|
||||||
|
|
||||||
|
let oversized = unsafe {
|
||||||
|
ZSTD_rust_compressContinue(
|
||||||
|
&state,
|
||||||
|
output.as_mut_ptr().cast(),
|
||||||
|
output.len(),
|
||||||
|
source.as_ptr().cast(),
|
||||||
|
source.len(),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
assert_eq!(oversized, ERROR(ZstdErrorCode::SrcSizeWrong));
|
||||||
|
assert_eq!(stage, ZSTD_COMPRESSION_STAGE_INIT);
|
||||||
|
assert_eq!((consumed, produced), (4, 9));
|
||||||
|
assert_eq!(context.update_window_calls, 0);
|
||||||
|
assert_eq!(context.block_calls, 0);
|
||||||
|
|
||||||
|
state.check_block_size = 0;
|
||||||
|
stage = ZSTD_COMPRESSION_STAGE_CREATED;
|
||||||
|
let created = unsafe {
|
||||||
|
ZSTD_rust_compressContinue(
|
||||||
|
&state,
|
||||||
|
output.as_mut_ptr().cast(),
|
||||||
|
output.len(),
|
||||||
|
source.as_ptr().cast(),
|
||||||
|
source.len(),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
assert_eq!(created, ERROR(ZstdErrorCode::StageWrong));
|
||||||
|
assert_eq!(stage, ZSTD_COMPRESSION_STAGE_CREATED);
|
||||||
|
assert_eq!(context.update_window_calls, 0);
|
||||||
|
assert_eq!(context.block_calls, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compress_continue_propagates_block_error_without_progression() {
|
||||||
|
let mut context = CompressContinueTestContext {
|
||||||
|
block_result: ERROR(ZstdErrorCode::DstSizeTooSmall),
|
||||||
|
..CompressContinueTestContext::default()
|
||||||
|
};
|
||||||
|
let mut stage = ZSTD_COMPRESSION_STAGE_ONGOING;
|
||||||
|
let mut consumed = 4;
|
||||||
|
let mut produced = 9;
|
||||||
|
let state = compress_continue_test_state(
|
||||||
|
&mut context,
|
||||||
|
&mut stage,
|
||||||
|
&mut consumed,
|
||||||
|
&mut produced,
|
||||||
|
0,
|
||||||
|
16,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
let source = [0x33u8; 5];
|
||||||
|
let mut output = [0xa5u8; 16];
|
||||||
|
|
||||||
|
let result = unsafe {
|
||||||
|
ZSTD_rust_compressContinue(
|
||||||
|
&state,
|
||||||
|
output.as_mut_ptr().cast(),
|
||||||
|
output.len(),
|
||||||
|
source.as_ptr().cast(),
|
||||||
|
source.len(),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(result, ERROR(ZstdErrorCode::DstSizeTooSmall));
|
||||||
|
assert_eq!(
|
||||||
|
(stage, consumed, produced),
|
||||||
|
(ZSTD_COMPRESSION_STAGE_ONGOING, 4, 9)
|
||||||
|
);
|
||||||
|
assert_eq!(context.update_window_calls, 1);
|
||||||
|
assert_eq!(context.overflow_calls, 1);
|
||||||
|
assert_eq!(context.block_calls, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compress_continue_reports_pledge_overrun_after_updating_counters() {
|
||||||
|
let mut context = CompressContinueTestContext {
|
||||||
|
block_result: 3,
|
||||||
|
..CompressContinueTestContext::default()
|
||||||
|
};
|
||||||
|
let mut stage = ZSTD_COMPRESSION_STAGE_ONGOING;
|
||||||
|
let mut consumed = 0;
|
||||||
|
let mut produced = 0;
|
||||||
|
let state = compress_continue_test_state(
|
||||||
|
&mut context,
|
||||||
|
&mut stage,
|
||||||
|
&mut consumed,
|
||||||
|
&mut produced,
|
||||||
|
6,
|
||||||
|
16,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
let source = [0x44u8; 6];
|
||||||
|
let mut output = [0xa5u8; 16];
|
||||||
|
|
||||||
|
let result = unsafe {
|
||||||
|
ZSTD_rust_compressContinue(
|
||||||
|
&state,
|
||||||
|
output.as_mut_ptr().cast(),
|
||||||
|
output.len(),
|
||||||
|
source.as_ptr().cast(),
|
||||||
|
source.len(),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(result, ERROR(ZstdErrorCode::SrcSizeWrong));
|
||||||
|
assert_eq!((consumed, produced), (6, 3));
|
||||||
|
assert_eq!(context.block_calls, 1);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn simple_strategy_follows_source_size_tiers() {
|
fn simple_strategy_follows_source_size_tiers() {
|
||||||
assert_eq!(ZSTD_rust_compressCCtxStrategy(0, 1), ZSTD_FAST);
|
assert_eq!(ZSTD_rust_compressCCtxStrategy(0, 1), ZSTD_FAST);
|
||||||
|
|||||||
Reference in New Issue
Block a user