Files
zstd-rs/rust/src/zstd_compress_frame.rs
T
ddidderr 534d7c9264 feat(compress): move frame header projection into Rust
Remove the C frame-header callback from the Rust-owned compressContinue
orchestration.  Project the applied frame parameters and dictionary ID as
scalars, so Rust can call the existing header serializer directly while
preserving pledged-size subtraction, stage transitions, and output accounting.
Route the sequence API's remaining header call directly to the same Rust leaf
and delete the redundant C wrapper.

Test Plan:
- ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo test --lib zstd_compress::tests::compress_continue -- --nocapture
- ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo clippy --all-targets -- -D warnings
- ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo test
- ulimit -v 41943040; make -j1
- ulimit -v 41943040; make -j1 -C tests test-zstream ZSTREAM_TESTTIME=-T2s
- ulimit -v 41943040; make -j1 -C tests test-fuzzer FUZZERTEST=-T3s FUZZER_FLAGS=--no-big-tests
2026-07-20 00:06:44 +02:00

873 lines
25 KiB
Rust

#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(clippy::missing_safety_doc)]
//! Frame-header and frame-trailer writing leaves.
//!
//! The high-level compression context remains in C. These leaves accept the
//! five scalar frame parameters they need, so no `ZSTD_CCtx_params` layout
//! crosses the language boundary.
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};
use std::ptr;
const ZSTD_MAGICNUMBER: u32 = 0xFD2F_B528;
const ZSTD_MAGIC_SKIPPABLE_START: u32 = 0x184D_2A50;
const ZSTD_FRAMEHEADERSIZE_MAX: usize = 18;
const ZSTD_SKIPPABLEHEADERSIZE: usize = 8;
const ZSTD_BLOCKHEADERSIZE: usize = 3;
const ZSTD_WINDOWLOG_ABSOLUTEMIN: u32 = 10;
const ZSTD_CONTENTSIZE_UNKNOWN: u64 = u64::MAX;
const ZSTD_F_ZSTD1: c_int = 0;
const ZSTD_BLOCK_SIZE: usize = 128 << 10;
const ZSTD_FAST: c_int = 1;
const ZSTD_BTULTRA2: c_int = 9;
const ZSTD_BT_COMPRESSED: u32 = 2;
const SPLIT_LEVELS: [c_int; 10] = [0, 0, 1, 2, 2, 3, 3, 4, 4, 4];
const ZSTD_BT_RLE: u32 = 1;
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) {
let bytes = value.to_le_bytes();
unsafe { ptr::copy_nonoverlapping(bytes.as_ptr(), dst, bytes.len()) };
}
#[inline]
unsafe fn write_le24(dst: *mut u8, value: u32) {
let bytes = value.to_le_bytes();
unsafe { ptr::copy_nonoverlapping(bytes.as_ptr(), dst, 3) };
}
#[inline]
unsafe fn write_le32(dst: *mut u8, value: u32) {
let bytes = value.to_le_bytes();
unsafe { ptr::copy_nonoverlapping(bytes.as_ptr(), dst, bytes.len()) };
}
#[inline]
unsafe fn write_le64(dst: *mut u8, value: u64) {
let bytes = value.to_le_bytes();
unsafe { ptr::copy_nonoverlapping(bytes.as_ptr(), dst, bytes.len()) };
}
/// Writes a raw block header and payload, returning the complete block size.
pub(crate) unsafe fn write_raw_block(
dst: *mut u8,
dst_capacity: usize,
src: *const u8,
src_size: usize,
last_block: u32,
) -> usize {
let needed = match src_size.checked_add(ZSTD_BLOCKHEADERSIZE) {
Some(value) => value,
None => return ERROR(ZstdErrorCode::DstSizeTooSmall),
};
if needed > dst_capacity {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
unsafe {
write_le24(
dst,
last_block.wrapping_add((src_size as u32).wrapping_shl(3)),
);
if src_size != 0 {
ptr::copy_nonoverlapping(src, dst.add(ZSTD_BLOCKHEADERSIZE), src_size);
}
}
needed
}
/// Rust implementation of the private compressed-block header serializer.
///
/// The C wrapper retains the original static helper signature and keeps its
/// debug logging at the call site. The helper itself only needs scalar
/// values and writes the low 24 bits in little-endian order, as `MEM_writeLE24`
/// does.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_writeBlockHeader(
op: *mut c_void,
c_size: usize,
block_size: usize,
last_block: c_uint,
) {
let block_type = if c_size == 1 {
ZSTD_BT_RLE
} else {
ZSTD_BT_COMPRESSED
};
let size = if c_size == 1 { block_size } else { c_size };
let header = last_block
.wrapping_add(block_type << 1)
.wrapping_add((size as u32).wrapping_shl(3));
unsafe { write_le24(op.cast(), header) };
}
/// Rust implementation of the private `ZSTD_writeFrameHeader()` leaf.
///
/// `no_dict_id_flag`, `checksum_flag`, `content_size_flag`, `format`, and
/// `window_log` are extracted by C callers from `ZSTD_CCtx_params`. Keeping
/// those projections scalar avoids any dependency on the full
/// context-parameter layout here.
///
/// The caller must provide a writable buffer of at least
/// `ZSTD_FRAMEHEADERSIZE_MAX` bytes, or this returns `dstSize_tooSmall`. As
/// in the C routine, `pledged_src_size` must not be
/// `ZSTD_CONTENTSIZE_UNKNOWN` when `content_size_flag` is nonzero.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_writeFrameHeader(
dst: *mut c_void,
dst_capacity: usize,
no_dict_id_flag: c_int,
checksum_flag: c_int,
content_size_flag: c_int,
format: c_int,
window_log: c_uint,
pledged_src_size: u64,
dict_id: u32,
) -> usize {
if dst_capacity < ZSTD_FRAMEHEADERSIZE_MAX {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
let dst = dst.cast::<u8>();
let dict_id_size_code_length =
u32::from(dict_id > 0) + u32::from(dict_id >= 256) + u32::from(dict_id >= 65_536);
let dict_id_size_code = if no_dict_id_flag != 0 {
0
} else {
dict_id_size_code_length
};
let checksum_flag = u32::from(checksum_flag > 0);
let content_size_flag = content_size_flag != 0;
debug_assert!(!(content_size_flag && pledged_src_size == ZSTD_CONTENTSIZE_UNKNOWN));
/* Parameters are validated before this leaf is reached. Avoid a Rust
* shift panic if a malformed caller bypasses that validation. */
let window_size = 1u64.checked_shl(window_log).unwrap_or(0);
let single_segment = u32::from(content_size_flag && window_size >= pledged_src_size);
let window_log_byte = window_log
.wrapping_sub(ZSTD_WINDOWLOG_ABSOLUTEMIN)
.wrapping_shl(3) as u8;
let fcs_code = if content_size_flag {
u32::from(pledged_src_size >= 256)
+ u32::from(pledged_src_size >= 65_536 + 256)
+ u32::from(pledged_src_size >= 0xFFFF_FFFF)
} else {
0
};
let frame_header_description_byte =
(dict_id_size_code + (checksum_flag << 2) + (single_segment << 5) + (fcs_code << 6)) as u8;
let mut pos = 0usize;
if format == ZSTD_F_ZSTD1 {
unsafe { write_le32(dst, ZSTD_MAGICNUMBER) };
pos = 4;
}
unsafe { dst.add(pos).write(frame_header_description_byte) };
pos += 1;
if single_segment == 0 {
unsafe { dst.add(pos).write(window_log_byte) };
pos += 1;
}
match dict_id_size_code {
0 => {}
1 => {
unsafe { dst.add(pos).write(dict_id as u8) };
pos += 1;
}
2 => {
unsafe { write_le16(dst.add(pos), dict_id as u16) };
pos += 2;
}
3 => {
unsafe { write_le32(dst.add(pos), dict_id) };
pos += 4;
}
_ => unreachable!("dictionary ID size code is bounded to 0..=3"),
}
match fcs_code {
0 => {
if single_segment != 0 {
unsafe { dst.add(pos).write(pledged_src_size as u8) };
pos += 1;
}
}
1 => {
unsafe { write_le16(dst.add(pos), (pledged_src_size - 256) as u16) };
pos += 2;
}
2 => {
unsafe { write_le32(dst.add(pos), pledged_src_size as u32) };
pos += 4;
}
3 => {
unsafe { write_le64(dst.add(pos), pledged_src_size) };
pos += 8;
}
_ => unreachable!("frame content size code is bounded to 0..=3"),
}
pos
}
/// Rust implementation of the public `ZSTD_writeSkippableFrame()` ABI.
///
/// Integration removes the C function body, allowing this direct export to
/// provide the existing public symbol without a wrapper.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_writeSkippableFrame(
dst: *mut c_void,
dst_capacity: usize,
src: *const c_void,
src_size: usize,
magic_variant: c_uint,
) -> usize {
let total_size = match src_size.checked_add(ZSTD_SKIPPABLEHEADERSIZE) {
Some(size) => size,
None if src_size > u32::MAX as usize => return ERROR(ZstdErrorCode::SrcSizeWrong),
/* A frame whose payload fills the `size_t` address space cannot have
* room for its eight-byte header. */
None => return ERROR(ZstdErrorCode::DstSizeTooSmall),
};
if dst_capacity < total_size {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
if src_size > u32::MAX as usize {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
if magic_variant > 15 {
return ERROR(ZstdErrorCode::ParameterOutOfBound);
}
let dst = dst.cast::<u8>();
let src = src.cast::<u8>();
unsafe { write_le32(dst, ZSTD_MAGIC_SKIPPABLE_START + magic_variant) };
unsafe { write_le32(dst.add(4), src_size as u32) };
if src_size != 0 {
unsafe { ptr::copy_nonoverlapping(src, dst.add(ZSTD_SKIPPABLEHEADERSIZE), src_size) };
}
total_size
}
/// Rust implementation of the private `ZSTD_writeLastEmptyBlock()` leaf.
///
/// Integration removes the C body and declares this symbol before the C call
/// sites, so the direct export needs no context or layout bridge.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_writeLastEmptyBlock(dst: *mut c_void, dst_capacity: usize) -> usize {
if dst_capacity < ZSTD_BLOCKHEADERSIZE {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
/* lastBlock = 1, block type = bt_raw, and block size = 0. */
unsafe { write_le24(dst.cast::<u8>(), 1) };
ZSTD_BLOCKHEADERSIZE
}
/// Rust implementation of the raw fallback block serializer.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_noCompressBlock(
dst: *mut c_void,
dst_capacity: usize,
src: *const c_void,
src_size: usize,
last_block: u32,
) -> usize {
unsafe { write_raw_block(dst.cast(), dst_capacity, src.cast(), src_size, last_block) }
}
/// Rust implementation of the one-byte RLE fallback block serializer.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_rleCompressBlock(
dst: *mut c_void,
dst_capacity: usize,
src: u8,
src_size: usize,
last_block: u32,
) -> usize {
if dst_capacity < 4 {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
let header = last_block
.wrapping_add(ZSTD_BT_RLE << 1)
.wrapping_add((src_size as u32).wrapping_shl(3));
let dst = dst.cast::<u8>();
unsafe {
write_le24(dst, header);
dst.add(3).write(src);
}
4
}
/// Rust implementation of the private `ZSTD_optimalBlockSize()` policy.
///
/// The compressor context stays in C. This leaf receives only the source
/// block, scalar policy inputs, and the pre-split workspace projected by the C
/// caller, then delegates the actual split heuristic to `ZSTD_splitBlock`.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_optimalBlockSize(
src: *const c_void,
src_size: usize,
block_size_max: usize,
split_level: c_int,
strategy: c_int,
savings: c_longlong,
workspace: *mut c_void,
workspace_size: usize,
) -> usize {
if src_size < ZSTD_BLOCK_SIZE || block_size_max < ZSTD_BLOCK_SIZE {
return src_size.min(block_size_max);
}
if savings < 3 {
return ZSTD_BLOCK_SIZE;
}
let split_level = if split_level == 1 {
return ZSTD_BLOCK_SIZE;
} else if split_level == 0 {
debug_assert!((ZSTD_FAST..=ZSTD_BTULTRA2).contains(&strategy));
SPLIT_LEVELS[strategy as usize]
} else {
debug_assert!((2..=6).contains(&split_level));
split_level - 2
};
unsafe { ZSTD_splitBlock(src, block_size_max, split_level, workspace, workspace_size) }
}
/// Rust implementation of the private `ZSTD_writeEpilogue()` serializer.
///
/// The 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 supplied as its
/// low 32 bits; the Rust end-of-frame orchestrator computes that value from
/// the projected XXH64 state.
#[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::*;
use crate::errors::{ERR_getErrorCode, ERR_isError};
#[test]
fn writes_single_segment_empty_frame_header() {
let mut output = [0u8; ZSTD_FRAMEHEADERSIZE_MAX];
let written = unsafe {
ZSTD_rust_writeFrameHeader(
output.as_mut_ptr().cast(),
output.len(),
0,
0,
1,
ZSTD_F_ZSTD1,
20,
0,
0,
)
};
assert_eq!(written, 6);
assert_eq!(&output[..written], &[0x28, 0xB5, 0x2F, 0xFD, 0x20, 0x00]);
}
#[test]
fn writes_magicless_unknown_size_header() {
let mut output = [0u8; ZSTD_FRAMEHEADERSIZE_MAX];
let written = unsafe {
ZSTD_rust_writeFrameHeader(
output.as_mut_ptr().cast(),
output.len(),
0,
0,
0,
1,
10,
ZSTD_CONTENTSIZE_UNKNOWN,
0,
)
};
assert_eq!(written, 2);
assert_eq!(&output[..written], &[0x00, 0x00]);
}
#[test]
fn writes_maximal_frame_header() {
let mut output = [0u8; ZSTD_FRAMEHEADERSIZE_MAX];
let content_size = 0x1_0000_0000u64;
let written = unsafe {
ZSTD_rust_writeFrameHeader(
output.as_mut_ptr().cast(),
output.len(),
0,
1,
1,
ZSTD_F_ZSTD1,
20,
content_size,
0x1234_5678,
)
};
assert_eq!(written, ZSTD_FRAMEHEADERSIZE_MAX);
assert_eq!(
&output[..written],
&[
0x28, 0xB5, 0x2F, 0xFD, 0xC7, 0x50, 0x78, 0x56, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00,
]
);
}
#[test]
fn encodes_short_dictionary_and_content_size_fields() {
let mut output = [0u8; ZSTD_FRAMEHEADERSIZE_MAX];
let written = unsafe {
ZSTD_rust_writeFrameHeader(
output.as_mut_ptr().cast(),
output.len(),
0,
0,
1,
ZSTD_F_ZSTD1,
10,
256,
255,
)
};
assert_eq!(written, 8);
assert_eq!(
&output[..written],
&[0x28, 0xB5, 0x2F, 0xFD, 0x61, 0xFF, 0x00, 0x00]
);
}
#[test]
fn ignores_dictionary_id_when_requested() {
let mut output = [0u8; ZSTD_FRAMEHEADERSIZE_MAX];
let written = unsafe {
ZSTD_rust_writeFrameHeader(
output.as_mut_ptr().cast(),
output.len(),
1,
0,
1,
ZSTD_F_ZSTD1,
10,
256,
0x1234_5678,
)
};
assert_eq!(written, 7);
assert_eq!(
&output[..written],
&[0x28, 0xB5, 0x2F, 0xFD, 0x60, 0x00, 0x00]
);
}
#[test]
fn frame_header_requires_worst_case_capacity() {
let mut output = [0u8; ZSTD_FRAMEHEADERSIZE_MAX - 1];
let result = unsafe {
ZSTD_rust_writeFrameHeader(
output.as_mut_ptr().cast(),
output.len(),
0,
0,
1,
ZSTD_F_ZSTD1,
20,
0,
0,
)
};
assert!(ERR_isError(result));
assert_eq!(
ERR_getErrorCode(result),
ZstdErrorCode::DstSizeTooSmall as i32
);
}
#[test]
fn writes_skippable_frame_and_validates_parameters() {
let mut output = [0u8; 11];
let written = unsafe {
ZSTD_writeSkippableFrame(
output.as_mut_ptr().cast(),
output.len(),
b"abc".as_ptr().cast(),
3,
2,
)
};
assert_eq!(written, output.len());
assert_eq!(
output,
[0x52, 0x2A, 0x4D, 0x18, 0x03, 0x00, 0x00, 0x00, b'a', b'b', b'c']
);
let too_small = unsafe {
ZSTD_writeSkippableFrame(
output.as_mut_ptr().cast(),
output.len() - 1,
b"abc".as_ptr().cast(),
3,
2,
)
};
assert_eq!(
ERR_getErrorCode(too_small),
ZstdErrorCode::DstSizeTooSmall as i32
);
let invalid_variant = unsafe {
ZSTD_writeSkippableFrame(
output.as_mut_ptr().cast(),
output.len(),
b"abc".as_ptr().cast(),
3,
16,
)
};
assert_eq!(
ERR_getErrorCode(invalid_variant),
ZstdErrorCode::ParameterOutOfBound as i32
);
}
#[test]
fn writes_last_empty_block() {
let mut output = [0u8; ZSTD_BLOCKHEADERSIZE];
assert_eq!(
unsafe { ZSTD_writeLastEmptyBlock(output.as_mut_ptr().cast(), output.len()) },
ZSTD_BLOCKHEADERSIZE
);
assert_eq!(output, [1, 0, 0]);
let result = unsafe { ZSTD_writeLastEmptyBlock(output.as_mut_ptr().cast(), 2) };
assert_eq!(
ERR_getErrorCode(result),
ZstdErrorCode::DstSizeTooSmall as i32
);
}
#[test]
fn writes_rle_block_header() {
let mut output = [0u8; ZSTD_BLOCKHEADERSIZE];
unsafe {
ZSTD_rust_writeBlockHeader(output.as_mut_ptr().cast(), 1, 0x12345, 1);
}
assert_eq!(output, [0x2b, 0x1a, 0x09]);
}
#[test]
fn writes_compressed_block_header() {
let mut output = [0u8; ZSTD_BLOCKHEADERSIZE];
unsafe {
ZSTD_rust_writeBlockHeader(output.as_mut_ptr().cast(), 0x23456, 0x54321, 1);
}
assert_eq!(output, [0xb5, 0xa2, 0x11]);
}
#[test]
fn optimal_block_size_keeps_small_blocks_intact() {
let result = unsafe {
ZSTD_rust_optimalBlockSize(
ptr::null(),
ZSTD_BLOCK_SIZE - 1,
ZSTD_BLOCK_SIZE,
0,
ZSTD_FAST,
3,
ptr::null_mut(),
0,
)
};
assert_eq!(result, ZSTD_BLOCK_SIZE - 1);
}
#[test]
fn optimal_block_size_requires_savings_before_splitting() {
let result = unsafe {
ZSTD_rust_optimalBlockSize(
ptr::null(),
ZSTD_BLOCK_SIZE,
ZSTD_BLOCK_SIZE,
0,
ZSTD_FAST,
2,
ptr::null_mut(),
0,
)
};
assert_eq!(result, ZSTD_BLOCK_SIZE);
}
#[test]
fn optimal_block_size_honors_explicit_no_split_level() {
let result = unsafe {
ZSTD_rust_optimalBlockSize(
ptr::null(),
ZSTD_BLOCK_SIZE,
ZSTD_BLOCK_SIZE,
1,
ZSTD_BTULTRA2,
3,
ptr::null_mut(),
0,
)
};
assert_eq!(result, ZSTD_BLOCK_SIZE);
}
#[test]
fn optimal_block_size_delegates_valid_split_requests() {
let source = vec![0u8; ZSTD_BLOCK_SIZE];
let mut workspace = vec![0usize; 8_208usize.div_ceil(std::mem::size_of::<usize>())];
let result = unsafe {
ZSTD_rust_optimalBlockSize(
source.as_ptr().cast(),
source.len(),
source.len(),
2,
ZSTD_FAST,
3,
workspace.as_mut_ptr().cast(),
workspace.len() * std::mem::size_of::<usize>(),
)
};
assert!(result > 0);
assert!(result <= ZSTD_BLOCK_SIZE);
}
#[test]
fn raw_block_serializer_writes_header_and_payload() {
let source = *b"abc";
let mut output = [0u8; 6];
let result = unsafe {
ZSTD_rust_noCompressBlock(
output.as_mut_ptr().cast(),
output.len(),
source.as_ptr().cast(),
source.len(),
1,
)
};
assert_eq!(result, output.len());
assert_eq!(output, [0x19, 0, 0, b'a', b'b', b'c']);
}
#[test]
fn raw_block_serializer_checks_capacity() {
let source = *b"a";
let mut output = [0u8; 3];
let result = unsafe {
ZSTD_rust_noCompressBlock(
output.as_mut_ptr().cast(),
output.len(),
source.as_ptr().cast(),
source.len(),
0,
)
};
assert_eq!(
ERR_getErrorCode(result),
ZstdErrorCode::DstSizeTooSmall as i32
);
}
#[test]
fn rle_block_serializer_writes_header_and_value() {
let mut output = [0u8; 4];
let result = unsafe {
ZSTD_rust_rleCompressBlock(output.as_mut_ptr().cast(), output.len(), b'Z', 7, 0)
};
assert_eq!(result, 4);
assert_eq!(output, [0x3a, 0, 0, b'Z']);
}
#[test]
fn rle_block_serializer_checks_capacity() {
let mut output = [0u8; 3];
let result = unsafe {
ZSTD_rust_rleCompressBlock(output.as_mut_ptr().cast(), output.len(), b'Z', 7, 0)
};
assert_eq!(
ERR_getErrorCode(result),
ZstdErrorCode::DstSizeTooSmall as i32
);
}
#[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);
}
}