diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 94dedb56a..e41539794 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -28,7 +28,7 @@ #include "zstd_opt.h" #include "zstd_ldm.h" #include "zstd_compress_superblock.h" -#include "../common/bits.h" /* ZSTD_highbit32, ZSTD_rotateRight_U64 */ +#include "../common/bits.h" /* ZSTD_highbit32 */ /* Frame serialization lives in Rust. Keep its interface scalar so the * large, configuration-sensitive CCtx parameter structure stays in C. */ @@ -54,6 +54,9 @@ int ZSTD_rust_simpleCompress2Level(const void* cctx); int ZSTD_rust_simpleCompressStream2Level(const void* cctx); void ZSTD_rust_reduceTable(U32* table, U32 size, U32 reducerValue, int preserveMark); +U64 ZSTD_rust_advanceHashSalt(U64 hashSalt, U64 hashSaltEntropy); +int ZSTD_rust_indexTooCloseToMax(size_t nextSrcBaseOffset); +int ZSTD_rust_dictTooBig(size_t loadedDictSize); /* Context-free compression-parameter selection and sizing leaves live in * Rust (rust/src/zstd_compress_params.rs). This file retains @@ -1445,18 +1448,9 @@ typedef enum { ZSTD_resetTarget_CCtx } ZSTD_resetTarget_e; -/* Mixes bits in a 64 bits in a value, based on XXH3_rrmxmx */ -static U64 ZSTD_bitmix(U64 val, U64 len) { - val ^= ZSTD_rotateRight_U64(val, 49) ^ ZSTD_rotateRight_U64(val, 24); - val *= 0x9FB21C651E98DF25ULL; - val ^= (val >> 35) + len ; - val *= 0x9FB21C651E98DF25ULL; - return val ^ (val >> 28); -} - /* Mixes in the hashSalt and hashSaltEntropy to create a new hashSalt */ static void ZSTD_advanceHashSalt(ZSTD_MatchState_t* ms) { - ms->hashSalt = ZSTD_bitmix(ms->hashSalt, 8) ^ ZSTD_bitmix((U64) ms->hashSaltEntropy, 4); + ms->hashSalt = ZSTD_rust_advanceHashSalt(ms->hashSalt, (U64)ms->hashSaltEntropy); } static size_t @@ -1556,7 +1550,7 @@ ZSTD_reset_matchState(ZSTD_MatchState_t* ms, #define ZSTD_INDEXOVERFLOW_MARGIN (16 MB) static int ZSTD_indexTooCloseToMax(ZSTD_window_t w) { - return (size_t)(w.nextSrc - w.base) > (ZSTD_CURRENT_MAX - ZSTD_INDEXOVERFLOW_MARGIN); + return ZSTD_rust_indexTooCloseToMax((size_t)(w.nextSrc - w.base)); } /** ZSTD_dictTooBig(): @@ -1566,7 +1560,7 @@ static int ZSTD_indexTooCloseToMax(ZSTD_window_t w) */ static int ZSTD_dictTooBig(size_t const loadedDictSize) { - return loadedDictSize > ZSTD_CHUNKSIZE_MAX; + return ZSTD_rust_dictTooBig(loadedDictSize); } /*! ZSTD_resetCCtx_internal() : diff --git a/rust/src/zstd_compress.rs b/rust/src/zstd_compress.rs index 8562c42b1..e58b3746c 100644 --- a/rust/src/zstd_compress.rs +++ b/rust/src/zstd_compress.rs @@ -64,6 +64,13 @@ const ZSTD_CONTENTSIZE_UNKNOWN: u64 = u64::MAX; const ZSTD_ROWSIZE: usize = 16; const ZSTD_WINDOW_START_INDEX: u32 = 2; const ZSTD_DUBT_UNSORTED_MARK: u32 = 1; +const ZSTD_INDEXOVERFLOW_MARGIN: usize = 16usize << 20; +const ZSTD_CURRENT_MAX: usize = if size_of::() == 8 { + 3500usize << 20 +} else { + 2000usize << 20 +}; +const ZSTD_CHUNKSIZE_MAX: usize = u32::MAX as usize - ZSTD_CURRENT_MAX; #[cfg(not(test))] const ZSTD_E_END: c_int = 2; @@ -89,6 +96,48 @@ pub struct ZSTD_outBuffer { * supported pointer widths. */ const TMP_WORKSPACE_SIZE: usize = 16 << 10; +#[inline] +fn bitmix(mut val: u64, len: u64) -> u64 { + val ^= val.rotate_right(49) ^ val.rotate_right(24); + val = val.wrapping_mul(0x9FB21C651E98DF25); + val ^= (val >> 35).wrapping_add(len); + val = val.wrapping_mul(0x9FB21C651E98DF25); + val ^ (val >> 28) +} + +#[inline] +fn advance_hash_salt(hash_salt: u64, hash_salt_entropy: u64) -> u64 { + bitmix(hash_salt, 8) ^ bitmix(hash_salt_entropy, 4) +} + +/// Advance the row-matchfinder salt without exposing C's private match state. +#[no_mangle] +pub extern "C" fn ZSTD_rust_advanceHashSalt(hash_salt: u64, hash_salt_entropy: u64) -> u64 { + advance_hash_salt(hash_salt, hash_salt_entropy) +} + +#[inline] +fn index_too_close_to_max(next_src_base_offset: usize) -> bool { + next_src_base_offset > ZSTD_CURRENT_MAX - ZSTD_INDEXOVERFLOW_MARGIN +} + +/// Return whether a scalar C window offset is within the overflow margin. +#[no_mangle] +pub extern "C" fn ZSTD_rust_indexTooCloseToMax(next_src_base_offset: usize) -> c_int { + index_too_close_to_max(next_src_base_offset) as c_int +} + +#[inline] +fn dict_too_big(loaded_dict_size: usize) -> bool { + loaded_dict_size > ZSTD_CHUNKSIZE_MAX +} + +/// Return whether a dictionary exceeds the maximum loadable chunk size. +#[no_mangle] +pub extern "C" fn ZSTD_rust_dictTooBig(loaded_dict_size: usize) -> c_int { + dict_too_big(loaded_dict_size) as c_int +} + #[inline] fn reduce_table_internal(table: &mut [u32], reducer_value: u32, preserve_mark: bool) { debug_assert_eq!(table.len() % ZSTD_ROWSIZE, 0); @@ -657,6 +706,37 @@ mod tests { assert_eq!(table[ZSTD_ROWSIZE * 2 - 1], u32::MAX - 4); } + #[test] + fn bitmix_and_hash_salt_match_the_c_arithmetic() { + assert_eq!(bitmix(0x0123_4567_89ab_cdef, 8), 0xd498_d855_4e8d_d8cb); + assert_eq!( + advance_hash_salt(0x0123_4567_89ab_cdef, 0xfedc_ba98_7654_3210), + 0xe5ee_f172_e5ff_3e57 + ); + assert_eq!( + ZSTD_rust_advanceHashSalt(0x0123_4567_89ab_cdef, 0xfedc_ba98_7654_3210), + 0xe5ee_f172_e5ff_3e57 + ); + } + + #[test] + fn index_too_close_to_max_uses_a_strict_margin_boundary() { + let threshold = ZSTD_CURRENT_MAX - ZSTD_INDEXOVERFLOW_MARGIN; + assert!(!index_too_close_to_max(threshold)); + assert!(index_too_close_to_max(threshold + 1)); + assert_eq!(ZSTD_rust_indexTooCloseToMax(threshold), 0); + assert_eq!(ZSTD_rust_indexTooCloseToMax(threshold + 1), 1); + } + + #[test] + fn dict_too_big_uses_a_strict_chunk_size_boundary() { + assert!(!dict_too_big(0)); + assert!(!dict_too_big(ZSTD_CHUNKSIZE_MAX)); + assert!(dict_too_big(ZSTD_CHUNKSIZE_MAX + 1)); + assert_eq!(ZSTD_rust_dictTooBig(ZSTD_CHUNKSIZE_MAX), 0); + assert_eq!(ZSTD_rust_dictTooBig(ZSTD_CHUNKSIZE_MAX + 1), 1); + } + #[test] fn public_one_shot_abi_is_c_compatible() { let entry: unsafe extern "C" fn(*mut c_void, usize, *const c_void, usize, c_int) -> usize =