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
873 lines
25 KiB
Rust
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);
|
|
}
|
|
}
|