feat(compress): move dictionary entropy loading to Rust
Port ZSTD_dictNCountRepeat and ZSTD_loadCEntropy to a dedicated Rust compression module. Preserve the existing C-facing ZSTD_loadCEntropy symbol through a narrow shim while keeping dictionary content and compression-context ownership in C. Test Plan: - cargo test --manifest-path rust/Cargo.toml --no-default-features --features compression - cargo clippy --manifest-path rust/Cargo.toml --no-default-features --features compression --benches --tests - make -B -C lib -j2 lib - make -C tests -j2 test-invalidDictionaries - make -C tests -j2 test-zstream
This commit is contained in:
@@ -303,6 +303,8 @@ size_t ZSTD_rust_determineBlockSize(int mode, size_t blockSize, size_t remaining
|
||||
size_t ZSTD_rust_validateSequence(U32 offBase, U32 matchLength, U32 minMatch,
|
||||
size_t posInSrc, U32 windowLog, size_t dictSize,
|
||||
int useSequenceProducer);
|
||||
size_t ZSTD_rust_loadCEntropy(ZSTD_compressedBlockState_t* bs, void* workspace,
|
||||
const void* dict, size_t dictSize);
|
||||
size_t ZSTD_rust_transferSequencesWBlockDelim(
|
||||
SeqStore_t* seqStore, ZSTD_SequencePosition* seqPos,
|
||||
const ZSTD_Sequence* inSeqs, size_t inSeqsSize,
|
||||
@@ -3566,113 +3568,12 @@ ZSTD_loadDictionaryContent(ZSTD_MatchState_t* ms,
|
||||
}
|
||||
|
||||
|
||||
/* Dictionaries that assign zero probability to symbols that show up causes problems
|
||||
* when FSE encoding. Mark dictionaries with zero probability symbols as FSE_repeat_check
|
||||
* and only dictionaries with 100% valid symbols can be assumed valid.
|
||||
*/
|
||||
static FSE_repeat ZSTD_dictNCountRepeat(short* normalizedCounter, unsigned dictMaxSymbolValue, unsigned maxSymbolValue)
|
||||
{
|
||||
U32 s;
|
||||
if (dictMaxSymbolValue < maxSymbolValue) {
|
||||
return FSE_repeat_check;
|
||||
}
|
||||
for (s = 0; s <= maxSymbolValue; ++s) {
|
||||
if (normalizedCounter[s] == 0) {
|
||||
return FSE_repeat_check;
|
||||
}
|
||||
}
|
||||
return FSE_repeat_valid;
|
||||
}
|
||||
|
||||
/* Dictionary entropy-header loading lives in Rust; C keeps the public internal
|
||||
* symbol and the dictionary-content orchestration around it. */
|
||||
size_t ZSTD_loadCEntropy(ZSTD_compressedBlockState_t* bs, void* workspace,
|
||||
const void* const dict, size_t dictSize)
|
||||
{
|
||||
short offcodeNCount[MaxOff+1];
|
||||
unsigned offcodeMaxValue = MaxOff;
|
||||
const BYTE* dictPtr = (const BYTE*)dict; /* skip magic num and dict ID */
|
||||
const BYTE* const dictEnd = dictPtr + dictSize;
|
||||
dictPtr += 8;
|
||||
bs->entropy.huf.repeatMode = HUF_repeat_check;
|
||||
|
||||
{ unsigned maxSymbolValue = 255;
|
||||
unsigned hasZeroWeights = 1;
|
||||
size_t const hufHeaderSize = HUF_readCTable((HUF_CElt*)bs->entropy.huf.CTable, &maxSymbolValue, dictPtr,
|
||||
(size_t)(dictEnd-dictPtr), &hasZeroWeights);
|
||||
|
||||
/* We only set the loaded table as valid if it contains all non-zero
|
||||
* weights. Otherwise, we set it to check */
|
||||
if (!hasZeroWeights && maxSymbolValue == 255)
|
||||
bs->entropy.huf.repeatMode = HUF_repeat_valid;
|
||||
|
||||
RETURN_ERROR_IF(HUF_isError(hufHeaderSize), dictionary_corrupted, "");
|
||||
dictPtr += hufHeaderSize;
|
||||
}
|
||||
|
||||
{ unsigned offcodeLog;
|
||||
size_t const offcodeHeaderSize = FSE_readNCount(offcodeNCount, &offcodeMaxValue, &offcodeLog, dictPtr, (size_t)(dictEnd-dictPtr));
|
||||
RETURN_ERROR_IF(FSE_isError(offcodeHeaderSize), dictionary_corrupted, "");
|
||||
RETURN_ERROR_IF(offcodeLog > OffFSELog, dictionary_corrupted, "");
|
||||
/* fill all offset symbols to avoid garbage at end of table */
|
||||
RETURN_ERROR_IF(FSE_isError(FSE_buildCTable_wksp(
|
||||
bs->entropy.fse.offcodeCTable,
|
||||
offcodeNCount, MaxOff, offcodeLog,
|
||||
workspace, HUF_WORKSPACE_SIZE)),
|
||||
dictionary_corrupted, "");
|
||||
/* Defer checking offcodeMaxValue because we need to know the size of the dictionary content */
|
||||
dictPtr += offcodeHeaderSize;
|
||||
}
|
||||
|
||||
{ short matchlengthNCount[MaxML+1];
|
||||
unsigned matchlengthMaxValue = MaxML, matchlengthLog;
|
||||
size_t const matchlengthHeaderSize = FSE_readNCount(matchlengthNCount, &matchlengthMaxValue, &matchlengthLog, dictPtr, (size_t)(dictEnd-dictPtr));
|
||||
RETURN_ERROR_IF(FSE_isError(matchlengthHeaderSize), dictionary_corrupted, "");
|
||||
RETURN_ERROR_IF(matchlengthLog > MLFSELog, dictionary_corrupted, "");
|
||||
RETURN_ERROR_IF(FSE_isError(FSE_buildCTable_wksp(
|
||||
bs->entropy.fse.matchlengthCTable,
|
||||
matchlengthNCount, matchlengthMaxValue, matchlengthLog,
|
||||
workspace, HUF_WORKSPACE_SIZE)),
|
||||
dictionary_corrupted, "");
|
||||
bs->entropy.fse.matchlength_repeatMode = ZSTD_dictNCountRepeat(matchlengthNCount, matchlengthMaxValue, MaxML);
|
||||
dictPtr += matchlengthHeaderSize;
|
||||
}
|
||||
|
||||
{ short litlengthNCount[MaxLL+1];
|
||||
unsigned litlengthMaxValue = MaxLL, litlengthLog;
|
||||
size_t const litlengthHeaderSize = FSE_readNCount(litlengthNCount, &litlengthMaxValue, &litlengthLog, dictPtr, (size_t)(dictEnd-dictPtr));
|
||||
RETURN_ERROR_IF(FSE_isError(litlengthHeaderSize), dictionary_corrupted, "");
|
||||
RETURN_ERROR_IF(litlengthLog > LLFSELog, dictionary_corrupted, "");
|
||||
RETURN_ERROR_IF(FSE_isError(FSE_buildCTable_wksp(
|
||||
bs->entropy.fse.litlengthCTable,
|
||||
litlengthNCount, litlengthMaxValue, litlengthLog,
|
||||
workspace, HUF_WORKSPACE_SIZE)),
|
||||
dictionary_corrupted, "");
|
||||
bs->entropy.fse.litlength_repeatMode = ZSTD_dictNCountRepeat(litlengthNCount, litlengthMaxValue, MaxLL);
|
||||
dictPtr += litlengthHeaderSize;
|
||||
}
|
||||
|
||||
RETURN_ERROR_IF(dictPtr+12 > dictEnd, dictionary_corrupted, "");
|
||||
bs->rep[0] = MEM_readLE32(dictPtr+0);
|
||||
bs->rep[1] = MEM_readLE32(dictPtr+4);
|
||||
bs->rep[2] = MEM_readLE32(dictPtr+8);
|
||||
dictPtr += 12;
|
||||
|
||||
{ size_t const dictContentSize = (size_t)(dictEnd - dictPtr);
|
||||
U32 offcodeMax = MaxOff;
|
||||
if (dictContentSize <= ((U32)-1) - 128 KB) {
|
||||
U32 const maxOffset = (U32)dictContentSize + 128 KB; /* The maximum offset that must be supported */
|
||||
offcodeMax = ZSTD_highbit32(maxOffset); /* Calculate minimum offset code required to represent maxOffset */
|
||||
}
|
||||
/* All offset values <= dictContentSize + 128 KB must be representable for a valid table */
|
||||
bs->entropy.fse.offcode_repeatMode = ZSTD_dictNCountRepeat(offcodeNCount, offcodeMaxValue, MIN(offcodeMax, MaxOff));
|
||||
|
||||
/* All repCodes must be <= dictContentSize and != 0 */
|
||||
{ U32 u;
|
||||
for (u=0; u<3; u++) {
|
||||
RETURN_ERROR_IF(bs->rep[u] == 0, dictionary_corrupted, "");
|
||||
RETURN_ERROR_IF(bs->rep[u] > dictContentSize, dictionary_corrupted, "");
|
||||
} } }
|
||||
|
||||
return (size_t)(dictPtr - (const BYTE*)dict);
|
||||
return ZSTD_rust_loadCEntropy(bs, workspace, dict, dictSize);
|
||||
}
|
||||
|
||||
/* Dictionary format :
|
||||
|
||||
@@ -37,6 +37,8 @@ pub mod zstd_compress;
|
||||
#[cfg(feature = "compression")]
|
||||
pub mod zstd_compress_api;
|
||||
#[cfg(feature = "compression")]
|
||||
pub mod zstd_compress_dictionary;
|
||||
#[cfg(feature = "compression")]
|
||||
pub mod zstd_compress_frame;
|
||||
#[cfg(feature = "compression")]
|
||||
pub mod zstd_compress_literals;
|
||||
|
||||
@@ -0,0 +1,478 @@
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
//! Dictionary entropy-header loading.
|
||||
//!
|
||||
//! This is the Rust leaf for `ZSTD_dictNCountRepeat()` and
|
||||
//! `ZSTD_loadCEntropy()` in `lib/compress/zstd_compress.c`. C retains the
|
||||
//! original symbol through a thin compatibility shim and keeps dictionary
|
||||
//! content loading and compression-context ownership on its side.
|
||||
|
||||
use crate::bits::ZSTD_highbit32;
|
||||
use crate::common::{LL_FSE_LOG, MAX_LL, MAX_ML, MAX_OFF, ML_FSE_LOG, OFF_FSE_LOG};
|
||||
use crate::entropy_common::FSE_readNCount;
|
||||
use crate::errors::{ERR_isError, ZstdErrorCode, ERROR};
|
||||
use crate::fse_compress::FSE_buildCTable_wksp;
|
||||
use crate::huf_compress::HUF_readCTable;
|
||||
use crate::zstd_compress_stats::ZSTD_compressedBlockState_t;
|
||||
use std::ffi::c_void;
|
||||
use std::os::raw::{c_int, c_short, c_uint};
|
||||
use std::slice;
|
||||
|
||||
const HUF_REPEAT_CHECK: c_int = 1;
|
||||
const HUF_REPEAT_VALID: c_int = 2;
|
||||
const FSE_REPEAT_CHECK: c_int = 1;
|
||||
const FSE_REPEAT_VALID: c_int = 2;
|
||||
const HUF_WORKSPACE_SIZE: usize = (8 << 10) + 512;
|
||||
const DICTIONARY_ID_AND_MAGIC_SIZE: usize = 8;
|
||||
const REPCODE_SECTION_SIZE: usize = 12;
|
||||
|
||||
#[inline]
|
||||
fn dictionary_corrupted() -> usize {
|
||||
ERROR(ZstdErrorCode::DictionaryCorrupted)
|
||||
}
|
||||
|
||||
/// Matches C's `ZSTD_dictNCountRepeat()`.
|
||||
#[inline]
|
||||
fn dict_n_count_repeat(
|
||||
normalized_counter: &[c_short],
|
||||
dict_max_symbol_value: usize,
|
||||
max_symbol_value: usize,
|
||||
) -> c_int {
|
||||
if dict_max_symbol_value < max_symbol_value {
|
||||
return FSE_REPEAT_CHECK;
|
||||
}
|
||||
if normalized_counter.len() <= max_symbol_value
|
||||
|| normalized_counter[..=max_symbol_value].contains(&0)
|
||||
{
|
||||
return FSE_REPEAT_CHECK;
|
||||
}
|
||||
FSE_REPEAT_VALID
|
||||
}
|
||||
|
||||
/// Reads and builds one dictionary FSE table, advancing `offset` on success.
|
||||
///
|
||||
/// C supplies `max_symbol_value` from `FSE_readNCount()` to the table builder
|
||||
/// for the match-length and literal-length tables, but deliberately builds the
|
||||
/// offset table with `MaxOff` so the unused tail is initialized. The caller
|
||||
/// passes that distinction through `table_max_symbol_value`.
|
||||
unsafe fn read_and_build_fse_table(
|
||||
dictionary: &[u8],
|
||||
offset: &mut usize,
|
||||
normalized_counter: &mut [c_short],
|
||||
initial_max_symbol_value: c_uint,
|
||||
max_table_log: c_uint,
|
||||
table: *mut c_uint,
|
||||
table_max_symbol_value: c_uint,
|
||||
workspace: *mut c_void,
|
||||
) -> Result<c_uint, ()> {
|
||||
let input = &dictionary[*offset..];
|
||||
let mut max_symbol_value = initial_max_symbol_value;
|
||||
let mut table_log = 0u32;
|
||||
let header_size = unsafe {
|
||||
FSE_readNCount(
|
||||
normalized_counter.as_mut_ptr(),
|
||||
&mut max_symbol_value,
|
||||
&mut table_log,
|
||||
input.as_ptr().cast(),
|
||||
input.len(),
|
||||
)
|
||||
};
|
||||
if ERR_isError(header_size)
|
||||
|| header_size > input.len()
|
||||
|| max_symbol_value as usize >= normalized_counter.len()
|
||||
|| table_log > max_table_log
|
||||
{
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let build_result = unsafe {
|
||||
FSE_buildCTable_wksp(
|
||||
table,
|
||||
normalized_counter.as_ptr(),
|
||||
table_max_symbol_value,
|
||||
table_log,
|
||||
workspace,
|
||||
HUF_WORKSPACE_SIZE,
|
||||
)
|
||||
};
|
||||
if ERR_isError(build_result) {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
*offset = offset.checked_add(header_size).ok_or(())?;
|
||||
Ok(max_symbol_value)
|
||||
}
|
||||
|
||||
/// Rust implementation of C `ZSTD_loadCEntropy()`.
|
||||
///
|
||||
/// The input is assumed to have the dictionary magic and ID in its first eight
|
||||
/// bytes, matching the C function's documented precondition. Malformed or
|
||||
/// truncated entropy headers are converted to `ZSTD_error_dictionaryCorrupted`
|
||||
/// just as the C `RETURN_ERROR_IF(..., dictionary_corrupted, ...)` paths do.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ZSTD_rust_loadCEntropy(
|
||||
bs: *mut ZSTD_compressedBlockState_t,
|
||||
workspace: *mut c_void,
|
||||
dict: *const c_void,
|
||||
dict_size: usize,
|
||||
) -> usize {
|
||||
if bs.is_null()
|
||||
|| dict.is_null()
|
||||
|| workspace.is_null()
|
||||
|| dict_size < DICTIONARY_ID_AND_MAGIC_SIZE
|
||||
{
|
||||
return dictionary_corrupted();
|
||||
}
|
||||
|
||||
let dictionary = unsafe { slice::from_raw_parts(dict.cast::<u8>(), dict_size) };
|
||||
let mut offset = DICTIONARY_ID_AND_MAGIC_SIZE;
|
||||
|
||||
unsafe {
|
||||
(*bs).entropy.huf.repeatMode = HUF_REPEAT_CHECK;
|
||||
}
|
||||
|
||||
let mut huf_max_symbol_value = 255u32;
|
||||
let mut huf_has_zero_weights = 1u32;
|
||||
let huf_header_size = unsafe {
|
||||
HUF_readCTable(
|
||||
(*bs).entropy.huf.CTable.as_mut_ptr(),
|
||||
&mut huf_max_symbol_value,
|
||||
dictionary[offset..].as_ptr().cast(),
|
||||
dictionary.len() - offset,
|
||||
&mut huf_has_zero_weights,
|
||||
)
|
||||
};
|
||||
if ERR_isError(huf_header_size) || huf_header_size > dictionary.len().saturating_sub(offset) {
|
||||
return dictionary_corrupted();
|
||||
}
|
||||
if huf_has_zero_weights == 0 && huf_max_symbol_value == 255 {
|
||||
unsafe {
|
||||
(*bs).entropy.huf.repeatMode = HUF_REPEAT_VALID;
|
||||
}
|
||||
}
|
||||
offset = match offset.checked_add(huf_header_size) {
|
||||
Some(value) => value,
|
||||
None => return dictionary_corrupted(),
|
||||
};
|
||||
|
||||
let mut offcode_ncount = [0i16; MAX_OFF + 1];
|
||||
let offcode_max_symbol_value = MAX_OFF as c_uint;
|
||||
let offcode_header_max = match unsafe {
|
||||
read_and_build_fse_table(
|
||||
dictionary,
|
||||
&mut offset,
|
||||
&mut offcode_ncount,
|
||||
offcode_max_symbol_value,
|
||||
OFF_FSE_LOG as c_uint,
|
||||
(*bs).entropy.fse.offcodeCTable.as_mut_ptr(),
|
||||
MAX_OFF as c_uint,
|
||||
workspace,
|
||||
)
|
||||
} {
|
||||
Ok(value) => value,
|
||||
Err(()) => return dictionary_corrupted(),
|
||||
};
|
||||
|
||||
let mut matchlength_ncount = [0i16; MAX_ML + 1];
|
||||
let matchlength_max_symbol_value = MAX_ML as c_uint;
|
||||
let matchlength_header_max = match unsafe {
|
||||
read_and_build_fse_table(
|
||||
dictionary,
|
||||
&mut offset,
|
||||
&mut matchlength_ncount,
|
||||
matchlength_max_symbol_value,
|
||||
ML_FSE_LOG as c_uint,
|
||||
(*bs).entropy.fse.matchlengthCTable.as_mut_ptr(),
|
||||
matchlength_max_symbol_value,
|
||||
workspace,
|
||||
)
|
||||
} {
|
||||
Ok(value) => value,
|
||||
Err(()) => return dictionary_corrupted(),
|
||||
};
|
||||
unsafe {
|
||||
(*bs).entropy.fse.matchlength_repeatMode =
|
||||
dict_n_count_repeat(&matchlength_ncount, matchlength_header_max as usize, MAX_ML);
|
||||
}
|
||||
|
||||
let mut litlength_ncount = [0i16; MAX_LL + 1];
|
||||
let litlength_max_symbol_value = MAX_LL as c_uint;
|
||||
let litlength_header_max = match unsafe {
|
||||
read_and_build_fse_table(
|
||||
dictionary,
|
||||
&mut offset,
|
||||
&mut litlength_ncount,
|
||||
litlength_max_symbol_value,
|
||||
LL_FSE_LOG as c_uint,
|
||||
(*bs).entropy.fse.litlengthCTable.as_mut_ptr(),
|
||||
litlength_max_symbol_value,
|
||||
workspace,
|
||||
)
|
||||
} {
|
||||
Ok(value) => value,
|
||||
Err(()) => return dictionary_corrupted(),
|
||||
};
|
||||
unsafe {
|
||||
(*bs).entropy.fse.litlength_repeatMode =
|
||||
dict_n_count_repeat(&litlength_ncount, litlength_header_max as usize, MAX_LL);
|
||||
}
|
||||
|
||||
if dictionary.len().saturating_sub(offset) < REPCODE_SECTION_SIZE {
|
||||
return dictionary_corrupted();
|
||||
}
|
||||
unsafe {
|
||||
(*bs).rep[0] = u32::from_le_bytes(dictionary[offset..offset + 4].try_into().unwrap());
|
||||
(*bs).rep[1] = u32::from_le_bytes(dictionary[offset + 4..offset + 8].try_into().unwrap());
|
||||
(*bs).rep[2] = u32::from_le_bytes(dictionary[offset + 8..offset + 12].try_into().unwrap());
|
||||
}
|
||||
offset += REPCODE_SECTION_SIZE;
|
||||
|
||||
let dict_content_size = dictionary.len() - offset;
|
||||
let offcode_max = if dict_content_size <= (u32::MAX as usize) - (128 << 10) {
|
||||
let max_offset = (dict_content_size + (128 << 10)) as u32;
|
||||
ZSTD_highbit32(max_offset) as usize
|
||||
} else {
|
||||
MAX_OFF
|
||||
};
|
||||
let required_offcode_max = offcode_max.min(MAX_OFF);
|
||||
let offcode_repeat_mode = dict_n_count_repeat(
|
||||
&offcode_ncount,
|
||||
offcode_header_max as usize,
|
||||
required_offcode_max,
|
||||
);
|
||||
unsafe {
|
||||
(*bs).entropy.fse.offcode_repeatMode = offcode_repeat_mode;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
for &rep in &(*bs).rep {
|
||||
if rep == 0 || rep as usize > dict_content_size {
|
||||
return dictionary_corrupted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
offset
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::errors::ERR_getErrorCode;
|
||||
use crate::fse_compress::{FSE_normalizeCount, FSE_writeNCount};
|
||||
use crate::huf_compress::{HUF_buildCTable_wksp, HUF_writeCTable_wksp};
|
||||
use crate::mem::MEM_writeLE32;
|
||||
use std::mem::{size_of, MaybeUninit};
|
||||
|
||||
const DICT_CONTENT_SIZE: usize = 16;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum ZeroWeight {
|
||||
None,
|
||||
Huffman,
|
||||
MatchLength,
|
||||
LiteralLength,
|
||||
Offset,
|
||||
}
|
||||
|
||||
fn assert_dictionary_corrupted(result: usize) {
|
||||
assert!(ERR_isError(result));
|
||||
assert_eq!(
|
||||
ERR_getErrorCode(result),
|
||||
ZstdErrorCode::DictionaryCorrupted as i32
|
||||
);
|
||||
}
|
||||
|
||||
unsafe fn write_fse_header(
|
||||
dictionary: &mut [u8],
|
||||
offset: &mut usize,
|
||||
max_symbol_value: usize,
|
||||
table_log: u32,
|
||||
zero_weight: bool,
|
||||
) {
|
||||
let mut counts = vec![1u32; max_symbol_value + 1];
|
||||
if zero_weight {
|
||||
counts[0] = 0;
|
||||
}
|
||||
let total = counts.iter().map(|&count| count as usize).sum();
|
||||
let mut normalized = vec![0i16; max_symbol_value + 1];
|
||||
let normalized_log = FSE_normalizeCount(
|
||||
normalized.as_mut_ptr(),
|
||||
table_log,
|
||||
counts.as_ptr(),
|
||||
total,
|
||||
max_symbol_value as c_uint,
|
||||
1,
|
||||
);
|
||||
assert!(!ERR_isError(normalized_log));
|
||||
let header_size = FSE_writeNCount(
|
||||
dictionary.as_mut_ptr().add(*offset).cast(),
|
||||
dictionary.len() - *offset,
|
||||
normalized.as_ptr(),
|
||||
max_symbol_value as c_uint,
|
||||
normalized_log as c_uint,
|
||||
);
|
||||
assert!(!ERR_isError(header_size));
|
||||
*offset += header_size;
|
||||
}
|
||||
|
||||
fn make_dictionary(zero_weight: ZeroWeight) -> Vec<u8> {
|
||||
let mut dictionary = vec![0u8; 2048];
|
||||
unsafe {
|
||||
MEM_writeLE32(dictionary.as_mut_ptr().cast(), 0xEC30_A437);
|
||||
MEM_writeLE32(dictionary.as_mut_ptr().add(4).cast(), 1234);
|
||||
}
|
||||
let mut offset = DICTIONARY_ID_AND_MAGIC_SIZE;
|
||||
|
||||
let mut huf_counts = std::array::from_fn::<_, 256, _>(|symbol| (symbol + 1) as u32);
|
||||
if matches!(zero_weight, ZeroWeight::Huffman) {
|
||||
huf_counts[0] = 0;
|
||||
}
|
||||
let mut huf_table = [0usize; 257];
|
||||
let mut workspace = [0u32; HUF_WORKSPACE_SIZE / size_of::<u32>()];
|
||||
let huff_log = unsafe {
|
||||
HUF_buildCTable_wksp(
|
||||
huf_table.as_mut_ptr(),
|
||||
huf_counts.as_ptr(),
|
||||
255,
|
||||
11,
|
||||
workspace.as_mut_ptr().cast(),
|
||||
size_of::<[u32; HUF_WORKSPACE_SIZE / size_of::<u32>()]>(),
|
||||
)
|
||||
};
|
||||
assert!(!ERR_isError(huff_log));
|
||||
let huf_header_size = unsafe {
|
||||
HUF_writeCTable_wksp(
|
||||
dictionary.as_mut_ptr().add(offset).cast(),
|
||||
dictionary.len() - offset,
|
||||
huf_table.as_ptr(),
|
||||
255,
|
||||
huff_log as c_uint,
|
||||
workspace.as_mut_ptr().cast(),
|
||||
size_of::<[u32; HUF_WORKSPACE_SIZE / size_of::<u32>()]>(),
|
||||
)
|
||||
};
|
||||
assert!(!ERR_isError(huf_header_size));
|
||||
offset += huf_header_size;
|
||||
|
||||
unsafe {
|
||||
write_fse_header(
|
||||
&mut dictionary,
|
||||
&mut offset,
|
||||
MAX_OFF,
|
||||
OFF_FSE_LOG as u32,
|
||||
matches!(zero_weight, ZeroWeight::Offset),
|
||||
);
|
||||
write_fse_header(
|
||||
&mut dictionary,
|
||||
&mut offset,
|
||||
MAX_ML,
|
||||
ML_FSE_LOG as u32,
|
||||
matches!(zero_weight, ZeroWeight::MatchLength),
|
||||
);
|
||||
write_fse_header(
|
||||
&mut dictionary,
|
||||
&mut offset,
|
||||
MAX_LL,
|
||||
LL_FSE_LOG as u32,
|
||||
matches!(zero_weight, ZeroWeight::LiteralLength),
|
||||
);
|
||||
MEM_writeLE32(dictionary.as_mut_ptr().add(offset).cast(), 1);
|
||||
MEM_writeLE32(dictionary.as_mut_ptr().add(offset + 4).cast(), 4);
|
||||
MEM_writeLE32(dictionary.as_mut_ptr().add(offset + 8).cast(), 8);
|
||||
}
|
||||
offset += REPCODE_SECTION_SIZE;
|
||||
dictionary[offset..offset + DICT_CONTENT_SIZE].fill(0xA5);
|
||||
offset += DICT_CONTENT_SIZE;
|
||||
dictionary.truncate(offset);
|
||||
dictionary
|
||||
}
|
||||
|
||||
fn load(dictionary: &[u8]) -> (usize, ZSTD_compressedBlockState_t) {
|
||||
let mut state =
|
||||
unsafe { MaybeUninit::<ZSTD_compressedBlockState_t>::zeroed().assume_init() };
|
||||
let mut workspace = [0u32; HUF_WORKSPACE_SIZE / size_of::<u32>()];
|
||||
let result = unsafe {
|
||||
ZSTD_rust_loadCEntropy(
|
||||
&mut state,
|
||||
workspace.as_mut_ptr().cast(),
|
||||
dictionary.as_ptr().cast(),
|
||||
dictionary.len(),
|
||||
)
|
||||
};
|
||||
(result, state)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_dictionary_loads_entropy_and_marks_repeat_tables_valid() {
|
||||
let dictionary = make_dictionary(ZeroWeight::None);
|
||||
let (header_size, state) = load(&dictionary);
|
||||
assert_eq!(header_size, dictionary.len() - DICT_CONTENT_SIZE);
|
||||
assert_eq!(state.rep, [1, 4, 8]);
|
||||
assert_eq!(state.entropy.huf.repeatMode, HUF_REPEAT_VALID);
|
||||
assert_eq!(state.entropy.fse.offcode_repeatMode, FSE_REPEAT_VALID);
|
||||
assert_eq!(state.entropy.fse.matchlength_repeatMode, FSE_REPEAT_VALID);
|
||||
assert_eq!(state.entropy.fse.litlength_repeatMode, FSE_REPEAT_VALID);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_weights_select_check_repeat_modes() {
|
||||
for zero_weight in [
|
||||
ZeroWeight::Huffman,
|
||||
ZeroWeight::Offset,
|
||||
ZeroWeight::MatchLength,
|
||||
ZeroWeight::LiteralLength,
|
||||
] {
|
||||
let dictionary = make_dictionary(zero_weight);
|
||||
let (header_size, state) = load(&dictionary);
|
||||
assert_eq!(header_size, dictionary.len() - DICT_CONTENT_SIZE);
|
||||
match zero_weight {
|
||||
ZeroWeight::Huffman => {
|
||||
assert_eq!(state.entropy.huf.repeatMode, HUF_REPEAT_CHECK)
|
||||
}
|
||||
ZeroWeight::Offset => {
|
||||
assert_eq!(state.entropy.fse.offcode_repeatMode, FSE_REPEAT_CHECK)
|
||||
}
|
||||
ZeroWeight::MatchLength => {
|
||||
assert_eq!(state.entropy.fse.matchlength_repeatMode, FSE_REPEAT_CHECK)
|
||||
}
|
||||
ZeroWeight::LiteralLength => {
|
||||
assert_eq!(state.entropy.fse.litlength_repeatMode, FSE_REPEAT_CHECK)
|
||||
}
|
||||
ZeroWeight::None => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn truncated_entropy_headers_are_rejected() {
|
||||
let dictionary = make_dictionary(ZeroWeight::None);
|
||||
let (header_size, _) = load(&dictionary);
|
||||
for truncated_size in [0, 7, 8, header_size - 1] {
|
||||
let truncated = &dictionary[..truncated_size];
|
||||
let (result, _) = load(truncated);
|
||||
assert_dictionary_corrupted(result);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn corrupt_repcode_header_is_rejected() {
|
||||
let mut dictionary = make_dictionary(ZeroWeight::None);
|
||||
let (header_size, _) = load(&dictionary);
|
||||
let repcode_offset = header_size - REPCODE_SECTION_SIZE;
|
||||
dictionary[repcode_offset..repcode_offset + 4].fill(0);
|
||||
let (result, _) = load(&dictionary);
|
||||
assert_dictionary_corrupted(result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dict_n_count_repeat_requires_coverage_and_nonzero_weights() {
|
||||
let mut normalized = [1i16; 4];
|
||||
assert_eq!(dict_n_count_repeat(&normalized, 3, 3), FSE_REPEAT_VALID);
|
||||
normalized[2] = 0;
|
||||
assert_eq!(dict_n_count_repeat(&normalized, 3, 3), FSE_REPEAT_CHECK);
|
||||
assert_eq!(dict_n_count_repeat(&normalized, 2, 2), FSE_REPEAT_CHECK);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user