use crate::common::bits::{ZSTD_countTrailingZeros32, ZSTD_highbit32};
use crate::common::error::{ErrorCode, ERROR};
use crate::common::mem::MEM_readLE32;
pub const FSE_VERSION_NUMBER: u32 = 900;
pub use crate::common::fse_decompress::{
FSE_MAX_SYMBOL_VALUE, FSE_MIN_TABLELOG, FSE_TABLELOG_ABSOLUTE_MAX,
};
#[inline]
pub const fn FSE_DTABLE_SIZE_U32(maxTableLog: u32) -> usize {
1 + (1usize << maxTableLog)
}
#[inline]
pub const fn FSE_DTABLE_SIZE(maxTableLog: u32) -> usize {
FSE_DTABLE_SIZE_U32(maxTableLog) * 4
}
#[inline]
pub const fn FSE_BUILD_DTABLE_WKSP_SIZE(maxTableLog: u32, maxSymbolValue: u32) -> usize {
2 * (maxSymbolValue as usize + 1) + (1usize << maxTableLog) + 8
}
#[inline]
pub const fn FSE_BUILD_DTABLE_WKSP_SIZE_U32(maxTableLog: u32, maxSymbolValue: u32) -> usize {
FSE_BUILD_DTABLE_WKSP_SIZE(maxTableLog, maxSymbolValue).div_ceil(4)
}
#[inline]
pub const fn FSE_DECOMPRESS_WKSP_SIZE_U32(maxTableLog: u32, maxSymbolValue: u32) -> usize {
FSE_DTABLE_SIZE_U32(maxTableLog)
+ 1
+ FSE_BUILD_DTABLE_WKSP_SIZE_U32(maxTableLog, maxSymbolValue)
+ ((FSE_MAX_SYMBOL_VALUE + 1) as usize) / 2
+ 1
}
#[inline]
pub const fn FSE_DECOMPRESS_WKSP_SIZE(maxTableLog: u32, maxSymbolValue: u32) -> usize {
FSE_DECOMPRESS_WKSP_SIZE_U32(maxTableLog, maxSymbolValue) * 4
}
pub use crate::decompress::huf_decompress::{
HUF_flags_bmi2, HUF_TABLELOG_ABSOLUTEMAX, HUF_TABLELOG_MAX,
};
pub const HUF_READ_STATS_WORKSPACE_SIZE_U32: usize =
FSE_DECOMPRESS_WKSP_SIZE_U32(6, HUF_TABLELOG_MAX - 1);
pub const HUF_READ_STATS_WORKSPACE_SIZE: usize = HUF_READ_STATS_WORKSPACE_SIZE_U32 * 4;
pub fn FSE_versionNumber() -> u32 {
FSE_VERSION_NUMBER
}
pub fn FSE_isError(code: usize) -> u32 {
crate::common::error::ERR_isError(code) as u32
}
pub fn FSE_getErrorName(code: usize) -> &'static str {
crate::common::error::ERR_getErrorName(code)
}
pub fn HUF_isError(code: usize) -> u32 {
crate::common::error::ERR_isError(code) as u32
}
pub fn HUF_getErrorName(code: usize) -> &'static str {
crate::common::error::ERR_getErrorName(code)
}
pub fn FSE_readNCount(
normalizedCounter: &mut [i16],
maxSVPtr: &mut u32,
tableLogPtr: &mut u32,
src: &[u8],
) -> usize {
FSE_readNCount_body(normalizedCounter, maxSVPtr, tableLogPtr, src, true)
}
pub(crate) fn FSE_readNCount_no_clear(
normalizedCounter: &mut [i16],
maxSVPtr: &mut u32,
tableLogPtr: &mut u32,
src: &[u8],
) -> usize {
FSE_readNCount_body(normalizedCounter, maxSVPtr, tableLogPtr, src, false)
}
fn FSE_readNCount_body(
normalizedCounter: &mut [i16],
maxSVPtr: &mut u32,
tableLogPtr: &mut u32,
src: &[u8],
clear_counts: bool,
) -> usize {
let hbSize = src.len();
if hbSize < 8 {
let mut buffer = [0u8; 8];
buffer[..hbSize].copy_from_slice(src);
let countSize = FSE_readNCount_body(
normalizedCounter,
maxSVPtr,
tableLogPtr,
&buffer,
clear_counts,
);
if crate::common::error::ERR_isError(countSize) {
return countSize;
}
if countSize > hbSize {
return ERROR(ErrorCode::CorruptionDetected);
}
return countSize;
}
let iend = hbSize;
let mut ip: usize = 0;
let mut charnum: u32 = 0;
let maxSV1 = (*maxSVPtr).wrapping_add(1);
let mut previous0 = false;
if normalizedCounter.len() < maxSV1 as usize {
return ERROR(ErrorCode::MaxSymbolValueTooSmall);
}
if clear_counts {
normalizedCounter[..maxSV1 as usize].fill(0);
}
let mut bitStream: u32 = MEM_readLE32(&src[ip..]);
let mut nbBits = ((bitStream & 0xF) + FSE_MIN_TABLELOG) as i32;
if nbBits > FSE_TABLELOG_ABSOLUTE_MAX as i32 {
return ERROR(ErrorCode::TableLogTooLarge);
}
bitStream >>= 4;
let mut bitCount: i32 = 4;
*tableLogPtr = nbBits as u32;
let mut remaining: i32 = (1 << nbBits) + 1;
let mut threshold: i32 = 1 << nbBits;
nbBits += 1;
loop {
if previous0 {
let mut repeats = (ZSTD_countTrailingZeros32(!bitStream | 0x8000_0000) >> 1) as i32;
while repeats >= 12 {
charnum += 3 * 12;
if ip <= iend - 7 {
ip += 3;
} else {
bitCount -= 8 * (iend as i32 - 7 - ip as i32);
bitCount &= 31;
ip = iend - 4;
}
bitStream = MEM_readLE32(&src[ip..]) >> bitCount;
repeats = (ZSTD_countTrailingZeros32(!bitStream | 0x8000_0000) >> 1) as i32;
}
charnum += 3 * repeats as u32;
bitStream >>= 2 * repeats;
bitCount += 2 * repeats;
debug_assert!(bitStream & 3 < 3);
charnum += bitStream & 3;
bitCount += 2;
if charnum >= maxSV1 {
break;
}
if ip <= iend - 7 || (ip + (bitCount >> 3) as usize <= iend - 4) {
ip += (bitCount >> 3) as usize;
bitCount &= 7;
} else {
bitCount -= 8 * (iend as i32 - 4 - ip as i32);
bitCount &= 31;
ip = iend - 4;
}
bitStream = MEM_readLE32(&src[ip..]) >> bitCount;
}
let max = (2 * threshold - 1) - remaining;
let mut count;
if (bitStream & (threshold as u32 - 1)) < max as u32 {
count = (bitStream & (threshold as u32 - 1)) as i32;
bitCount += nbBits - 1;
} else {
count = (bitStream & (2 * threshold as u32 - 1)) as i32;
if count >= threshold {
count -= max;
}
bitCount += nbBits;
}
count -= 1; if count >= 0 {
remaining -= count;
} else {
debug_assert_eq!(count, -1);
remaining += count;
}
if charnum as usize >= normalizedCounter.len() {
return ERROR(ErrorCode::MaxSymbolValueTooSmall);
}
normalizedCounter[charnum as usize] = count as i16;
charnum += 1;
previous0 = count == 0;
debug_assert!(threshold > 1);
if remaining < threshold {
if remaining <= 1 {
break;
}
nbBits = ZSTD_highbit32(remaining as u32) as i32 + 1;
threshold = 1 << (nbBits - 1);
}
if charnum >= maxSV1 {
break;
}
if ip <= iend - 7 || (ip + (bitCount >> 3) as usize <= iend - 4) {
ip += (bitCount >> 3) as usize;
bitCount &= 7;
} else {
bitCount -= 8 * (iend as i32 - 4 - ip as i32);
bitCount &= 31;
ip = iend - 4;
}
bitStream = MEM_readLE32(&src[ip..]) >> bitCount;
}
if remaining != 1 {
return ERROR(ErrorCode::CorruptionDetected);
}
if charnum > maxSV1 {
return ERROR(ErrorCode::MaxSymbolValueTooSmall);
}
if bitCount > 32 {
return ERROR(ErrorCode::CorruptionDetected);
}
*maxSVPtr = charnum.wrapping_sub(1);
ip += ((bitCount + 7) >> 3) as usize;
ip
}
pub fn FSE_readNCount_bmi2(
normalizedCounter: &mut [i16],
maxSVPtr: &mut u32,
tableLogPtr: &mut u32,
src: &[u8],
_bmi2: i32,
) -> usize {
FSE_readNCount(normalizedCounter, maxSVPtr, tableLogPtr, src)
}
pub fn HUF_readStats(
huffWeight: &mut [u8],
hwSize: usize,
rankStats: &mut [u32],
nbSymbolsPtr: &mut u32,
tableLogPtr: &mut u32,
src: &[u8],
) -> usize {
let mut wksp = [0u32; HUF_READ_STATS_WORKSPACE_SIZE_U32];
HUF_readStats_wksp(
huffWeight,
hwSize,
rankStats,
nbSymbolsPtr,
tableLogPtr,
src,
&mut wksp,
0,
)
}
pub fn HUF_readStats_wksp(
huffWeight: &mut [u8],
hwSize: usize,
rankStats: &mut [u32],
nbSymbolsPtr: &mut u32,
tableLogPtr: &mut u32,
src: &[u8],
workSpace: &mut [u32],
flags: i32,
) -> usize {
if src.is_empty() {
return ERROR(ErrorCode::SrcSizeWrong);
}
let mut iSize = src[0] as usize;
let oSize;
let mut read_from_fse = 0usize;
if iSize >= 128 {
oSize = iSize - 127;
iSize = oSize.div_ceil(2);
if iSize + 1 > src.len() {
return ERROR(ErrorCode::SrcSizeWrong);
}
if oSize >= hwSize {
return ERROR(ErrorCode::CorruptionDetected);
}
if huffWeight.len() < hwSize {
return ERROR(ErrorCode::CorruptionDetected);
}
let payload = &src[1..];
let mut n = 0;
while n < oSize {
huffWeight[n] = payload[n / 2] >> 4;
huffWeight[n + 1] = payload[n / 2] & 0x0F;
n += 2;
}
} else {
if iSize + 1 > src.len() {
return ERROR(ErrorCode::SrcSizeWrong);
}
if hwSize == 0 || huffWeight.len() < hwSize {
return ERROR(ErrorCode::CorruptionDetected);
}
let bmi2 = (flags & HUF_flags_bmi2) != 0;
oSize = crate::common::fse_decompress::FSE_decompress_wksp_bmi2(
&mut huffWeight[..hwSize - 1],
&src[1..1 + iSize],
6,
bytemuck_u32_as_u8(workSpace),
bmi2 as i32,
);
if crate::common::error::ERR_isError(oSize) {
return oSize;
}
read_from_fse = oSize;
}
if rankStats.len() < HUF_TABLELOG_MAX as usize + 1 {
return ERROR(ErrorCode::CorruptionDetected);
}
for r in rankStats.iter_mut().take(HUF_TABLELOG_MAX as usize + 1) {
*r = 0;
}
let _ = read_from_fse;
let mut weightTotal: u32 = 0;
for n in 0..oSize {
if huffWeight[n] as u32 > HUF_TABLELOG_MAX {
return ERROR(ErrorCode::CorruptionDetected);
}
rankStats[huffWeight[n] as usize] += 1;
weightTotal += (1u32 << huffWeight[n]) >> 1;
}
if weightTotal == 0 {
return ERROR(ErrorCode::CorruptionDetected);
}
let tableLog = ZSTD_highbit32(weightTotal) + 1;
if tableLog > HUF_TABLELOG_MAX {
return ERROR(ErrorCode::CorruptionDetected);
}
*tableLogPtr = tableLog;
let total = 1u32 << tableLog;
let rest = total - weightTotal;
let verif = 1u32 << ZSTD_highbit32(rest);
let lastWeight = ZSTD_highbit32(rest) + 1;
if verif != rest {
return ERROR(ErrorCode::CorruptionDetected);
}
huffWeight[oSize] = lastWeight as u8;
rankStats[lastWeight as usize] += 1;
if rankStats[1] < 2 || (rankStats[1] & 1) != 0 {
return ERROR(ErrorCode::CorruptionDetected);
}
*nbSymbolsPtr = (oSize + 1) as u32;
iSize + 1
}
fn bytemuck_u32_as_u8(words: &mut [u32]) -> &mut [u8] {
let len = words.len() * 4;
unsafe { core::slice::from_raw_parts_mut(words.as_mut_ptr() as *mut u8, len) }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn error_forwarders_mirror_zstd_errors() {
let err = ERROR(ErrorCode::CorruptionDetected);
assert_eq!(FSE_isError(err), 1);
assert_eq!(HUF_isError(err), 1);
assert_eq!(FSE_isError(0), 0);
assert_eq!(HUF_isError(0), 0);
assert!(FSE_getErrorName(err).contains("corruption"));
assert!(HUF_getErrorName(err).contains("corruption"));
}
#[test]
fn fse_version_matches_upstream() {
assert_eq!(FSE_versionNumber(), 900);
}
#[test]
fn fse_workspace_sizes_match_upstream_macros() {
assert_eq!(FSE_DTABLE_SIZE_U32(6), 65);
assert_eq!(FSE_DTABLE_SIZE(6), 260);
assert_eq!(FSE_BUILD_DTABLE_WKSP_SIZE(0, 0), 11);
assert_eq!(FSE_BUILD_DTABLE_WKSP_SIZE_U32(0, 0), 3);
assert_eq!(FSE_BUILD_DTABLE_WKSP_SIZE(6, HUF_TABLELOG_MAX - 1), 96);
assert_eq!(FSE_BUILD_DTABLE_WKSP_SIZE_U32(6, HUF_TABLELOG_MAX - 1), 24);
assert_eq!(
FSE_BUILD_DTABLE_WKSP_SIZE(FSE_TABLELOG_ABSOLUTE_MAX, FSE_MAX_SYMBOL_VALUE),
33_288
);
assert_eq!(
FSE_BUILD_DTABLE_WKSP_SIZE_U32(FSE_TABLELOG_ABSOLUTE_MAX, FSE_MAX_SYMBOL_VALUE),
8_322
);
assert_eq!(HUF_READ_STATS_WORKSPACE_SIZE_U32, 219);
assert_eq!(HUF_READ_STATS_WORKSPACE_SIZE, 876);
}
#[test]
fn fse_readncount_rejects_tablelog_too_large() {
let src = [0x0B, 0, 0, 0, 0, 0, 0, 0];
let mut counts = [0i16; 256];
let mut maxSV: u32 = 255;
let mut tl: u32 = 0;
let rc = FSE_readNCount(&mut counts, &mut maxSV, &mut tl, &src);
assert!(crate::common::error::ERR_isError(rc));
assert_eq!(
crate::common::error::ERR_getErrorCode(rc),
ErrorCode::TableLogTooLarge
);
}
#[test]
fn fse_readncount_rejects_counter_buffer_too_small() {
let src = [0x00, 0, 0, 0, 0, 0, 0, 0];
let mut counts = [0i16; 1];
let mut maxSV: u32 = 255;
let mut tl: u32 = 0;
let rc = FSE_readNCount(&mut counts, &mut maxSV, &mut tl, &src);
assert!(crate::common::error::ERR_isError(rc));
assert_eq!(
crate::common::error::ERR_getErrorCode(rc),
ErrorCode::MaxSymbolValueTooSmall
);
}
#[test]
fn fse_readncount_no_clear_still_rejects_counter_buffer_too_small() {
let src = [0x00, 0, 0, 0, 0, 0, 0, 0];
let mut counts = [0i16; 1];
let mut maxSV: u32 = 255;
let mut tl: u32 = 0;
let rc = FSE_readNCount_no_clear(&mut counts, &mut maxSV, &mut tl, &src);
assert!(crate::common::error::ERR_isError(rc));
assert_eq!(
crate::common::error::ERR_getErrorCode(rc),
ErrorCode::MaxSymbolValueTooSmall
);
}
#[test]
fn fse_readncount_short_header_recursion_preserves_clear_counts() {
let src = [0x0B];
let mut counts = [7i16; 4];
let mut maxSV: u32 = 3;
let mut tl: u32 = 0;
let rc = FSE_readNCount_no_clear(&mut counts, &mut maxSV, &mut tl, &src);
assert!(crate::common::error::ERR_isError(rc));
assert_eq!(counts, [7i16; 4]);
let mut counts = [7i16; 4];
let mut maxSV: u32 = 3;
let mut tl: u32 = 0;
let rc = FSE_readNCount(&mut counts, &mut maxSV, &mut tl, &src);
assert!(crate::common::error::ERR_isError(rc));
assert_eq!(counts, [0i16; 4]);
}
#[test]
fn huf_readstats_rejects_empty_input() {
let mut hw = [0u8; 256];
let mut rs = [0u32; 16];
let mut ns: u32 = 0;
let mut tl: u32 = 0;
let mut wksp = [0u32; 64];
let rc = HUF_readStats_wksp(&mut hw, 256, &mut rs, &mut ns, &mut tl, &[], &mut wksp, 0);
assert!(crate::common::error::ERR_isError(rc));
}
#[test]
fn huf_readstats_rejects_truncated_raw_header() {
let src = [128 + 10, 0u8];
let mut hw = [0u8; 256];
let mut rs = [0u32; 16];
let mut ns: u32 = 0;
let mut tl: u32 = 0;
let mut wksp = [0u32; 64];
let rc = HUF_readStats_wksp(&mut hw, 256, &mut rs, &mut ns, &mut tl, &src, &mut wksp, 0);
assert!(crate::common::error::ERR_isError(rc));
}
#[test]
fn huf_readstats_rejects_invalid_output_capacity() {
let src = [128 + 2, 0x11, 0x11];
let mut hw = [0u8; 1];
let mut rs = [0u32; 16];
let mut ns: u32 = 0;
let mut tl: u32 = 0;
let mut wksp = [0u32; HUF_READ_STATS_WORKSPACE_SIZE_U32];
let rc = HUF_readStats_wksp(&mut hw, 0, &mut rs, &mut ns, &mut tl, &src, &mut wksp, 0);
assert!(crate::common::error::ERR_isError(rc));
assert_eq!(
crate::common::error::ERR_getErrorCode(rc),
ErrorCode::CorruptionDetected
);
}
}