Introduce the Rust static library and move the shared byte, bitstream, CPU, error, xxHash, debug, and public-common implementations into it. Thin C shims preserve the existing header-driven C build while original tests link the Rust archive. This establishes the ABI-safe foundation for later codec and CLI ports; entropy coding, runtime support, codecs, dictionaries, and the CLI remain C. The top-down migration map documents that boundary and its validation path. Test Plan: - cargo fmt --check - cargo test --all-targets - cargo clippy --all-targets -- -D warnings - cargo build --release Refs: rust/README.md
689 lines
20 KiB
Rust
689 lines
20 KiB
Rust
#![allow(non_camel_case_types)]
|
|
#![allow(non_snake_case)]
|
|
|
|
use std::os::raw::c_void;
|
|
|
|
pub type XXH32_hash_t = u32;
|
|
pub type XXH64_hash_t = u64;
|
|
pub type XXH_errorcode = i32;
|
|
|
|
pub const XXH_OK: XXH_errorcode = 0;
|
|
pub const XXH_ERROR: XXH_errorcode = 1;
|
|
|
|
const PRIME32_1: u32 = 0x9e37_79b1;
|
|
const PRIME32_2: u32 = 0x85eb_ca77;
|
|
const PRIME32_3: u32 = 0xc2b2_ae3d;
|
|
const PRIME32_4: u32 = 0x27d4_eb2f;
|
|
const PRIME32_5: u32 = 0x1656_67b1;
|
|
|
|
const PRIME64_1: u64 = 0x9e37_79b1_85eb_ca87;
|
|
const PRIME64_2: u64 = 0xc2b2_ae3d_27d4_eb4f;
|
|
const PRIME64_3: u64 = 0x1656_67b1_9e37_79f9;
|
|
const PRIME64_4: u64 = 0x85eb_ca77_c2b2_ae63;
|
|
const PRIME64_5: u64 = 0x27d4_eb2f_1656_67c5;
|
|
|
|
#[inline]
|
|
fn XXH64_round(mut acc: u64, input: u64) -> u64 {
|
|
acc = acc.wrapping_add(input.wrapping_mul(PRIME64_2));
|
|
acc = acc.rotate_left(31);
|
|
acc.wrapping_mul(PRIME64_1)
|
|
}
|
|
|
|
#[inline]
|
|
fn XXH64_mergeRound(mut acc: u64, val: u64) -> u64 {
|
|
acc ^= XXH64_round(0, val);
|
|
acc.wrapping_mul(PRIME64_1).wrapping_add(PRIME64_4)
|
|
}
|
|
|
|
#[inline]
|
|
fn XXH64_avalanche(mut hash: u64) -> u64 {
|
|
hash ^= hash >> 33;
|
|
hash = hash.wrapping_mul(PRIME64_2);
|
|
hash ^= hash >> 29;
|
|
hash = hash.wrapping_mul(PRIME64_3);
|
|
hash ^= hash >> 32;
|
|
hash
|
|
}
|
|
|
|
#[inline]
|
|
fn read_le64(input: &[u8]) -> u64 {
|
|
u64::from_le_bytes(input[..8].try_into().unwrap())
|
|
}
|
|
|
|
#[inline]
|
|
fn read_le32(input: &[u8]) -> u32 {
|
|
u32::from_le_bytes(input[..4].try_into().unwrap())
|
|
}
|
|
|
|
#[inline]
|
|
fn XXH32_round(mut acc: u32, input: u32) -> u32 {
|
|
acc = acc.wrapping_add(input.wrapping_mul(PRIME32_2));
|
|
acc = acc.rotate_left(13);
|
|
acc.wrapping_mul(PRIME32_1)
|
|
}
|
|
|
|
#[inline]
|
|
fn XXH32_avalanche(mut hash: u32) -> u32 {
|
|
hash ^= hash >> 15;
|
|
hash = hash.wrapping_mul(PRIME32_2);
|
|
hash ^= hash >> 13;
|
|
hash = hash.wrapping_mul(PRIME32_3);
|
|
hash ^= hash >> 16;
|
|
hash
|
|
}
|
|
|
|
#[inline]
|
|
fn process_stripe32(accumulators: &mut [u32; 4], stripe: &[u8]) {
|
|
accumulators[0] = XXH32_round(accumulators[0], read_le32(&stripe[0..]));
|
|
accumulators[1] = XXH32_round(accumulators[1], read_le32(&stripe[4..]));
|
|
accumulators[2] = XXH32_round(accumulators[2], read_le32(&stripe[8..]));
|
|
accumulators[3] = XXH32_round(accumulators[3], read_le32(&stripe[12..]));
|
|
}
|
|
|
|
#[inline]
|
|
fn finalize32(mut hash: u32, mut input: &[u8]) -> u32 {
|
|
while input.len() >= 4 {
|
|
hash = hash.wrapping_add(read_le32(input).wrapping_mul(PRIME32_3));
|
|
hash = hash.rotate_left(17).wrapping_mul(PRIME32_4);
|
|
input = &input[4..];
|
|
}
|
|
for &byte in input {
|
|
hash = hash.wrapping_add(u32::from(byte).wrapping_mul(PRIME32_5));
|
|
hash = hash.rotate_left(11).wrapping_mul(PRIME32_1);
|
|
}
|
|
XXH32_avalanche(hash)
|
|
}
|
|
|
|
fn hash32_bytes(input: &[u8], seed: u32) -> u32 {
|
|
let mut offset = 0;
|
|
let mut hash;
|
|
if input.len() >= 16 {
|
|
let mut accumulators = [
|
|
seed.wrapping_add(PRIME32_1).wrapping_add(PRIME32_2),
|
|
seed.wrapping_add(PRIME32_2),
|
|
seed,
|
|
seed.wrapping_sub(PRIME32_1),
|
|
];
|
|
while offset + 16 <= input.len() {
|
|
process_stripe32(&mut accumulators, &input[offset..offset + 16]);
|
|
offset += 16;
|
|
}
|
|
hash = accumulators[0]
|
|
.rotate_left(1)
|
|
.wrapping_add(accumulators[1].rotate_left(7))
|
|
.wrapping_add(accumulators[2].rotate_left(12))
|
|
.wrapping_add(accumulators[3].rotate_left(18));
|
|
} else {
|
|
hash = seed.wrapping_add(PRIME32_5);
|
|
}
|
|
hash = hash.wrapping_add(input.len() as u32);
|
|
finalize32(hash, &input[offset..])
|
|
}
|
|
|
|
#[inline]
|
|
fn process_stripe(accumulators: &mut [u64; 4], stripe: &[u8]) {
|
|
accumulators[0] = XXH64_round(accumulators[0], read_le64(&stripe[0..]));
|
|
accumulators[1] = XXH64_round(accumulators[1], read_le64(&stripe[8..]));
|
|
accumulators[2] = XXH64_round(accumulators[2], read_le64(&stripe[16..]));
|
|
accumulators[3] = XXH64_round(accumulators[3], read_le64(&stripe[24..]));
|
|
}
|
|
|
|
#[inline]
|
|
fn finalize(mut hash: u64, mut input: &[u8]) -> u64 {
|
|
while input.len() >= 8 {
|
|
let lane = XXH64_round(0, read_le64(input));
|
|
hash ^= lane;
|
|
hash = hash
|
|
.rotate_left(27)
|
|
.wrapping_mul(PRIME64_1)
|
|
.wrapping_add(PRIME64_4);
|
|
input = &input[8..];
|
|
}
|
|
|
|
if input.len() >= 4 {
|
|
hash ^= u64::from(read_le32(input)).wrapping_mul(PRIME64_1);
|
|
hash = hash
|
|
.rotate_left(23)
|
|
.wrapping_mul(PRIME64_2)
|
|
.wrapping_add(PRIME64_3);
|
|
input = &input[4..];
|
|
}
|
|
|
|
for &byte in input {
|
|
hash ^= u64::from(byte).wrapping_mul(PRIME64_5);
|
|
hash = hash.rotate_left(11).wrapping_mul(PRIME64_1);
|
|
}
|
|
|
|
XXH64_avalanche(hash)
|
|
}
|
|
|
|
fn hash_bytes(input: &[u8], seed: u64) -> u64 {
|
|
let mut offset = 0;
|
|
let mut hash;
|
|
|
|
if input.len() >= 32 {
|
|
let mut accumulators = [
|
|
seed.wrapping_add(PRIME64_1).wrapping_add(PRIME64_2),
|
|
seed.wrapping_add(PRIME64_2),
|
|
seed,
|
|
seed.wrapping_sub(PRIME64_1),
|
|
];
|
|
while offset + 32 <= input.len() {
|
|
process_stripe(&mut accumulators, &input[offset..offset + 32]);
|
|
offset += 32;
|
|
}
|
|
|
|
hash = accumulators[0]
|
|
.rotate_left(1)
|
|
.wrapping_add(accumulators[1].rotate_left(7))
|
|
.wrapping_add(accumulators[2].rotate_left(12))
|
|
.wrapping_add(accumulators[3].rotate_left(18));
|
|
for accumulator in accumulators {
|
|
hash = XXH64_mergeRound(hash, accumulator);
|
|
}
|
|
} else {
|
|
hash = seed.wrapping_add(PRIME64_5);
|
|
}
|
|
|
|
hash = hash.wrapping_add(input.len() as u64);
|
|
finalize(hash, &input[offset..])
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Clone, Copy)]
|
|
pub struct XXH32_state_t {
|
|
pub total_len_32: u32,
|
|
pub large_len: u32,
|
|
pub v: [u32; 4],
|
|
pub mem32: [u32; 4],
|
|
pub memsize: u32,
|
|
pub reserved: u32,
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub struct XXH32_canonical_t {
|
|
pub digest: [u8; 4],
|
|
}
|
|
|
|
#[export_name = "ZSTD_XXH32"]
|
|
pub unsafe extern "C" fn XXH32(
|
|
input: *const c_void,
|
|
length: usize,
|
|
seed: XXH32_hash_t,
|
|
) -> XXH32_hash_t {
|
|
if input.is_null() {
|
|
if length != 0 {
|
|
return 0;
|
|
}
|
|
return hash32_bytes(&[], seed);
|
|
}
|
|
hash32_bytes(std::slice::from_raw_parts(input.cast::<u8>(), length), seed)
|
|
}
|
|
|
|
#[export_name = "ZSTD_XXH32_createState"]
|
|
pub unsafe extern "C" fn XXH32_createState() -> *mut XXH32_state_t {
|
|
libc::malloc(std::mem::size_of::<XXH32_state_t>()).cast::<XXH32_state_t>()
|
|
}
|
|
|
|
#[export_name = "ZSTD_XXH32_freeState"]
|
|
pub unsafe extern "C" fn XXH32_freeState(state: *mut XXH32_state_t) -> XXH_errorcode {
|
|
libc::free(state.cast::<c_void>());
|
|
XXH_OK
|
|
}
|
|
|
|
#[export_name = "ZSTD_XXH32_copyState"]
|
|
pub unsafe extern "C" fn XXH32_copyState(
|
|
destination: *mut XXH32_state_t,
|
|
source: *const XXH32_state_t,
|
|
) {
|
|
std::ptr::copy_nonoverlapping(source, destination, 1);
|
|
}
|
|
|
|
#[export_name = "ZSTD_XXH32_reset"]
|
|
pub unsafe extern "C" fn XXH32_reset(
|
|
state: *mut XXH32_state_t,
|
|
seed: XXH32_hash_t,
|
|
) -> XXH_errorcode {
|
|
if state.is_null() {
|
|
return XXH_ERROR;
|
|
}
|
|
|
|
std::ptr::write_bytes(state, 0, 1);
|
|
let state = &mut *state;
|
|
state.v[0] = seed.wrapping_add(PRIME32_1).wrapping_add(PRIME32_2);
|
|
state.v[1] = seed.wrapping_add(PRIME32_2);
|
|
state.v[2] = seed;
|
|
state.v[3] = seed.wrapping_sub(PRIME32_1);
|
|
XXH_OK
|
|
}
|
|
|
|
#[export_name = "ZSTD_XXH32_update"]
|
|
pub unsafe extern "C" fn XXH32_update(
|
|
state: *mut XXH32_state_t,
|
|
input: *const c_void,
|
|
length: usize,
|
|
) -> XXH_errorcode {
|
|
if state.is_null() {
|
|
return XXH_ERROR;
|
|
}
|
|
if input.is_null() {
|
|
return XXH_OK;
|
|
}
|
|
|
|
let state = &mut *state;
|
|
let input = std::slice::from_raw_parts(input.cast::<u8>(), length);
|
|
state.total_len_32 = state.total_len_32.wrapping_add(length as u32);
|
|
state.large_len |= u32::from(length >= 16 || state.total_len_32 >= 16);
|
|
|
|
let buffered = state.memsize as usize;
|
|
if buffered + length < 16 {
|
|
std::ptr::copy_nonoverlapping(
|
|
input.as_ptr(),
|
|
state.mem32.as_mut_ptr().cast::<u8>().add(buffered),
|
|
length,
|
|
);
|
|
state.memsize += length as u32;
|
|
return XXH_OK;
|
|
}
|
|
|
|
let mut offset = 0;
|
|
if buffered != 0 {
|
|
let fill = 16 - buffered;
|
|
std::ptr::copy_nonoverlapping(
|
|
input.as_ptr(),
|
|
state.mem32.as_mut_ptr().cast::<u8>().add(buffered),
|
|
fill,
|
|
);
|
|
let stripe = std::slice::from_raw_parts(state.mem32.as_ptr().cast::<u8>(), 16);
|
|
process_stripe32(&mut state.v, stripe);
|
|
offset = fill;
|
|
state.memsize = 0;
|
|
}
|
|
|
|
while offset + 16 <= length {
|
|
process_stripe32(&mut state.v, &input[offset..offset + 16]);
|
|
offset += 16;
|
|
}
|
|
|
|
let remaining = length - offset;
|
|
if remaining != 0 {
|
|
std::ptr::copy_nonoverlapping(
|
|
input.as_ptr().add(offset),
|
|
state.mem32.as_mut_ptr().cast::<u8>(),
|
|
remaining,
|
|
);
|
|
state.memsize = remaining as u32;
|
|
}
|
|
XXH_OK
|
|
}
|
|
|
|
#[export_name = "ZSTD_XXH32_digest"]
|
|
pub unsafe extern "C" fn XXH32_digest(state: *const XXH32_state_t) -> XXH32_hash_t {
|
|
if state.is_null() {
|
|
return 0;
|
|
}
|
|
let state = &*state;
|
|
let mut hash = if state.large_len != 0 {
|
|
state.v[0]
|
|
.rotate_left(1)
|
|
.wrapping_add(state.v[1].rotate_left(7))
|
|
.wrapping_add(state.v[2].rotate_left(12))
|
|
.wrapping_add(state.v[3].rotate_left(18))
|
|
} else {
|
|
state.v[2].wrapping_add(PRIME32_5)
|
|
};
|
|
hash = hash.wrapping_add(state.total_len_32);
|
|
let buffered =
|
|
std::slice::from_raw_parts(state.mem32.as_ptr().cast::<u8>(), state.memsize as usize);
|
|
finalize32(hash, buffered)
|
|
}
|
|
|
|
#[export_name = "ZSTD_XXH32_canonicalFromHash"]
|
|
pub unsafe extern "C" fn XXH32_canonicalFromHash(
|
|
destination: *mut XXH32_canonical_t,
|
|
hash: XXH32_hash_t,
|
|
) {
|
|
(*destination).digest = hash.to_be_bytes();
|
|
}
|
|
|
|
#[export_name = "ZSTD_XXH32_hashFromCanonical"]
|
|
pub unsafe extern "C" fn XXH32_hashFromCanonical(source: *const XXH32_canonical_t) -> XXH32_hash_t {
|
|
u32::from_be_bytes((*source).digest)
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Clone, Copy)]
|
|
pub struct XXH64_state_t {
|
|
pub total_len: u64,
|
|
pub v: [u64; 4],
|
|
pub mem64: [u64; 4],
|
|
pub memsize: u32,
|
|
pub reserved32: u32,
|
|
pub reserved64: u64,
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub struct XXH64_canonical_t {
|
|
pub digest: [u8; 8],
|
|
}
|
|
|
|
#[export_name = "ZSTD_XXH64"]
|
|
pub unsafe extern "C" fn XXH64(
|
|
input: *const c_void,
|
|
length: usize,
|
|
seed: XXH64_hash_t,
|
|
) -> XXH64_hash_t {
|
|
if input.is_null() {
|
|
if length != 0 {
|
|
return 0;
|
|
}
|
|
return hash_bytes(&[], seed);
|
|
}
|
|
hash_bytes(std::slice::from_raw_parts(input.cast::<u8>(), length), seed)
|
|
}
|
|
|
|
#[export_name = "ZSTD_XXH64_createState"]
|
|
pub unsafe extern "C" fn XXH64_createState() -> *mut XXH64_state_t {
|
|
libc::malloc(std::mem::size_of::<XXH64_state_t>()).cast::<XXH64_state_t>()
|
|
}
|
|
|
|
#[export_name = "ZSTD_XXH64_freeState"]
|
|
pub unsafe extern "C" fn XXH64_freeState(state: *mut XXH64_state_t) -> XXH_errorcode {
|
|
libc::free(state.cast::<c_void>());
|
|
XXH_OK
|
|
}
|
|
|
|
#[export_name = "ZSTD_XXH64_copyState"]
|
|
pub unsafe extern "C" fn XXH64_copyState(
|
|
destination: *mut XXH64_state_t,
|
|
source: *const XXH64_state_t,
|
|
) {
|
|
std::ptr::copy_nonoverlapping(source, destination, 1);
|
|
}
|
|
|
|
#[export_name = "ZSTD_XXH64_reset"]
|
|
pub unsafe extern "C" fn XXH64_reset(
|
|
state: *mut XXH64_state_t,
|
|
seed: XXH64_hash_t,
|
|
) -> XXH_errorcode {
|
|
if state.is_null() {
|
|
return XXH_ERROR;
|
|
}
|
|
|
|
std::ptr::write_bytes(state, 0, 1);
|
|
let state = &mut *state;
|
|
state.v[0] = seed.wrapping_add(PRIME64_1).wrapping_add(PRIME64_2);
|
|
state.v[1] = seed.wrapping_add(PRIME64_2);
|
|
state.v[2] = seed;
|
|
state.v[3] = seed.wrapping_sub(PRIME64_1);
|
|
XXH_OK
|
|
}
|
|
|
|
#[export_name = "ZSTD_XXH64_update"]
|
|
pub unsafe extern "C" fn XXH64_update(
|
|
state: *mut XXH64_state_t,
|
|
input: *const c_void,
|
|
length: usize,
|
|
) -> XXH_errorcode {
|
|
if state.is_null() {
|
|
return XXH_ERROR;
|
|
}
|
|
if input.is_null() {
|
|
return XXH_OK;
|
|
}
|
|
|
|
let state = &mut *state;
|
|
let input = std::slice::from_raw_parts(input.cast::<u8>(), length);
|
|
state.total_len = state.total_len.wrapping_add(length as u64);
|
|
|
|
let buffered = state.memsize as usize;
|
|
if buffered + length < 32 {
|
|
std::ptr::copy_nonoverlapping(
|
|
input.as_ptr(),
|
|
state.mem64.as_mut_ptr().cast::<u8>().add(buffered),
|
|
length,
|
|
);
|
|
state.memsize += length as u32;
|
|
return XXH_OK;
|
|
}
|
|
|
|
let mut offset = 0;
|
|
if buffered != 0 {
|
|
let fill = 32 - buffered;
|
|
std::ptr::copy_nonoverlapping(
|
|
input.as_ptr(),
|
|
state.mem64.as_mut_ptr().cast::<u8>().add(buffered),
|
|
fill,
|
|
);
|
|
let stripe = std::slice::from_raw_parts(state.mem64.as_ptr().cast::<u8>(), 32);
|
|
process_stripe(&mut state.v, stripe);
|
|
offset = fill;
|
|
state.memsize = 0;
|
|
}
|
|
|
|
while offset + 32 <= length {
|
|
process_stripe(&mut state.v, &input[offset..offset + 32]);
|
|
offset += 32;
|
|
}
|
|
|
|
let remaining = length - offset;
|
|
if remaining != 0 {
|
|
std::ptr::copy_nonoverlapping(
|
|
input.as_ptr().add(offset),
|
|
state.mem64.as_mut_ptr().cast::<u8>(),
|
|
remaining,
|
|
);
|
|
state.memsize = remaining as u32;
|
|
}
|
|
|
|
XXH_OK
|
|
}
|
|
|
|
#[export_name = "ZSTD_XXH64_digest"]
|
|
pub unsafe extern "C" fn XXH64_digest(state: *const XXH64_state_t) -> XXH64_hash_t {
|
|
if state.is_null() {
|
|
return 0;
|
|
}
|
|
let state = &*state;
|
|
|
|
let mut hash = if state.total_len >= 32 {
|
|
let mut hash = state.v[0]
|
|
.rotate_left(1)
|
|
.wrapping_add(state.v[1].rotate_left(7))
|
|
.wrapping_add(state.v[2].rotate_left(12))
|
|
.wrapping_add(state.v[3].rotate_left(18));
|
|
for accumulator in state.v {
|
|
hash = XXH64_mergeRound(hash, accumulator);
|
|
}
|
|
hash
|
|
} else {
|
|
state.v[2].wrapping_add(PRIME64_5)
|
|
};
|
|
|
|
hash = hash.wrapping_add(state.total_len);
|
|
let buffered =
|
|
std::slice::from_raw_parts(state.mem64.as_ptr().cast::<u8>(), state.memsize as usize);
|
|
finalize(hash, buffered)
|
|
}
|
|
|
|
#[export_name = "ZSTD_XXH64_canonicalFromHash"]
|
|
pub unsafe extern "C" fn XXH64_canonicalFromHash(
|
|
destination: *mut XXH64_canonical_t,
|
|
hash: XXH64_hash_t,
|
|
) {
|
|
(*destination).digest = hash.to_be_bytes();
|
|
}
|
|
|
|
#[export_name = "ZSTD_XXH64_hashFromCanonical"]
|
|
pub unsafe extern "C" fn XXH64_hashFromCanonical(source: *const XXH64_canonical_t) -> XXH64_hash_t {
|
|
u64::from_be_bytes((*source).digest)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::mem::{offset_of, size_of, zeroed};
|
|
|
|
fn hash(input: &[u8], seed: u64) -> u64 {
|
|
unsafe { XXH64(input.as_ptr().cast(), input.len(), seed) }
|
|
}
|
|
|
|
fn hash32(input: &[u8], seed: u32) -> u32 {
|
|
unsafe { XXH32(input.as_ptr().cast(), input.len(), seed) }
|
|
}
|
|
|
|
#[test]
|
|
fn state_layout_matches_xxhash_h() {
|
|
assert_eq!(size_of::<XXH32_state_t>(), 48);
|
|
assert_eq!(offset_of!(XXH32_state_t, total_len_32), 0);
|
|
assert_eq!(offset_of!(XXH32_state_t, v), 8);
|
|
assert_eq!(offset_of!(XXH32_state_t, mem32), 24);
|
|
assert_eq!(offset_of!(XXH32_state_t, memsize), 40);
|
|
assert_eq!(size_of::<XXH64_state_t>(), 88);
|
|
assert_eq!(offset_of!(XXH64_state_t, total_len), 0);
|
|
assert_eq!(offset_of!(XXH64_state_t, v), 8);
|
|
assert_eq!(offset_of!(XXH64_state_t, mem64), 40);
|
|
assert_eq!(offset_of!(XXH64_state_t, memsize), 72);
|
|
assert_eq!(offset_of!(XXH64_state_t, reserved64), 80);
|
|
}
|
|
|
|
#[test]
|
|
fn reference_vectors_match_xxhash_0_8() {
|
|
let vectors: &[(&[u8], u64)] = &[
|
|
(b"", 0xef46_db37_51d8_e999),
|
|
(b"a", 0xd24e_c4f1_a98c_6e5b),
|
|
(b"abc", 0x44bc_2cf5_ad77_0999),
|
|
(b"message digest", 0x066e_d728_fcee_b3be),
|
|
(b"abcdefghijklmnopqrstuvwxyz", 0xcfe1_f278_fa89_835c),
|
|
(
|
|
b"1234567890123456789012345678901234567890",
|
|
0x5f3a_f5e2_3eeb_431d,
|
|
),
|
|
];
|
|
for &(input, expected) in vectors {
|
|
assert_eq!(hash(input, 0), expected, "input={input:?}");
|
|
}
|
|
|
|
let vectors32: &[(&[u8], u32)] = &[
|
|
(b"", 0x02cc_5d05),
|
|
(b"a", 0x550d_7456),
|
|
(b"abc", 0x32d1_53ff),
|
|
(b"message digest", 0x7c94_8494),
|
|
(b"abcdefghijklmnopqrstuvwxyz", 0x63a1_4d5f),
|
|
(b"1234567890123456789012345678901234567890", 0x765d_8c05),
|
|
];
|
|
for &(input, expected) in vectors32 {
|
|
assert_eq!(hash32(input, 0), expected, "input={input:?}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn streaming_matches_one_shot_across_every_buffer_boundary() {
|
|
let input: Vec<u8> = (0..257)
|
|
.map(|index| (index as u8).wrapping_mul(101).wrapping_add(17))
|
|
.collect();
|
|
|
|
for &seed in &[0, 1, u64::MAX, 0x0123_4567_89ab_cdef] {
|
|
let expected = hash(&input, seed);
|
|
for chunk_size in 1..=65 {
|
|
let mut state: XXH64_state_t = unsafe { zeroed() };
|
|
assert_eq!(unsafe { XXH64_reset(&mut state, seed) }, XXH_OK);
|
|
for chunk in input.chunks(chunk_size) {
|
|
assert_eq!(
|
|
unsafe { XXH64_update(&mut state, chunk.as_ptr().cast(), chunk.len()) },
|
|
XXH_OK
|
|
);
|
|
}
|
|
assert_eq!(
|
|
unsafe { XXH64_digest(&state) },
|
|
expected,
|
|
"chunk={chunk_size}"
|
|
);
|
|
}
|
|
}
|
|
|
|
for &seed in &[0, 1, u32::MAX, 0x89ab_cdef] {
|
|
let expected = hash32(&input, seed);
|
|
for chunk_size in 1..=33 {
|
|
let mut state: XXH32_state_t = unsafe { zeroed() };
|
|
assert_eq!(unsafe { XXH32_reset(&mut state, seed) }, XXH_OK);
|
|
for chunk in input.chunks(chunk_size) {
|
|
assert_eq!(
|
|
unsafe { XXH32_update(&mut state, chunk.as_ptr().cast(), chunk.len()) },
|
|
XXH_OK
|
|
);
|
|
}
|
|
assert_eq!(
|
|
unsafe { XXH32_digest(&state) },
|
|
expected,
|
|
"chunk={chunk_size}"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn intermediate_digests_do_not_modify_state() {
|
|
let input: Vec<u8> = (0..96).map(|value| value as u8).collect();
|
|
let mut state: XXH64_state_t = unsafe { zeroed() };
|
|
assert_eq!(unsafe { XXH64_reset(&mut state, 7) }, XXH_OK);
|
|
for end in 0..=input.len() {
|
|
if end != 0 {
|
|
assert_eq!(
|
|
unsafe { XXH64_update(&mut state, input[end - 1..].as_ptr().cast(), 1) },
|
|
XXH_OK
|
|
);
|
|
}
|
|
assert_eq!(unsafe { XXH64_digest(&state) }, hash(&input[..end], 7));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn canonical_form_is_big_endian() {
|
|
let mut canonical32 = XXH32_canonical_t { digest: [0; 4] };
|
|
unsafe { XXH32_canonicalFromHash(&mut canonical32, 0x0123_4567) };
|
|
assert_eq!(canonical32.digest, [1, 0x23, 0x45, 0x67]);
|
|
assert_eq!(
|
|
unsafe { XXH32_hashFromCanonical(&canonical32) },
|
|
0x0123_4567
|
|
);
|
|
|
|
let mut canonical = XXH64_canonical_t { digest: [0; 8] };
|
|
unsafe { XXH64_canonicalFromHash(&mut canonical, 0x0123_4567_89ab_cdef) };
|
|
assert_eq!(
|
|
canonical.digest,
|
|
[1, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]
|
|
);
|
|
assert_eq!(
|
|
unsafe { XXH64_hashFromCanonical(&canonical) },
|
|
0x0123_4567_89ab_cdef
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn null_empty_input_is_supported() {
|
|
assert_eq!(unsafe { XXH32(std::ptr::null(), 0, 0) }, 0x02cc_5d05);
|
|
let mut state32: XXH32_state_t = unsafe { zeroed() };
|
|
assert_eq!(unsafe { XXH32_reset(&mut state32, 0) }, XXH_OK);
|
|
assert_eq!(
|
|
unsafe { XXH32_update(&mut state32, std::ptr::null(), 0) },
|
|
XXH_OK
|
|
);
|
|
assert_eq!(unsafe { XXH32_digest(&state32) }, 0x02cc_5d05);
|
|
|
|
assert_eq!(
|
|
unsafe { XXH64(std::ptr::null(), 0, 0) },
|
|
0xef46_db37_51d8_e999
|
|
);
|
|
let mut state: XXH64_state_t = unsafe { zeroed() };
|
|
assert_eq!(unsafe { XXH64_reset(&mut state, 0) }, XXH_OK);
|
|
assert_eq!(
|
|
unsafe { XXH64_update(&mut state, std::ptr::null(), 0) },
|
|
XXH_OK
|
|
);
|
|
assert_eq!(unsafe { XXH64_digest(&state) }, 0xef46_db37_51d8_e999);
|
|
}
|
|
}
|