diff --git a/lib/compress/zstd_compress_literals.c b/lib/compress/zstd_compress_literals.c index 06036de5d..e8077ddcd 100644 --- a/lib/compress/zstd_compress_literals.c +++ b/lib/compress/zstd_compress_literals.c @@ -8,228 +8,10 @@ * You may select, at your option, one of the above-listed licenses. */ - /*-************************************* - * Dependencies - ***************************************/ -#include "zstd_compress_literals.h" - - -/* ************************************************************** -* Debug Traces -****************************************************************/ -#if DEBUGLEVEL >= 2 - -static size_t showHexa(const void* src, size_t srcSize) -{ - const BYTE* const ip = (const BYTE*)src; - size_t u; - for (u=0; u31) + (srcSize>4095); - - DEBUGLOG(5, "ZSTD_noCompressLiterals: srcSize=%zu, dstCapacity=%zu", srcSize, dstCapacity); - - RETURN_ERROR_IF(srcSize + flSize > dstCapacity, dstSize_tooSmall, ""); - - switch(flSize) - { - case 1: /* 2 - 1 - 5 */ - ostart[0] = (BYTE)((U32)set_basic + (srcSize<<3)); - break; - case 2: /* 2 - 2 - 12 */ - MEM_writeLE16(ostart, (U16)((U32)set_basic + (1<<2) + (srcSize<<4))); - break; - case 3: /* 2 - 2 - 20 */ - MEM_writeLE32(ostart, (U32)((U32)set_basic + (3<<2) + (srcSize<<4))); - break; - default: /* not necessary : flSize is {1,2,3} */ - assert(0); - } - - ZSTD_memcpy(ostart + flSize, src, srcSize); - DEBUGLOG(5, "Raw (uncompressed) literals: %u -> %u", (U32)srcSize, (U32)(srcSize + flSize)); - return srcSize + flSize; -} - -static int allBytesIdentical(const void* src, size_t srcSize) -{ - assert(srcSize >= 1); - assert(src != NULL); - { const BYTE b = ((const BYTE*)src)[0]; - size_t p; - for (p=1; p31) + (srcSize>4095); - - assert(dstCapacity >= 4); (void)dstCapacity; - assert(allBytesIdentical(src, srcSize)); - - switch(flSize) - { - case 1: /* 2 - 1 - 5 */ - ostart[0] = (BYTE)((U32)set_rle + (srcSize<<3)); - break; - case 2: /* 2 - 2 - 12 */ - MEM_writeLE16(ostart, (U16)((U32)set_rle + (1<<2) + (srcSize<<4))); - break; - case 3: /* 2 - 2 - 20 */ - MEM_writeLE32(ostart, (U32)((U32)set_rle + (3<<2) + (srcSize<<4))); - break; - default: /* not necessary : flSize is {1,2,3} */ - assert(0); - } - - ostart[flSize] = *(const BYTE*)src; - DEBUGLOG(5, "RLE : Repeated Literal (%02X: %u times) -> %u bytes encoded", ((const BYTE*)src)[0], (U32)srcSize, (U32)flSize + 1); - return flSize+1; -} - -/* ZSTD_minLiteralsToCompress() : - * returns minimal amount of literals - * for literal compression to even be attempted. - * Minimum is made tighter as compression strategy increases. +/* + * Literals compression is implemented in + * rust/src/zstd_compress_literals.rs. Keep this declaration-compatible + * translation unit so every native build continues to use the existing + * internal header and links the Rust ABI exports. */ -static size_t -ZSTD_minLiteralsToCompress(ZSTD_strategy strategy, HUF_repeat huf_repeat) -{ - assert((int)strategy >= 0); - assert((int)strategy <= 9); - /* btultra2 : min 8 bytes; - * then 2x larger for each successive compression strategy - * max threshold 64 bytes */ - { int const shift = MIN(9-(int)strategy, 3); - size_t const mintc = (huf_repeat == HUF_repeat_valid) ? 6 : (size_t)8 << shift; - DEBUGLOG(7, "minLiteralsToCompress = %zu", mintc); - return mintc; - } -} - -size_t ZSTD_compressLiterals ( - void* dst, size_t dstCapacity, - const void* src, size_t srcSize, - void* entropyWorkspace, size_t entropyWorkspaceSize, - const ZSTD_hufCTables_t* prevHuf, - ZSTD_hufCTables_t* nextHuf, - ZSTD_strategy strategy, - int disableLiteralCompression, - int suspectUncompressible, - int bmi2) -{ - size_t const lhSize = 3 + (srcSize >= 1 KB) + (srcSize >= 16 KB); - BYTE* const ostart = (BYTE*)dst; - U32 singleStream = srcSize < 256; - SymbolEncodingType_e hType = set_compressed; - size_t cLitSize; - - DEBUGLOG(5,"ZSTD_compressLiterals (disableLiteralCompression=%i, srcSize=%u, dstCapacity=%zu)", - disableLiteralCompression, (U32)srcSize, dstCapacity); - - DEBUGLOG(6, "Completed literals listing (%zu bytes)", showHexa(src, srcSize)); - - /* Prepare nextEntropy assuming reusing the existing table */ - ZSTD_memcpy(nextHuf, prevHuf, sizeof(*prevHuf)); - - if (disableLiteralCompression) - return ZSTD_noCompressLiterals(dst, dstCapacity, src, srcSize); - - /* if too small, don't even attempt compression (speed opt) */ - if (srcSize < ZSTD_minLiteralsToCompress(strategy, prevHuf->repeatMode)) - return ZSTD_noCompressLiterals(dst, dstCapacity, src, srcSize); - - RETURN_ERROR_IF(dstCapacity < lhSize+1, dstSize_tooSmall, "not enough space for compression"); - { HUF_repeat repeat = prevHuf->repeatMode; - int const flags = 0 - | (bmi2 ? HUF_flags_bmi2 : 0) - | (strategy < ZSTD_lazy && srcSize <= 1024 ? HUF_flags_preferRepeat : 0) - | (strategy >= HUF_OPTIMAL_DEPTH_THRESHOLD ? HUF_flags_optimalDepth : 0) - | (suspectUncompressible ? HUF_flags_suspectUncompressible : 0); - - typedef size_t (*huf_compress_f)(void*, size_t, const void*, size_t, unsigned, unsigned, void*, size_t, HUF_CElt*, HUF_repeat*, int); - huf_compress_f huf_compress; - if (repeat == HUF_repeat_valid && lhSize == 3) singleStream = 1; - huf_compress = singleStream ? HUF_compress1X_repeat : HUF_compress4X_repeat; - cLitSize = huf_compress(ostart+lhSize, dstCapacity-lhSize, - src, srcSize, - HUF_SYMBOLVALUE_MAX, LitHufLog, - entropyWorkspace, entropyWorkspaceSize, - (HUF_CElt*)nextHuf->CTable, - &repeat, flags); - DEBUGLOG(5, "%zu literals compressed into %zu bytes (before header)", srcSize, cLitSize); - if (repeat != HUF_repeat_none) { - /* reused the existing table */ - DEBUGLOG(5, "reusing statistics from previous huffman block"); - hType = set_repeat; - } - } - - { size_t const minGain = ZSTD_minGain(srcSize, strategy); - if ((cLitSize==0) || (cLitSize >= srcSize - minGain) || ERR_isError(cLitSize)) { - ZSTD_memcpy(nextHuf, prevHuf, sizeof(*prevHuf)); - return ZSTD_noCompressLiterals(dst, dstCapacity, src, srcSize); - } } - if (cLitSize==1) { - /* A return value of 1 signals that the alphabet consists of a single symbol. - * However, in some rare circumstances, it could be the compressed size (a single byte). - * For that outcome to have a chance to happen, it's necessary that `srcSize < 8`. - * (it's also necessary to not generate statistics). - * Therefore, in such a case, actively check that all bytes are identical. */ - if ((srcSize >= 8) || allBytesIdentical(src, srcSize)) { - ZSTD_memcpy(nextHuf, prevHuf, sizeof(*prevHuf)); - return ZSTD_compressRleLiteralsBlock(dst, dstCapacity, src, srcSize); - } } - - if (hType == set_compressed) { - /* using a newly constructed table */ - nextHuf->repeatMode = HUF_repeat_check; - } - - /* Build header */ - switch(lhSize) - { - case 3: /* 2 - 2 - 10 - 10 */ - if (!singleStream) assert(srcSize >= MIN_LITERALS_FOR_4_STREAMS); - { U32 const lhc = hType + ((U32)(!singleStream) << 2) + ((U32)srcSize<<4) + ((U32)cLitSize<<14); - MEM_writeLE24(ostart, lhc); - break; - } - case 4: /* 2 - 2 - 14 - 14 */ - assert(srcSize >= MIN_LITERALS_FOR_4_STREAMS); - { U32 const lhc = hType + (2 << 2) + ((U32)srcSize<<4) + ((U32)cLitSize<<18); - MEM_writeLE32(ostart, lhc); - break; - } - case 5: /* 2 - 2 - 18 - 18 */ - assert(srcSize >= MIN_LITERALS_FOR_4_STREAMS); - { U32 const lhc = hType + (3 << 2) + ((U32)srcSize<<4) + ((U32)cLitSize<<22); - MEM_writeLE32(ostart, lhc); - ostart[4] = (BYTE)(cLitSize >> 10); - break; - } - default: /* not possible : lhSize is {3,4,5} */ - assert(0); - } - DEBUGLOG(5, "Compressed literals: %u -> %u", (U32)srcSize, (U32)(lhSize+cLitSize)); - return lhSize+cLitSize; -} +#include "zstd_compress_literals.h" diff --git a/rust/README.md b/rust/README.md index 6f4e5cdc1..d9ced35db 100644 --- a/rust/README.md +++ b/rust/README.md @@ -29,6 +29,8 @@ zstd ABI: - Compression primitives - `hist` counts byte frequencies for FSE and Huffman compression. - `zstd_presplit` chooses split points for full compression blocks. + - `zstd_compress_literals` emits raw, RLE, and Huffman literal sections + while preserving the compressor's Huffman-table repeat state. - Runtime support - `threading` provides platform pthread wrappers required by zstd headers. - `pool` implements the bounded worker pool used by multithreaded compression. diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 48c86c8ed..abb1cc1c7 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -21,6 +21,8 @@ pub mod pool; pub mod threading; pub mod xxhash; pub mod zstd_common; +#[cfg(feature = "compression")] +pub mod zstd_compress_literals; #[cfg(feature = "decompression")] pub mod zstd_ddict; #[cfg(feature = "compression")] diff --git a/rust/src/zstd_compress_literals.rs b/rust/src/zstd_compress_literals.rs new file mode 100644 index 000000000..cf2047f2d --- /dev/null +++ b/rust/src/zstd_compress_literals.rs @@ -0,0 +1,536 @@ +#![allow(non_snake_case)] + +//! Compression of a block's literals section. +//! +//! This is a direct ABI translation of `zstd_compress_literals.c`. The +//! surrounding block compressor remains in C during the migration, so the +//! Huffman table wrapper deliberately uses the exact C layout and the caller +//! continues to own the entropy workspace. + +use crate::common::{LIT_HUF_LOG, MIN_LITERALS_FOR_4_STREAMS}; +use crate::errors::{ERR_isError, ZstdErrorCode, ERROR}; +use crate::huf_compress::{HUF_compress1X_repeat, HUF_compress4X_repeat}; +use crate::mem::{MEM_writeLE16, MEM_writeLE24, MEM_writeLE32}; +use std::ffi::c_void; +use std::mem::size_of; +use std::os::raw::c_int; +use std::ptr; + +const HUF_SYMBOLVALUE_MAX: u32 = 255; +const HUF_CTABLE_SIZE_ST: usize = HUF_SYMBOLVALUE_MAX as usize + 2; + +const HUF_REPEAT_NONE: c_int = 0; +const HUF_REPEAT_CHECK: c_int = 1; +const HUF_REPEAT_VALID: c_int = 2; + +const HUF_FLAGS_BMI2: c_int = 1 << 0; +const HUF_FLAGS_OPTIMAL_DEPTH: c_int = 1 << 1; +const HUF_FLAGS_PREFER_REPEAT: c_int = 1 << 2; +const HUF_FLAGS_SUSPECT_UNCOMPRESSIBLE: c_int = 1 << 3; + +const ZSTD_LAZY: c_int = 4; +const ZSTD_BTULTRA: c_int = 8; + +const SET_BASIC: u32 = 0; +const SET_RLE: u32 = 1; +const SET_COMPRESSED: u32 = 2; +const SET_REPEAT: u32 = 3; + +/// ABI-compatible `ZSTD_hufCTables_t` from `zstd_compress_internal.h`. +/// +/// `HUF_CElt` is `size_t` in C, and `HUF_repeat` is the platform C enum ABI +/// (`int` for the supported C compilers). Keeping this type explicit is +/// necessary because C compression code passes it across the language +/// boundary for every compressed block. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct ZSTD_hufCTables_t { + pub CTable: [usize; HUF_CTABLE_SIZE_ST], + pub repeatMode: c_int, +} + +#[inline] +fn literal_header_size(src_size: usize) -> usize { + 1 + usize::from(src_size > 31) + usize::from(src_size > 4095) +} + +#[inline] +unsafe fn copy_bytes(dst: *mut u8, src: *const u8, size: usize) { + // C's ZSTD_memcpy() permits the zero-length call used by the empty + // literals path. Avoid forming a Rust copy from a potentially-null + // zero-length source while preserving the byte-for-byte result. + if size != 0 { + unsafe { ptr::copy_nonoverlapping(src, dst, size) }; + } +} + +#[inline] +unsafe fn copy_huf_tables(dst: *mut ZSTD_hufCTables_t, src: *const ZSTD_hufCTables_t) { + // Copy as bytes, not as a Rust struct: C's memcpy preserves the four tail + // padding bytes that this layout has on 64-bit targets. Some C callers + // copy or compare the complete entropy state, so leaving those bytes from + // `nextHuf` would not be ABI-equivalent even though the named fields match. + unsafe { + ptr::copy( + src.cast::(), + dst.cast::(), + size_of::(), + ) + }; +} + +#[inline] +fn all_bytes_identical(src: *const u8, src_size: usize) -> bool { + if src_size == 0 || src.is_null() { + return false; + } + let first = unsafe { *src }; + for index in 1..src_size { + if unsafe { *src.add(index) } != first { + return false; + } + } + true +} + +/// Writes an uncompressed literals section. +#[no_mangle] +pub unsafe extern "C" fn ZSTD_noCompressLiterals( + dst: *mut c_void, + dst_capacity: usize, + src: *const c_void, + src_size: usize, +) -> usize { + let fl_size = literal_header_size(src_size); + let needed = match src_size.checked_add(fl_size) { + Some(size) => size, + None => return ERROR(ZstdErrorCode::DstSizeTooSmall), + }; + if needed > dst_capacity { + return ERROR(ZstdErrorCode::DstSizeTooSmall); + } + + let output = dst.cast::(); + match fl_size { + 1 => unsafe { + *output = SET_BASIC.wrapping_add((src_size as u32).wrapping_shl(3)) as u8; + }, + 2 => unsafe { + MEM_writeLE16( + output.cast::(), + SET_BASIC + .wrapping_add(1 << 2) + .wrapping_add((src_size as u32).wrapping_shl(4)) as u16, + ); + }, + 3 => unsafe { + MEM_writeLE32( + output.cast::(), + SET_BASIC + .wrapping_add(3 << 2) + .wrapping_add((src_size as u32).wrapping_shl(4)), + ); + }, + _ => unreachable!("literal header size is always one to three bytes"), + } + unsafe { copy_bytes(output.add(fl_size), src.cast::(), src_size) }; + needed +} + +/// Writes an RLE literals section. +/// +/// Like the C entry point, callers must provide at least four output bytes and +/// a non-empty input consisting entirely of one byte. +#[no_mangle] +pub unsafe extern "C" fn ZSTD_compressRleLiteralsBlock( + dst: *mut c_void, + dst_capacity: usize, + src: *const c_void, + src_size: usize, +) -> usize { + debug_assert!(dst_capacity >= 4); + debug_assert!(all_bytes_identical(src.cast::(), src_size)); + + let fl_size = literal_header_size(src_size); + let output = dst.cast::(); + match fl_size { + 1 => unsafe { + *output = SET_RLE.wrapping_add((src_size as u32).wrapping_shl(3)) as u8; + }, + 2 => unsafe { + MEM_writeLE16( + output.cast::(), + SET_RLE + .wrapping_add(1 << 2) + .wrapping_add((src_size as u32).wrapping_shl(4)) as u16, + ); + }, + 3 => unsafe { + MEM_writeLE32( + output.cast::(), + SET_RLE + .wrapping_add(3 << 2) + .wrapping_add((src_size as u32).wrapping_shl(4)), + ); + }, + _ => unreachable!("literal header size is always one to three bytes"), + } + unsafe { *output.add(fl_size) = *src.cast::() }; + fl_size + 1 +} + +#[inline] +fn min_literals_to_compress(strategy: c_int, huf_repeat: c_int) -> usize { + // ZSTD_strategy is pre-validated by the C compression context. Clamp the + // calculated shift nevertheless so an invalid direct FFI call cannot turn + // into a Rust invalid shift; valid strategy values retain C's formula. + let shift = (9 - strategy).clamp(0, 3) as u32; + if huf_repeat == HUF_REPEAT_VALID { + 6 + } else { + 8usize << shift + } +} + +#[inline] +fn min_gain(src_size: usize, strategy: c_int) -> usize { + let min_log = if strategy >= ZSTD_BTULTRA { + strategy.saturating_sub(1) as u32 + } else { + 6 + }; + // A valid ZSTD_strategy is at most 9. The clamp only gives invalid direct + // FFI callers a defined result instead of allowing an invalid shift. + (src_size >> min_log.min(usize::BITS - 1)) + 2 +} + +/// Compresses a literals section with the previous Huffman table when useful. +/// +/// The entropy workspace is passed straight into the HUF compressor. Its +/// alignment and minimum size therefore retain the `HUF_compress*repeat()` C +/// contract; this wrapper does not allocate a replacement workspace. +#[allow(clippy::too_many_arguments)] +#[no_mangle] +pub unsafe extern "C" fn ZSTD_compressLiterals( + dst: *mut c_void, + dst_capacity: usize, + src: *const c_void, + src_size: usize, + entropy_workspace: *mut c_void, + entropy_workspace_size: usize, + prev_huf: *const ZSTD_hufCTables_t, + next_huf: *mut ZSTD_hufCTables_t, + strategy: c_int, + disable_literal_compression: c_int, + suspect_uncompressible: c_int, + bmi2: c_int, +) -> usize { + let lh_size = 3 + usize::from(src_size >= 1024) + usize::from(src_size >= 16 * 1024); + let output = dst.cast::(); + let mut single_stream = src_size < 256; + let mut h_type = SET_COMPRESSED; + + unsafe { copy_huf_tables(next_huf, prev_huf) }; + + if disable_literal_compression != 0 { + return unsafe { ZSTD_noCompressLiterals(dst, dst_capacity, src, src_size) }; + } + + let previous_repeat = unsafe { (*prev_huf).repeatMode }; + if src_size < min_literals_to_compress(strategy, previous_repeat) { + return unsafe { ZSTD_noCompressLiterals(dst, dst_capacity, src, src_size) }; + } + + if dst_capacity < lh_size + 1 { + return ERROR(ZstdErrorCode::DstSizeTooSmall); + } + + let mut repeat = previous_repeat; + let mut flags = 0; + if bmi2 != 0 { + flags |= HUF_FLAGS_BMI2; + } + if strategy < ZSTD_LAZY && src_size <= 1024 { + flags |= HUF_FLAGS_PREFER_REPEAT; + } + if strategy >= ZSTD_BTULTRA { + flags |= HUF_FLAGS_OPTIMAL_DEPTH; + } + if suspect_uncompressible != 0 { + flags |= HUF_FLAGS_SUSPECT_UNCOMPRESSIBLE; + } + + if repeat == HUF_REPEAT_VALID && lh_size == 3 { + single_stream = true; + } + + let c_lit_size = if single_stream { + unsafe { + HUF_compress1X_repeat( + output.add(lh_size).cast::(), + dst_capacity - lh_size, + src, + src_size, + HUF_SYMBOLVALUE_MAX, + LIT_HUF_LOG as u32, + entropy_workspace, + entropy_workspace_size, + (*next_huf).CTable.as_mut_ptr(), + &mut repeat, + flags, + ) + } + } else { + unsafe { + HUF_compress4X_repeat( + output.add(lh_size).cast::(), + dst_capacity - lh_size, + src, + src_size, + HUF_SYMBOLVALUE_MAX, + LIT_HUF_LOG as u32, + entropy_workspace, + entropy_workspace_size, + (*next_huf).CTable.as_mut_ptr(), + &mut repeat, + flags, + ) + } + }; + + if c_lit_size == 0 + || c_lit_size >= src_size.saturating_sub(min_gain(src_size, strategy)) + || ERR_isError(c_lit_size) + { + unsafe { copy_huf_tables(next_huf, prev_huf) }; + return unsafe { ZSTD_noCompressLiterals(dst, dst_capacity, src, src_size) }; + } + + if c_lit_size == 1 && (src_size >= 8 || all_bytes_identical(src.cast::(), src_size)) { + unsafe { copy_huf_tables(next_huf, prev_huf) }; + return unsafe { ZSTD_compressRleLiteralsBlock(dst, dst_capacity, src, src_size) }; + } + + if repeat != HUF_REPEAT_NONE { + h_type = SET_REPEAT; + } + if h_type == SET_COMPRESSED { + unsafe { (*next_huf).repeatMode = HUF_REPEAT_CHECK }; + } + + match lh_size { + 3 => { + debug_assert!(single_stream || src_size >= MIN_LITERALS_FOR_4_STREAMS); + let header = h_type + .wrapping_add(u32::from(!single_stream) << 2) + .wrapping_add((src_size as u32).wrapping_shl(4)) + .wrapping_add((c_lit_size as u32).wrapping_shl(14)); + unsafe { MEM_writeLE24(output.cast::(), header) }; + } + 4 => { + debug_assert!(src_size >= MIN_LITERALS_FOR_4_STREAMS); + let header = h_type + .wrapping_add(2 << 2) + .wrapping_add((src_size as u32).wrapping_shl(4)) + .wrapping_add((c_lit_size as u32).wrapping_shl(18)); + unsafe { MEM_writeLE32(output.cast::(), header) }; + } + 5 => { + debug_assert!(src_size >= MIN_LITERALS_FOR_4_STREAMS); + let header = h_type + .wrapping_add(3 << 2) + .wrapping_add((src_size as u32).wrapping_shl(4)) + .wrapping_add((c_lit_size as u32).wrapping_shl(22)); + unsafe { + MEM_writeLE32(output.cast::(), header); + *output.add(4) = (c_lit_size >> 10) as u8; + } + } + _ => unreachable!("literal header size is always three to five bytes"), + } + + lh_size + c_lit_size +} + +#[cfg(test)] +mod tests { + use super::*; + use std::mem::{align_of, size_of}; + + #[test] + fn huf_table_layout_matches_the_c_header() { + assert_eq!(align_of::(), align_of::()); + assert_eq!( + size_of::(), + HUF_CTABLE_SIZE_ST * size_of::() + size_of::(), + ); + } + + #[test] + fn raw_literals_headers_match_the_format() { + let source = [0xABu8; 4096]; + let mut output = [0u8; 4099]; + + let size = unsafe { + ZSTD_noCompressLiterals( + output.as_mut_ptr().cast(), + output.len(), + source.as_ptr().cast(), + 31, + ) + }; + assert_eq!(size, 32); + assert_eq!(output[0], 31 << 3); + assert_eq!(&output[1..size], &source[..31]); + + let size = unsafe { + ZSTD_noCompressLiterals( + output.as_mut_ptr().cast(), + output.len(), + source.as_ptr().cast(), + 32, + ) + }; + assert_eq!(size, 34); + assert_eq!(&output[..2], &[0x04, 0x02]); + assert_eq!(&output[2..size], &source[..32]); + + let size = unsafe { + ZSTD_noCompressLiterals( + output.as_mut_ptr().cast(), + output.len(), + source.as_ptr().cast(), + source.len(), + ) + }; + assert_eq!(size, 4099); + assert_eq!(&output[..3], &[0x0C, 0x00, 0x01]); + assert_eq!(&output[3..size], source.as_slice()); + + output.fill(0xA5); + let error = unsafe { + ZSTD_noCompressLiterals( + output.as_mut_ptr().cast(), + source.len() + 2, + source.as_ptr().cast(), + source.len(), + ) + }; + assert_eq!(error, ERROR(ZstdErrorCode::DstSizeTooSmall)); + assert!(output.iter().all(|&byte| byte == 0xA5)); + } + + #[test] + fn rle_literals_headers_match_the_format() { + let source = [0x5Au8; 4096]; + let mut output = [0u8; 4]; + + let size = unsafe { + ZSTD_compressRleLiteralsBlock( + output.as_mut_ptr().cast(), + output.len(), + source.as_ptr().cast(), + 31, + ) + }; + assert_eq!(size, 2); + assert_eq!(&output[..size], &[0xF9, 0x5A]); + + let size = unsafe { + ZSTD_compressRleLiteralsBlock( + output.as_mut_ptr().cast(), + output.len(), + source.as_ptr().cast(), + source.len(), + ) + }; + assert_eq!(size, 4); + assert_eq!(&output[..size], &[0x0D, 0x00, 0x01, 0x5A]); + } + + #[test] + fn direct_paths_copy_the_previous_huf_state() { + let mut previous = ZSTD_hufCTables_t { + CTable: [0; HUF_CTABLE_SIZE_ST], + repeatMode: HUF_REPEAT_VALID, + }; + previous.CTable[23] = 0x1234_5678; + let mut next = ZSTD_hufCTables_t { + CTable: [usize::MAX; HUF_CTABLE_SIZE_ST], + repeatMode: HUF_REPEAT_NONE, + }; + let source = [1u8, 2, 3]; + let mut output = [0u8; 8]; + + let size = unsafe { + ZSTD_compressLiterals( + output.as_mut_ptr().cast(), + output.len(), + source.as_ptr().cast(), + source.len(), + ptr::null_mut(), + 0, + &previous, + &mut next, + ZSTD_LAZY, + 0, + 0, + 0, + ) + }; + assert_eq!(size, 4); + assert_eq!(&output[..size], &[24, 1, 2, 3]); + assert_eq!(next.CTable[23], previous.CTable[23]); + assert_eq!(next.repeatMode, previous.repeatMode); + } + + #[test] + fn huffman_literals_use_the_c_table_layout() { + const HUF_WORKSPACE_SIZE: usize = (8 << 10) + 512; + + let mut source = [0u8; 1024]; + for (index, byte) in source.iter_mut().enumerate() { + *byte = (index % 4) as u8; + } + let previous = ZSTD_hufCTables_t { + CTable: [0; HUF_CTABLE_SIZE_ST], + repeatMode: HUF_REPEAT_NONE, + }; + let mut next = previous; + let mut output = [0u8; 2048]; + let mut workspace = [0u64; HUF_WORKSPACE_SIZE / size_of::()]; + + let size = unsafe { + ZSTD_compressLiterals( + output.as_mut_ptr().cast(), + output.len(), + source.as_ptr().cast(), + source.len(), + workspace.as_mut_ptr().cast(), + size_of::<[u64; HUF_WORKSPACE_SIZE / size_of::()]>(), + &previous, + &mut next, + 3, + 0, + 0, + 0, + ) + }; + + assert!(!ERR_isError(size)); + assert!(size < source.len()); + assert_eq!(output[0] & 3, SET_COMPRESSED as u8); + assert_eq!(next.repeatMode, HUF_REPEAT_CHECK); + } + + #[test] + fn min_literal_thresholds_match_strategies() { + assert_eq!(min_literals_to_compress(1, HUF_REPEAT_NONE), 64); + assert_eq!(min_literals_to_compress(6, HUF_REPEAT_NONE), 64); + assert_eq!(min_literals_to_compress(8, HUF_REPEAT_NONE), 16); + assert_eq!(min_literals_to_compress(9, HUF_REPEAT_NONE), 8); + assert_eq!(min_literals_to_compress(1, HUF_REPEAT_VALID), 6); + assert_eq!(min_gain(128, 1), 4); + assert_eq!(min_gain(128, ZSTD_BTULTRA), 3); + } +}