#![allow(dead_code)]
#![allow(unused_imports)]
use std::{
cmp, env, fmt, fs,
hash::Hasher,
io::{self, BufRead, BufReader, Read, Write},
path::Path,
process,
time::Instant,
};
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;
const XXH3_STRIPE_LEN: usize = 64;
const XXH3_ACC_NB: usize = 8;
const XXH3_SECRET_SIZE_MIN: usize = 136;
const XXH3_SECRET_DEFAULT_SIZE: usize = 192;
const XXH3_INTERNALBUFFER_SIZE: usize = 256;
const XXH3_INTERNALBUFFER_STRIPES: usize = XXH3_INTERNALBUFFER_SIZE / XXH3_STRIPE_LEN; const XXH3_MIDSIZE_MAX: usize = 240;
const XXH3_SECRET_MERGEACCS_START: usize = 11;
#[rustfmt::skip]
const XXH3_DEFAULT_SECRET: [u8; 192] = [
0xb8, 0xfe, 0x6c, 0x39, 0x23, 0xa4, 0x4b, 0xbe,
0x7c, 0x01, 0x81, 0x2c, 0xf7, 0x21, 0xad, 0x1c,
0xde, 0xd4, 0x6d, 0xe9, 0x83, 0x90, 0x97, 0xdb,
0x72, 0x40, 0xa4, 0xa4, 0xb7, 0xb3, 0x67, 0x1f,
0xcb, 0x79, 0xe6, 0x4e, 0xcc, 0xc0, 0xe5, 0x78,
0x82, 0x5a, 0xd0, 0x7d, 0xcc, 0xff, 0x72, 0x21,
0xb8, 0x08, 0x46, 0x74, 0xf7, 0x43, 0x24, 0x8e,
0xe0, 0x35, 0x90, 0xe6, 0x81, 0x3a, 0x26, 0x4c,
0x3c, 0x28, 0x52, 0xbb, 0x91, 0xc3, 0x00, 0xcb,
0x88, 0xd0, 0x65, 0x8b, 0x1b, 0x53, 0x2e, 0xa3,
0x71, 0x64, 0x48, 0x97, 0xa2, 0x0d, 0xf9, 0x4e,
0x38, 0x19, 0xef, 0x46, 0xa9, 0xde, 0xac, 0xd8,
0xa8, 0xfa, 0x76, 0x3f, 0xe3, 0x9c, 0x34, 0x3f,
0xf9, 0xdc, 0xbb, 0xc7, 0xc7, 0x0b, 0x4f, 0x1d,
0x8a, 0x51, 0xe0, 0x4b, 0xcd, 0xb4, 0x59, 0x31,
0xc8, 0x9f, 0x7e, 0xc9, 0xd9, 0x78, 0x73, 0x64,
0xea, 0xc5, 0xac, 0x83, 0x34, 0xd3, 0xeb, 0xc3,
0xc5, 0x81, 0xa0, 0xff, 0xfa, 0x13, 0x63, 0xeb,
0x17, 0x0d, 0xdd, 0x51, 0xb7, 0xf0, 0xda, 0x49,
0xd3, 0x16, 0x55, 0x26, 0x29, 0xd4, 0x68, 0x9e,
0x2b, 0x16, 0xbe, 0x58, 0x7d, 0x47, 0xa1, 0xfc,
0x8f, 0xf8, 0xb8, 0xd1, 0x7a, 0xd0, 0x31, 0xce,
0x45, 0xcb, 0x3a, 0x8f, 0x95, 0x16, 0x04, 0x28,
0xaf, 0xd7, 0xfb, 0xca, 0xbb, 0x4b, 0x40, 0x7e,
];
#[inline(always)]
fn rotl32(x: u32, r: u32) -> u32 {
(x << r) | (x >> (32 - r))
}
#[inline(always)]
fn rotl64(x: u64, r: u32) -> u64 {
(x << r) | (x >> (64 - r))
}
#[inline(always)]
fn read_u32_le(buf: &[u8]) -> u32 {
u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]])
}
#[inline(always)]
fn read_u64_le(buf: &[u8]) -> u64 {
u64::from_le_bytes([
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
])
}
#[inline(always)]
fn read_u32_le_safe(buf: &[u8], offset: usize) -> u32 {
if offset + 4 <= buf.len() {
read_u32_le(&buf[offset..])
} else {
let mut b = [0u8; 4];
let len = buf.len().saturating_sub(offset);
b[..len].copy_from_slice(&buf[offset..offset + len]);
u32::from_le_bytes(b)
}
}
#[inline(always)]
fn read_u64_le_safe(buf: &[u8], offset: usize) -> u64 {
if offset + 8 <= buf.len() {
read_u64_le(&buf[offset..])
} else {
let mut b = [0u8; 8];
let len = buf.len().saturating_sub(offset);
b[..len].copy_from_slice(&buf[offset..offset + len]);
u64::from_le_bytes(b)
}
}
#[inline(always)]
fn xxh32_round(acc: u32, lane: u32) -> u32 {
acc.wrapping_add(lane.wrapping_mul(PRIME32_2))
.rotate_left(13)
.wrapping_mul(PRIME32_1)
}
#[inline(always)]
fn xxh32_avalanche(mut h: u32) -> u32 {
h ^= h >> 15;
h = h.wrapping_mul(PRIME32_2);
h ^= h >> 13;
h = h.wrapping_mul(PRIME32_3);
h ^= h >> 16;
h
}
#[inline(always)]
fn xxh64_round(acc: u64, lane: u64) -> u64 {
acc.wrapping_add(lane.wrapping_mul(PRIME64_2))
.rotate_left(31)
.wrapping_mul(PRIME64_1)
}
#[inline(always)]
fn xxh64_avalanche(mut h: u64) -> u64 {
h ^= h >> 33;
h = h.wrapping_mul(PRIME64_2);
h ^= h >> 29;
h = h.wrapping_mul(PRIME64_3);
h ^= h >> 32;
h
}
#[inline(always)]
fn xxh3_avalanche(mut h: u64) -> u64 {
h ^= h >> 37;
h = h.wrapping_mul(0x1656_67B1_9E37_79F9);
h ^= h >> 32;
h
}
#[inline(always)]
fn xxh3_rrmxmx(mut h: u64, len: u64) -> u64 {
h ^= rotl64(h, 49) ^ rotl64(h, 24);
h = h.wrapping_mul(0x9FB2_1C65_1E98_DF25);
h ^= (h >> 35).wrapping_add(len);
h = h.wrapping_mul(0x9FB2_1C65_1E98_DF25);
h ^= h >> 28;
h
}
#[inline(always)]
fn mul128_64(a: u64, b: u64) -> (u64, u64) {
let wide = (a as u128).wrapping_mul(b as u128);
(wide as u64, (wide >> 64) as u64)
}
pub fn xxh32(input: &[u8], seed: u32) -> u32 {
let len = input.len();
let mut h: u32;
if len >= 16 {
let mut v1 = seed.wrapping_add(PRIME32_1).wrapping_add(PRIME32_2);
let mut v2 = seed.wrapping_add(PRIME32_2);
let mut v3 = seed;
let mut v4 = seed.wrapping_sub(PRIME32_1);
let mut offset = 0usize;
let limit = len - 16;
while offset <= limit {
v1 = xxh32_round(v1, read_u32_le(&input[offset..]));
v2 = xxh32_round(v2, read_u32_le(&input[offset + 4..]));
v3 = xxh32_round(v3, read_u32_le(&input[offset + 8..]));
v4 = xxh32_round(v4, read_u32_le(&input[offset + 12..]));
offset += 16;
}
h = rotl32(v1, 1)
.wrapping_add(rotl32(v2, 7))
.wrapping_add(rotl32(v3, 12))
.wrapping_add(rotl32(v4, 18));
} else {
h = seed.wrapping_add(PRIME32_5);
}
h = h.wrapping_add(len as u32);
let mut offset = (len / 16) * 16;
while offset + 4 <= len {
h = h.wrapping_add(read_u32_le(&input[offset..]).wrapping_mul(PRIME32_3));
h = rotl32(h, 17).wrapping_mul(PRIME32_4);
offset += 4;
}
while offset < len {
h = h.wrapping_add((input[offset] as u32).wrapping_mul(PRIME32_5));
h = rotl32(h, 11).wrapping_mul(PRIME32_1);
offset += 1;
}
xxh32_avalanche(h)
}
pub fn xxh64(input: &[u8], seed: u64) -> u64 {
let len = input.len();
let mut h: u64;
if len >= 32 {
let mut v1 = seed.wrapping_add(PRIME64_1).wrapping_add(PRIME64_2);
let mut v2 = seed.wrapping_add(PRIME64_2);
let mut v3 = seed;
let mut v4 = seed.wrapping_sub(PRIME64_1);
let mut offset = 0usize;
let limit = len - 32;
while offset <= limit {
v1 = xxh64_round(v1, read_u64_le(&input[offset..]));
v2 = xxh64_round(v2, read_u64_le(&input[offset + 8..]));
v3 = xxh64_round(v3, read_u64_le(&input[offset + 16..]));
v4 = xxh64_round(v4, read_u64_le(&input[offset + 24..]));
offset += 32;
}
h = rotl64(v1, 1)
.wrapping_add(rotl64(v2, 7))
.wrapping_add(rotl64(v3, 12))
.wrapping_add(rotl64(v4, 18));
} else {
h = seed.wrapping_add(PRIME64_5);
}
h = h.wrapping_add(len as u64);
let mut offset = (len / 32) * 32;
while offset + 8 <= len {
let lane = read_u64_le(&input[offset..]);
h = h
.wrapping_add(xxh64_round(0, lane))
.rotate_left(27)
.wrapping_mul(PRIME64_1)
.wrapping_add(PRIME64_4);
offset += 8;
}
while offset + 4 <= len {
let lane = read_u32_le(&input[offset..]) as u64;
h = h
.wrapping_add(lane.wrapping_mul(PRIME64_1))
.rotate_left(23)
.wrapping_mul(PRIME64_2)
.wrapping_add(PRIME64_3);
offset += 4;
}
while offset < len {
let lane = input[offset] as u64;
h = h
.wrapping_add(lane.wrapping_mul(PRIME64_5))
.rotate_left(11)
.wrapping_mul(PRIME64_1);
offset += 1;
}
xxh64_avalanche(h)
}
#[inline]
fn xxh3_accumulate(acc: &mut [u64; 8], input: &[u8], secret: &[u8]) {
debug_assert!(input.len() >= 64);
debug_assert!(secret.len() >= 64);
for i in 0..8 {
let data_val = read_u64_le(&input[8 * i..]);
let data_key = data_val ^ read_u64_le(&secret[8 * i..]);
acc[i ^ 1] = acc[i ^ 1].wrapping_add(data_val);
let lo = (data_key & 0xFFFF_FFFF) as u64;
let hi = data_key >> 32;
acc[i] = acc[i].wrapping_add(lo.wrapping_mul(hi));
}
}
#[inline]
fn xxh3_scramble_acc(acc: &mut [u64; 8], secret: &[u8]) {
debug_assert!(secret.len() >= 64);
for i in 0..8 {
acc[i] ^= acc[i] >> 47;
let key = read_u64_le(&secret[8 * i..]);
acc[i] ^= key;
acc[i] = acc[i].wrapping_mul(PRIME64_1);
}
}
#[inline]
fn xxh3_merge_accs(acc: &[u64; 8], secret: &[u8], start: usize, len: u64) -> u64 {
let mut h = len;
let nb_rounds = 8;
for i in 0..nb_rounds {
let idx = start + 8 * i;
let key = if idx + 8 <= secret.len() {
read_u64_le(&secret[idx..])
} else {
read_u64_le_safe(secret, idx)
};
let data_val = acc[i].wrapping_mul(PRIME64_2);
let mixed = data_val.rotate_left(31).wrapping_mul(PRIME64_1);
h = h.wrapping_add(mixed) ^ key;
h = h.rotate_left(27).wrapping_mul(PRIME64_1).wrapping_add(PRIME64_4);
}
h
}
fn xxh3_len_0to16_64b(input: &[u8], len: usize, secret: &[u8], seed: u64) -> u64 {
if len > 8 {
if len > 8 {
let bitflip1 =
read_u64_le(&secret[24..]) ^ read_u64_le(&secret[32..]);
let bitflip2 =
read_u64_le(&secret[40..]) ^ read_u64_le(&secret[48..]);
let input_lo = read_u64_le(input) ^ bitflip1.wrapping_sub(seed);
let input_hi =
read_u64_le(&input[len - 8..]) ^ bitflip2.wrapping_add(seed);
let acc = (len as u64)
.wrapping_add(input_lo)
.wrapping_add(input_hi);
return xxh3_avalanche(acc);
}
}
if len >= 4 {
let bitflip1 = read_u64_le(&secret[16..]) ^ read_u64_le(&secret[24..]);
let bitflip2 = read_u64_le(&secret[32..]) ^ read_u64_le(&secret[40..]);
let input_lo = read_u32_le(input);
let input_hi = read_u32_le(&input[len - 4..]);
let input64 = (input_hi as u64).wrapping_add((input_lo as u64) << 32);
let keyed = input64 ^ (bitflip1.wrapping_sub(seed));
let mut acc = (len as u64)
.wrapping_add(keyed)
.wrapping_add(bitflip2.wrapping_add(seed));
acc = acc.rotate_left(17).wrapping_mul(PRIME64_1);
acc ^= acc >> 33;
acc = acc.wrapping_mul(PRIME64_2);
acc ^= acc >> 29;
acc = acc.wrapping_mul(PRIME64_3);
acc ^= acc >> 32;
return acc;
}
if len > 0 {
let c1 = input[0] as u32;
let c2 = input[len >> 1] as u32;
let c3 = input[len - 1] as u32;
let combined = (c1 << 16) | (c2 << 24) | c3;
let key_low = combined ^ (read_u32_le(&secret[0..]) >> 8);
let key_high =
((combined >> 1) ^ (read_u32_le(&secret[4..]) >> 24)).wrapping_add(len as u32);
let key = (key_high as u64) << 32 | key_low as u64;
let mut h = key ^ seed;
h = h.wrapping_mul(PRIME64_1).wrapping_add((len as u64) << 2);
h ^= h >> 33;
h = h.wrapping_mul(PRIME64_2);
h ^= h >> 29;
h = h.wrapping_mul(PRIME64_3);
h ^= h >> 32;
return h;
}
let a = read_u64_le(&secret[56..]);
let b = read_u64_le(&secret[64..]);
xxh64_avalanche(seed ^ a ^ b)
}
fn xxh3_len_17to128_64b(input: &[u8], len: usize, secret: &[u8], seed: u64) -> u64 {
let nb_rounds = (len - 1) / 32;
let mut acc = [0u64; 8];
for i in 0..4 {
let s_off = i * 32;
acc[2 * i] = read_u64_le(&secret[s_off..]);
acc[2 * i + 1] = read_u64_le(&secret[s_off + 8..]);
}
for a in acc.iter_mut() {
*a = a.wrapping_add(seed);
}
for r in 0..nb_rounds {
for i in 0..4 {
let data_off = r * 32 + i * 8;
let data_val = read_u64_le(&input[data_off..]);
let secret_off = (r * 32 + i * 8) % XXH3_SECRET_DEFAULT_SIZE;
let secret_val = read_u64_le(&secret[secret_off..]);
let data_key = data_val ^ secret_val;
acc[i * 2] = acc[i * 2].wrapping_add(data_val);
acc[i * 2 + 1] = acc[i * 2 + 1]
.wrapping_add(((data_key & 0xFFFF_FFFF) as u64)
.wrapping_mul(data_key >> 32));
}
}
let mut h = (len as u64).wrapping_mul(PRIME64_1);
for i in 0..8 {
h = h.wrapping_add(acc[i].wrapping_mul(PRIME64_2));
h = h.rotate_left(31);
h = h.wrapping_mul(PRIME64_1);
}
h ^= h >> 33;
h = h.wrapping_mul(PRIME64_2);
h ^= h >> 29;
h = h.wrapping_mul(PRIME64_3);
h ^= h >> 32;
h
}
fn xxh3_len_129to240_64b(input: &[u8], len: usize, secret: &[u8], seed: u64) -> u64 {
let nb_rounds = len / 32;
let mut acc = [0u64; 8];
for i in 0..4 {
let s_off = i * 32;
acc[2 * i] = read_u64_le(&secret[s_off..]);
acc[2 * i + 1] = read_u64_le(&secret[s_off + 8..]);
}
for a in acc.iter_mut() {
*a = a.wrapping_add(seed);
}
let rounds = cmp::min(nb_rounds, 4);
for r in 0..rounds {
for i in 0..4 {
let data_off = r * 32 + i * 8;
let data_val = read_u64_le(&input[data_off..]);
let secret_off = (r * 32 + i * 8) % XXH3_SECRET_DEFAULT_SIZE;
let secret_val = read_u64_le(&secret[secret_off..]);
let data_key = data_val ^ secret_val;
acc[i * 2] = acc[i * 2].wrapping_add(data_val);
acc[i * 2 + 1] = acc[i * 2 + 1]
.wrapping_add(((data_key & 0xFFFF_FFFF) as u64)
.wrapping_mul(data_key >> 32));
}
for a in acc.iter_mut() {
*a ^= *a >> 47;
*a = a.wrapping_mul(PRIME64_1);
}
}
let mut h = (len as u64).wrapping_mul(PRIME64_1);
for i in (128..len).step_by(8) {
let end = cmp::min(i + 8, len);
let mut buf = [0u8; 8];
buf[..end - i].copy_from_slice(&input[i..end]);
let data_val = u64::from_le_bytes(buf);
h = h.wrapping_add(data_val.wrapping_mul(PRIME64_1));
h = h.rotate_left(37).wrapping_mul(PRIME64_2);
}
for i in 0..8 {
h = h.wrapping_add(acc[i].wrapping_mul(PRIME64_2));
h = h.rotate_left(31);
h = h.wrapping_mul(PRIME64_1);
}
h ^= h >> 33;
h = h.wrapping_mul(PRIME64_2);
h ^= h >> 29;
h = h.wrapping_mul(PRIME64_3);
h ^= h >> 32;
h
}
fn xxh3_hash_long_64b_internal(
input: &[u8],
len: usize,
secret: &[u8],
seed: u64,
) -> u64 {
let secret_limit = XXH3_SECRET_DEFAULT_SIZE - XXH3_STRIPE_LEN;
let nb_rounds = len / XXH3_STRIPE_LEN;
let nb_blocks = nb_rounds / XXH3_INTERNALBUFFER_STRIPES;
let mut acc = [0u64; 8];
for i in 0..8 {
acc[i] = read_u64_le(&secret[8 * i..]) ^ seed;
}
let mut offset = 0usize;
for _ in 0..nb_blocks {
for _ in 0..XXH3_INTERNALBUFFER_STRIPES {
xxh3_accumulate(&mut acc, &input[offset..], secret);
offset += XXH3_STRIPE_LEN;
}
xxh3_scramble_acc(&mut acc, &secret[secret_limit..]);
}
while offset + XXH3_STRIPE_LEN <= len {
xxh3_accumulate(&mut acc, &input[offset..], secret);
offset += XXH3_STRIPE_LEN;
}
if offset < len {
let remaining = len - offset;
let mut buf = [0u8; XXH3_STRIPE_LEN];
buf[..remaining].copy_from_slice(&input[offset..]);
xxh3_accumulate(&mut acc, &buf, secret);
}
let mut h = xxh3_merge_accs(
&acc,
secret,
XXH3_SECRET_MERGEACCS_START,
(len as u64).wrapping_mul(PRIME64_1),
);
h ^= h >> 33;
h = h.wrapping_mul(PRIME64_2);
h ^= h >> 29;
h = h.wrapping_mul(PRIME64_3);
h ^= h >> 32;
h
}
pub fn xxh3_64(input: &[u8]) -> u64 {
xxh3_64_with_secret(input, &XXH3_DEFAULT_SECRET, 0)
}
pub fn xxh3_64_with_secret(input: &[u8], secret: &[u8], seed: u64) -> u64 {
let len = input.len();
let local_secret: [u8; 192] = if secret.len() >= 192 {
secret[..192].try_into().unwrap()
} else if secret.is_empty() {
XXH3_DEFAULT_SECRET
} else {
let mut s = [0u8; 192];
for i in 0..192 {
s[i] = secret[i % secret.len()];
}
s
};
if len <= 16 {
return xxh3_len_0to16_64b(input, len, &local_secret, seed);
}
if len <= 128 {
return xxh3_len_17to128_64b(input, len, &local_secret, seed);
}
if len <= 240 {
return xxh3_len_129to240_64b(input, len, &local_secret, seed);
}
xxh3_hash_long_64b_internal(input, len, &local_secret, seed)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct XXH128 {
pub lo: u64,
pub hi: u64,
}
impl XXH128 {
pub const fn new(lo: u64, hi: u64) -> Self {
Self { lo, hi }
}
}
impl fmt::Display for XXH128 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:016x}{:016x}", self.hi, self.lo)
}
}
impl fmt::LowerHex for XXH128 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:016x}{:016x}", self.hi, self.lo)
}
}
pub fn xxh3_128(input: &[u8]) -> XXH128 {
xxh3_128_with_secret(input, &XXH3_DEFAULT_SECRET, 0)
}
pub fn xxh3_128_with_secret(input: &[u8], secret: &[u8], seed: u64) -> XXH128 {
let len = input.len();
let local_secret: [u8; 192] = if secret.len() >= 192 {
secret[..192].try_into().unwrap()
} else if secret.is_empty() {
XXH3_DEFAULT_SECRET
} else {
let mut s = [0u8; 192];
for i in 0..192 {
s[i] = secret[i % secret.len()];
}
s
};
if len <= 16 {
return xxh3_len_0to16_128b(input, len, &local_secret, seed);
}
if len <= 128 {
return xxh3_len_17to128_128b(input, len, &local_secret, seed);
}
if len <= 240 {
return xxh3_len_129to240_128b(input, len, &local_secret, seed);
}
xxh3_hash_long_128b_internal(input, len, &local_secret, seed)
}
fn xxh3_len_0to16_128b(input: &[u8], len: usize, secret: &[u8], seed: u64) -> XXH128 {
if len >= 8 {
let lo = xxh3_len_0to16_64b(input, len, secret, seed);
let hi = xxh3_len_0to16_64b(
input,
len,
&shift_secret_left(secret, 32),
seed ^ PRIME64_1,
);
return XXH128::new(lo, hi);
}
let lo = xxh3_len_0to16_64b(input, len, secret, seed);
let hi = xxh3_avalanche(lo ^ PRIME64_1);
XXH128::new(lo, hi)
}
fn xxh3_len_17to128_128b(input: &[u8], len: usize, secret: &[u8], seed: u64) -> XXH128 {
let mut acc = [0u64; 8];
for i in 0..4 {
let s_off = i * 32;
acc[2 * i] = read_u64_le(&secret[s_off..]);
acc[2 * i + 1] = read_u64_le(&secret[s_off + 8..]);
}
for a in acc.iter_mut() {
*a = a.wrapping_add(seed);
}
let nb_rounds = (len - 1) / 32;
for r in 0..nb_rounds {
for i in 0..4 {
let data_off = r * 32 + i * 8;
let data_val = read_u64_le(&input[data_off..]);
let secret_off = (r * 32 + i * 8) % XXH3_SECRET_DEFAULT_SIZE;
let secret_val = read_u64_le(&secret[secret_off..]);
let data_key = data_val ^ secret_val;
acc[i * 2] = acc[i * 2].wrapping_add(data_val);
acc[i * 2 + 1] = acc[i * 2 + 1]
.wrapping_add(((data_key & 0xFFFF_FFFF) as u64)
.wrapping_mul(data_key >> 32));
}
}
let lo = xxh3_merge_accs(&acc, secret, XXH3_SECRET_MERGEACCS_START, (len as u64).wrapping_mul(PRIME64_1));
let hi = xxh3_merge_accs(
&acc,
secret,
XXH3_SECRET_DEFAULT_SIZE - 64 - XXH3_SECRET_MERGEACCS_START,
!(len as u64).wrapping_mul(PRIME64_2),
);
XXH128::new(lo, hi)
}
fn xxh3_len_129to240_128b(input: &[u8], len: usize, secret: &[u8], seed: u64) -> XXH128 {
let nb_rounds = len / 32;
let mut acc = [0u64; 8];
for i in 0..4 {
let s_off = i * 32;
acc[2 * i] = read_u64_le(&secret[s_off..]);
acc[2 * i + 1] = read_u64_le(&secret[s_off + 8..]);
}
for a in acc.iter_mut() {
*a = a.wrapping_add(seed);
}
let rounds = cmp::min(nb_rounds, 4);
for r in 0..rounds {
for i in 0..4 {
let data_off = r * 32 + i * 8;
let data_val = read_u64_le(&input[data_off..]);
let secret_off = (r * 32 + i * 8) % XXH3_SECRET_DEFAULT_SIZE;
let secret_val = read_u64_le(&secret[secret_off..]);
let data_key = data_val ^ secret_val;
acc[i * 2] = acc[i * 2].wrapping_add(data_val);
acc[i * 2 + 1] = acc[i * 2 + 1]
.wrapping_add(((data_key & 0xFFFF_FFFF) as u64)
.wrapping_mul(data_key >> 32));
}
for a in acc.iter_mut() {
*a ^= *a >> 47;
*a = a.wrapping_mul(PRIME64_1);
}
}
let lo = xxh3_merge_accs(&acc, secret, XXH3_SECRET_MERGEACCS_START, (len as u64).wrapping_mul(PRIME64_1));
let hi = xxh3_merge_accs(
&acc,
secret,
XXH3_SECRET_DEFAULT_SIZE - 64 - XXH3_SECRET_MERGEACCS_START,
!(len as u64).wrapping_mul(PRIME64_2),
);
XXH128::new(lo, hi)
}
fn xxh3_hash_long_128b_internal(
input: &[u8],
len: usize,
secret: &[u8],
seed: u64,
) -> XXH128 {
let secret_limit = XXH3_SECRET_DEFAULT_SIZE - XXH3_STRIPE_LEN;
let nb_rounds = len / XXH3_STRIPE_LEN;
let nb_blocks = nb_rounds / XXH3_INTERNALBUFFER_STRIPES;
let mut acc = [0u64; 8];
for i in 0..8 {
acc[i] = read_u64_le(&secret[8 * i..]) ^ seed;
}
let mut offset = 0usize;
for _ in 0..nb_blocks {
for _ in 0..XXH3_INTERNALBUFFER_STRIPES {
xxh3_accumulate(&mut acc, &input[offset..], secret);
offset += XXH3_STRIPE_LEN;
}
xxh3_scramble_acc(&mut acc, &secret[secret_limit..]);
}
while offset + XXH3_STRIPE_LEN <= len {
xxh3_accumulate(&mut acc, &input[offset..], secret);
offset += XXH3_STRIPE_LEN;
}
if offset < len {
let remaining = len - offset;
let mut buf = [0u8; XXH3_STRIPE_LEN];
buf[..remaining].copy_from_slice(&input[offset..]);
xxh3_accumulate(&mut acc, &buf, secret);
}
let lo = xxh3_merge_accs(&acc, secret, XXH3_SECRET_MERGEACCS_START, (len as u64).wrapping_mul(PRIME64_1));
let hi = xxh3_merge_accs(
&acc,
secret,
XXH3_SECRET_DEFAULT_SIZE - 64 - XXH3_SECRET_MERGEACCS_START,
!(len as u64).wrapping_mul(PRIME64_2),
);
XXH128::new(lo, hi)
}
fn shift_secret_left(secret: &[u8; 192], n: usize) -> [u8; 192] {
let mut s = [0u8; 192];
for i in 0..192 {
s[i] = secret[(i + n) % 192];
}
s
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Canonical32([u8; 4]);
impl Canonical32 {
pub fn from_u32(hash: u32) -> Self {
Self(hash.to_be_bytes())
}
pub fn to_u32(&self) -> u32 {
u32::from_be_bytes(self.0)
}
pub fn as_bytes(&self) -> &[u8; 4] {
&self.0
}
}
impl fmt::Display for Canonical32 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for b in &self.0 {
write!(f, "{:02x}", b)?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Canonical64([u8; 8]);
impl Canonical64 {
pub fn from_u64(hash: u64) -> Self {
Self(hash.to_be_bytes())
}
pub fn to_u64(&self) -> u64 {
u64::from_be_bytes(self.0)
}
pub fn as_bytes(&self) -> &[u8; 8] {
&self.0
}
}
impl fmt::Display for Canonical64 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for b in &self.0 {
write!(f, "{:02x}", b)?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Canonical128([u8; 16]);
impl Canonical128 {
pub fn from_xxh128(hash: XXH128) -> Self {
let mut bytes = [0u8; 16];
bytes[..8].copy_from_slice(&hash.hi.to_be_bytes());
bytes[8..].copy_from_slice(&hash.lo.to_be_bytes());
Self(bytes)
}
pub fn to_xxh128(&self) -> XXH128 {
let hi = u64::from_be_bytes(self.0[..8].try_into().unwrap());
let lo = u64::from_be_bytes(self.0[8..].try_into().unwrap());
XXH128::new(lo, hi)
}
pub fn as_bytes(&self) -> &[u8; 16] {
&self.0
}
}
impl fmt::Display for Canonical128 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for b in &self.0 {
write!(f, "{:02x}", b)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct XXH32State {
v1: u32,
v2: u32,
v3: u32,
v4: u32,
total_len: usize,
mem_size: usize,
mem: [u8; 16],
seed: u32,
}
impl XXH32State {
const INTERNAL_BUFFER_SIZE: usize = 16;
pub fn new(seed: u32) -> Self {
Self {
v1: seed.wrapping_add(PRIME32_1).wrapping_add(PRIME32_2),
v2: seed.wrapping_add(PRIME32_2),
v3: seed,
v4: seed.wrapping_sub(PRIME32_1),
total_len: 0,
mem_size: 0,
mem: [0u8; 16],
seed,
}
}
pub fn update(&mut self, input: &[u8]) {
self.total_len = self.total_len.wrapping_add(input.len());
let mut offset = 0usize;
let len = input.len();
if self.mem_size > 0 {
let fill = cmp::min(Self::INTERNAL_BUFFER_SIZE - self.mem_size, len);
self.mem[self.mem_size..self.mem_size + fill]
.copy_from_slice(&input[..fill]);
self.mem_size += fill;
offset = fill;
if self.mem_size < Self::INTERNAL_BUFFER_SIZE {
return;
}
self.v1 = xxh32_round(self.v1, read_u32_le(&self.mem[0..]));
self.v2 = xxh32_round(self.v2, read_u32_le(&self.mem[4..]));
self.v3 = xxh32_round(self.v3, read_u32_le(&self.mem[8..]));
self.v4 = xxh32_round(self.v4, read_u32_le(&self.mem[12..]));
self.mem_size = 0;
}
let limit = len.saturating_sub(16);
while offset <= limit {
self.v1 = xxh32_round(self.v1, read_u32_le(&input[offset..]));
self.v2 = xxh32_round(self.v2, read_u32_le(&input[offset + 4..]));
self.v3 = xxh32_round(self.v3, read_u32_le(&input[offset + 8..]));
self.v4 = xxh32_round(self.v4, read_u32_le(&input[offset + 12..]));
offset += 16;
}
let remaining = len - offset;
if remaining > 0 {
self.mem[..remaining].copy_from_slice(&input[offset..]);
self.mem_size = remaining;
}
}
pub fn digest(&self) -> u32 {
let mut h = if self.total_len >= 16 {
rotl32(self.v1, 1)
.wrapping_add(rotl32(self.v2, 7))
.wrapping_add(rotl32(self.v3, 12))
.wrapping_add(rotl32(self.v4, 18))
} else {
self.seed.wrapping_add(PRIME32_5)
};
h = h.wrapping_add(self.total_len as u32);
let mut offset = 0usize;
while offset + 4 <= self.mem_size {
h = h.wrapping_add(read_u32_le(&self.mem[offset..]).wrapping_mul(PRIME32_3));
h = rotl32(h, 17).wrapping_mul(PRIME32_4);
offset += 4;
}
while offset < self.mem_size {
h = h.wrapping_add((self.mem[offset] as u32).wrapping_mul(PRIME32_5));
h = rotl32(h, 11).wrapping_mul(PRIME32_1);
offset += 1;
}
xxh32_avalanche(h)
}
pub fn reset(&mut self) {
*self = Self::new(self.seed);
}
}
#[derive(Debug, Clone)]
pub struct XXH64State {
v1: u64,
v2: u64,
v3: u64,
v4: u64,
total_len: usize,
mem_size: usize,
mem: [u8; 32],
seed: u64,
}
impl XXH64State {
const INTERNAL_BUFFER_SIZE: usize = 32;
pub fn new(seed: u64) -> Self {
Self {
v1: seed.wrapping_add(PRIME64_1).wrapping_add(PRIME64_2),
v2: seed.wrapping_add(PRIME64_2),
v3: seed,
v4: seed.wrapping_sub(PRIME64_1),
total_len: 0,
mem_size: 0,
mem: [0u8; 32],
seed,
}
}
pub fn update(&mut self, input: &[u8]) {
self.total_len = self.total_len.wrapping_add(input.len());
let mut offset = 0usize;
let len = input.len();
if self.mem_size > 0 {
let fill = cmp::min(Self::INTERNAL_BUFFER_SIZE - self.mem_size, len);
self.mem[self.mem_size..self.mem_size + fill]
.copy_from_slice(&input[..fill]);
self.mem_size += fill;
offset = fill;
if self.mem_size < Self::INTERNAL_BUFFER_SIZE {
return;
}
self.v1 = xxh64_round(self.v1, read_u64_le(&self.mem[0..]));
self.v2 = xxh64_round(self.v2, read_u64_le(&self.mem[8..]));
self.v3 = xxh64_round(self.v3, read_u64_le(&self.mem[16..]));
self.v4 = xxh64_round(self.v4, read_u64_le(&self.mem[24..]));
self.mem_size = 0;
}
let limit = len.saturating_sub(32);
while offset <= limit {
self.v1 = xxh64_round(self.v1, read_u64_le(&input[offset..]));
self.v2 = xxh64_round(self.v2, read_u64_le(&input[offset + 8..]));
self.v3 = xxh64_round(self.v3, read_u64_le(&input[offset + 16..]));
self.v4 = xxh64_round(self.v4, read_u64_le(&input[offset + 24..]));
offset += 32;
}
let remaining = len - offset;
if remaining > 0 {
self.mem[..remaining].copy_from_slice(&input[offset..]);
self.mem_size = remaining;
}
}
pub fn digest(&self) -> u64 {
let mut h = if self.total_len >= 32 {
rotl64(self.v1, 1)
.wrapping_add(rotl64(self.v2, 7))
.wrapping_add(rotl64(self.v3, 12))
.wrapping_add(rotl64(self.v4, 18))
} else {
self.seed.wrapping_add(PRIME64_5)
};
h = h.wrapping_add(self.total_len as u64);
let mut offset = 0usize;
while offset + 8 <= self.mem_size {
let lane = read_u64_le(&self.mem[offset..]);
h = h
.wrapping_add(xxh64_round(0, lane))
.rotate_left(27)
.wrapping_mul(PRIME64_1)
.wrapping_add(PRIME64_4);
offset += 8;
}
while offset + 4 <= self.mem_size {
let lane = read_u32_le(&self.mem[offset..]) as u64;
h = h
.wrapping_add(lane.wrapping_mul(PRIME64_1))
.rotate_left(23)
.wrapping_mul(PRIME64_2)
.wrapping_add(PRIME64_3);
offset += 4;
}
while offset < self.mem_size {
let lane = self.mem[offset] as u64;
h = h
.wrapping_add(lane.wrapping_mul(PRIME64_5))
.rotate_left(11)
.wrapping_mul(PRIME64_1);
offset += 1;
}
xxh64_avalanche(h)
}
pub fn reset(&mut self) {
*self = Self::new(self.seed);
}
}
#[derive(Debug, Clone)]
struct XXH3StateShared {
acc: [u64; 8],
secret: [u8; 192],
buffer: [u8; XXH3_INTERNALBUFFER_SIZE],
buffered_size: usize,
nb_stripes_so_far: usize,
total_len: usize,
seed: u64,
}
impl XXH3StateShared {
fn new(secret: &[u8], seed: u64) -> Self {
let local_secret: [u8; 192] = if secret.len() >= 192 {
secret[..192].try_into().unwrap()
} else if secret.is_empty() {
XXH3_DEFAULT_SECRET
} else {
let mut s = [0u8; 192];
for i in 0..192 {
s[i] = secret[i % secret.len()];
}
s
};
let mut acc = [0u64; 8];
for i in 0..8 {
acc[i] = read_u64_le(&local_secret[8 * i..]) ^ seed;
}
Self {
acc,
secret: local_secret,
buffer: [0u8; XXH3_INTERNALBUFFER_SIZE],
buffered_size: 0,
nb_stripes_so_far: 0,
total_len: 0,
seed,
}
}
fn update(&mut self, input: &[u8]) {
self.total_len = self.total_len.wrapping_add(input.len());
let mut offset = 0usize;
let len = input.len();
if self.buffered_size > 0 {
let fill = cmp::min(XXH3_INTERNALBUFFER_SIZE - self.buffered_size, len);
self.buffer[self.buffered_size..self.buffered_size + fill]
.copy_from_slice(&input[..fill]);
self.buffered_size += fill;
offset = fill;
if self.buffered_size < XXH3_INTERNALBUFFER_SIZE {
return;
}
for stripe in 0..XXH3_INTERNALBUFFER_STRIPES {
let s_off = stripe * XXH3_STRIPE_LEN;
xxh3_accumulate(&mut self.acc, &self.buffer[s_off..], &self.secret);
self.nb_stripes_so_far += 1;
}
let secret_limit = XXH3_SECRET_DEFAULT_SIZE - XXH3_STRIPE_LEN;
xxh3_scramble_acc(&mut self.acc, &self.secret[secret_limit..]);
self.buffered_size = 0;
}
while offset + XXH3_INTERNALBUFFER_SIZE <= len {
for stripe in 0..XXH3_INTERNALBUFFER_STRIPES {
let s_off = offset + stripe * XXH3_STRIPE_LEN;
xxh3_accumulate(&mut self.acc, &input[s_off..], &self.secret);
self.nb_stripes_so_far += 1;
}
let secret_limit = XXH3_SECRET_DEFAULT_SIZE - XXH3_STRIPE_LEN;
xxh3_scramble_acc(&mut self.acc, &self.secret[secret_limit..]);
offset += XXH3_INTERNALBUFFER_SIZE;
}
let remaining = len - offset;
if remaining > 0 {
self.buffer[..remaining].copy_from_slice(&input[offset..]);
self.buffered_size = remaining;
}
}
fn digest_64(&self) -> u64 {
let len = self.total_len;
if len <= XXH3_MIDSIZE_MAX {
if len <= 16 {
let mut data = Vec::with_capacity(len);
data.extend_from_slice(&self.buffer[..self.buffered_size]);
return xxh3_len_0to16_64b(&data, len, &self.secret, self.seed);
}
if len <= 128 {
let mut data = Vec::with_capacity(len);
data.extend_from_slice(&self.buffer[..self.buffered_size]);
return xxh3_len_17to128_64b(&data, len, &self.secret, self.seed);
}
let mut data = Vec::with_capacity(len);
data.extend_from_slice(&self.buffer[..self.buffered_size]);
return xxh3_len_129to240_64b(&data, len, &self.secret, self.seed);
}
let mut acc = self.acc;
let mut offset = 0usize;
while offset + XXH3_STRIPE_LEN <= self.buffered_size {
xxh3_accumulate(&mut acc, &self.buffer[offset..], &self.secret);
offset += XXH3_STRIPE_LEN;
}
if offset < self.buffered_size {
let remaining = self.buffered_size - offset;
let mut buf = [0u8; XXH3_STRIPE_LEN];
buf[..remaining].copy_from_slice(&self.buffer[offset..]);
xxh3_accumulate(&mut acc, &buf, &self.secret);
}
let mut h = xxh3_merge_accs(
&acc,
&self.secret,
XXH3_SECRET_MERGEACCS_START,
(len as u64).wrapping_mul(PRIME64_1),
);
h ^= h >> 33;
h = h.wrapping_mul(PRIME64_2);
h ^= h >> 29;
h = h.wrapping_mul(PRIME64_3);
h ^= h >> 32;
h
}
fn digest_128(&self) -> XXH128 {
let len = self.total_len;
if len <= XXH3_MIDSIZE_MAX {
if len <= 16 {
let data = &self.buffer[..self.buffered_size];
return xxh3_len_0to16_128b(data, len, &self.secret, self.seed);
}
if len <= 128 {
let data = &self.buffer[..self.buffered_size];
return xxh3_len_17to128_128b(data, len, &self.secret, self.seed);
}
let data = &self.buffer[..self.buffered_size];
return xxh3_len_129to240_128b(data, len, &self.secret, self.seed);
}
let mut acc = self.acc;
let mut offset = 0usize;
while offset + XXH3_STRIPE_LEN <= self.buffered_size {
xxh3_accumulate(&mut acc, &self.buffer[offset..], &self.secret);
offset += XXH3_STRIPE_LEN;
}
if offset < self.buffered_size {
let remaining = self.buffered_size - offset;
let mut buf = [0u8; XXH3_STRIPE_LEN];
buf[..remaining].copy_from_slice(&self.buffer[offset..]);
xxh3_accumulate(&mut acc, &buf, &self.secret);
}
let lo = xxh3_merge_accs(
&acc,
&self.secret,
XXH3_SECRET_MERGEACCS_START,
(len as u64).wrapping_mul(PRIME64_1),
);
let hi = xxh3_merge_accs(
&acc,
&self.secret,
XXH3_SECRET_DEFAULT_SIZE - 64 - XXH3_SECRET_MERGEACCS_START,
!(len as u64).wrapping_mul(PRIME64_2),
);
XXH128::new(lo, hi)
}
fn reset(&mut self) {
*self = Self::new(&[], self.seed);
}
}
#[derive(Debug, Clone)]
pub struct XXH3_64State {
inner: XXH3StateShared,
}
impl XXH3_64State {
pub fn new() -> Self {
Self::with_seed(0)
}
pub fn with_seed(seed: u64) -> Self {
Self {
inner: XXH3StateShared::new(&XXH3_DEFAULT_SECRET, seed),
}
}
pub fn with_secret(secret: &[u8], seed: u64) -> Self {
Self {
inner: XXH3StateShared::new(secret, seed),
}
}
pub fn update(&mut self, input: &[u8]) {
self.inner.update(input);
}
pub fn digest(&self) -> u64 {
self.inner.digest_64()
}
pub fn reset(&mut self) {
self.inner.reset();
}
}
impl Default for XXH3_64State {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct XXH3_128State {
inner: XXH3StateShared,
}
impl XXH3_128State {
pub fn new() -> Self {
Self::with_seed(0)
}
pub fn with_seed(seed: u64) -> Self {
Self {
inner: XXH3StateShared::new(&XXH3_DEFAULT_SECRET, seed),
}
}
pub fn with_secret(secret: &[u8], seed: u64) -> Self {
Self {
inner: XXH3StateShared::new(secret, seed),
}
}
pub fn update(&mut self, input: &[u8]) {
self.inner.update(input);
}
pub fn digest(&self) -> XXH128 {
self.inner.digest_128()
}
pub fn reset(&mut self) {
self.inner.reset();
}
}
impl Default for XXH3_128State {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum HashVariant {
XXH32,
XXH64,
XXH128,
}
impl HashVariant {
fn from_argv0(name: &str) -> Self {
let base = Path::new(name)
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or(name)
.to_lowercase();
if base.contains("xxh128") {
HashVariant::XXH128
} else if base.contains("xxh64") {
HashVariant::XXH64
} else {
HashVariant::XXH32
}
}
fn display_name(&self) -> &'static str {
match self {
HashVariant::XXH32 => "XXH32",
HashVariant::XXH64 => "XXH64",
HashVariant::XXH128 => "XXH3_128",
}
}
}
#[derive(Debug)]
struct CliConfig {
variant: HashVariant,
check_mode: bool,
benchmark_mode: bool,
quiet: bool,
status: bool,
warn: bool,
little_endian: bool,
files: Vec<String>,
block_size: usize,
nb_loops: u32,
}
impl Default for CliConfig {
fn default() -> Self {
Self {
variant: HashVariant::XXH64,
check_mode: false,
benchmark_mode: false,
quiet: false,
status: false,
warn: false,
little_endian: false,
files: Vec::new(),
block_size: 64 * 1024,
nb_loops: 3,
}
}
}
fn parse_args() -> CliConfig {
let args: Vec<String> = env::args().collect();
let mut cfg = CliConfig::default();
cfg.variant = HashVariant::from_argv0(&args[0]);
let mut i = 1usize;
while i < args.len() {
match args[i].as_str() {
"-h" | "--help" => {
print_help(cfg.variant);
process::exit(0);
}
"-V" | "--version" => {
println!("tool_xxhash.rs — xxHash clean-room Rust reimplementation v0.1.0");
process::exit(0);
}
"-H" | "--hash" => {
i += 1;
if i < args.len() {
cfg.variant = match args[i].to_lowercase().as_str() {
"32" | "xxh32" => HashVariant::XXH32,
"64" | "xxh64" => HashVariant::XXH64,
"128" | "xxh128" => HashVariant::XXH128,
other => {
eprintln!("Error: unknown hash variant '{}'", other);
process::exit(1);
}
};
}
}
"-c" | "--check" => cfg.check_mode = true,
"-b" | "--benchmark" => cfg.benchmark_mode = true,
"-B" | "--block-size" => {
i += 1;
if i < args.len() {
cfg.block_size = parse_block_size(&args[i]);
}
}
"-q" | "--quiet" => cfg.quiet = true,
"--status" => cfg.status = true,
"-w" | "--warn" => cfg.warn = true,
"--little-endian" => cfg.little_endian = true,
arg if !arg.starts_with('-') => {
cfg.files.push(arg.to_string());
}
other => {
if !cfg.status {
eprintln!("Warning: unrecognized option '{}'", other);
}
}
}
i += 1;
}
if cfg.files.is_empty() && !cfg.benchmark_mode {
cfg.files.push("-".to_string());
}
cfg
}
fn parse_block_size(s: &str) -> usize {
let s = s.to_lowercase();
let (num_str, mult) = if s.ends_with("kb") || s.ends_with("k") {
(s.trim_end_matches("kb").trim_end_matches('k'), 1024usize)
} else if s.ends_with("mb") || s.ends_with("m") {
(s.trim_end_matches("mb").trim_end_matches('m'), 1024 * 1024)
} else if s.ends_with("gb") || s.ends_with("g") {
(s.trim_end_matches("gb").trim_end_matches('g'), 1024 * 1024 * 1024)
} else {
(s.as_str(), 1)
};
num_str.parse::<usize>().unwrap_or(64) * mult
}
fn print_help(variant: HashVariant) {
let name = match variant {
HashVariant::XXH32 => "xxh32sum",
HashVariant::XXH64 => "xxh64sum",
HashVariant::XXH128 => "xxh128sum",
};
println!(
"{} — xxHash {} checksum utility
Usage: {} [options] [files...]
Options:
-H, --hash HASH Select hash variant (32, 64, 128)
-c, --check Read checksums from FILEs and check them
-b, --benchmark Run benchmark mode
-B, --block-size N Set benchmark block size (e.g. 64K, 1M)
-q, --quiet Suppress non-error messages
--status Suppress all output; use exit code only
-w, --warn Warn about improperly formatted checksum lines
--little-endian Output checksums in little-endian format
-h, --help Show this help message
-V, --version Show version information
When no FILE is given, or when FILE is '-', read from stdin.
",
name,
variant.display_name(),
name
);
}
fn hash_file(path: &str, variant: HashVariant) -> io::Result<String> {
let data = if path == "-" {
let mut buf = Vec::new();
io::stdin().lock().read_to_end(&mut buf)?;
buf
} else {
fs::read(path)?
};
match variant {
HashVariant::XXH32 => {
let h = xxh32(&data, 0);
Ok(format!("{:08x}", h))
}
HashVariant::XXH64 => {
let h = xxh64(&data, 0);
Ok(format!("{:016x}", h))
}
HashVariant::XXH128 => {
let h = xxh3_128(&data);
Ok(format!("{:032x}", h))
}
}
}
fn run_check_mode(cfg: &CliConfig) -> i32 {
let mut exit_code = 0i32;
let mut files_ok = 0u64;
let mut files_bad = 0u64;
for file in &cfg.files {
let reader: Box<dyn BufRead> = if file == "-" {
Box::new(BufReader::new(io::stdin().lock()))
} else {
match fs::File::open(file) {
Ok(f) => Box::new(BufReader::new(f)),
Err(e) => {
eprintln!("Error: cannot open '{}': {}", file, e);
exit_code = 1;
continue;
}
}
};
for (line_no, line_res) in reader.lines().enumerate() {
let line = match line_res {
Ok(l) => l,
Err(e) => {
eprintln!("Error reading '{}': {}", file, e);
exit_code = 1;
break;
}
};
let line = line.trim().to_string();
if line.is_empty() || line.starts_with('#') {
continue;
}
match parse_checksum_line(&line) {
Ok((expected_hash, file_path, is_binary)) => {
let display_path = if file_path == "-" { "stdin" } else { &file_path };
match hash_file(&file_path, cfg.variant) {
Ok(actual_hash) => {
if actual_hash == expected_hash {
if !cfg.status && !cfg.quiet {
println!("{}: OK", display_path);
}
files_ok += 1;
} else {
if !cfg.status {
eprintln!("{}: FAILED", display_path);
eprintln!(
" expected: {} got: {}",
expected_hash, actual_hash
);
}
files_bad += 1;
exit_code = 1;
}
}
Err(e) => {
if !cfg.status {
eprintln!(
"{}: cannot open '{}': {}",
file, file_path, e
);
}
files_bad += 1;
exit_code = 1;
}
}
}
Err(msg) => {
if cfg.warn || !cfg.status {
eprintln!("{}:{}: {}", file, line_no + 1, msg);
}
files_bad += 1;
exit_code = 1;
}
}
}
}
if !cfg.status {
let total = files_ok + files_bad;
if total > 0 {
println!(
"{} file{} checked: {} OK, {} FAILED",
total,
if total == 1 { "" } else { "s" },
files_ok,
files_bad
);
} else {
println!("No files checked.");
}
}
exit_code
}
fn parse_checksum_line(line: &str) -> Result<(String, String, bool), String> {
let trimmed = line.trim();
let (hash_part, file_part, is_binary) = if let Some(idx) = trimmed.find(" *") {
(&trimmed[..idx], trimmed[idx + 2..].trim(), true)
} else if let Some(idx) = trimmed.find(" ") {
(&trimmed[..idx], trimmed[idx + 2..].trim(), false)
} else if let Some(idx) = trimmed.find(' ') {
(&trimmed[..idx], trimmed[idx + 1..].trim(), false)
} else {
return Err("invalid checksum line format".to_string());
};
if hash_part.is_empty() || file_part.is_empty() {
return Err("empty hash or filename".to_string());
}
if !hash_part.chars().all(|c| c.is_ascii_hexdigit()) {
return Err(format!("invalid hex hash: '{}'", hash_part));
}
Ok((hash_part.to_string(), file_part.to_string(), is_binary))
}
fn run_benchmark(cfg: &CliConfig) {
let block_size = cfg.block_size;
let nb_loops = cfg.nb_loops;
let data = vec![0xAAu8; block_size];
println!(
"Benchmarking {} with {} KB blocks, {} iteration{}...\n",
cfg.variant.display_name(),
block_size / 1024,
nb_loops,
if nb_loops == 1 { "" } else { "s" }
);
let mut total_duration = std::time::Duration::ZERO;
let mut total_bytes: u64 = 0;
for iteration in 0..nb_loops {
let start = Instant::now();
let mut state: Box<dyn BenchHasher> = match cfg.variant {
HashVariant::XXH32 => Box::new(XXH32BenchHasher::new()),
HashVariant::XXH64 => Box::new(XXH64BenchHasher::new()),
HashVariant::XXH128 => Box::new(XXH128BenchHasher::new()),
};
let nb_blocks = if block_size >= 1024 * 1024 { 64u64 } else { 256u64 };
for _ in 0..nb_blocks {
state.update(&data);
}
let hash = state.finalize();
let elapsed = start.elapsed();
let bytes = nb_blocks * block_size as u64;
if iteration == 0 {
println!(" First run (warmup): {:?}, {} bytes, hash={}", elapsed, bytes, hash);
} else {
total_duration += elapsed;
total_bytes += bytes;
let mbps = (bytes as f64 / 1_048_576.0) / elapsed.as_secs_f64();
println!(
" Run {}: {:?}, {} bytes, {:.1} MB/s, hash={}",
iteration, elapsed, bytes, mbps, hash
);
}
}
if nb_loops > 1 {
let avg = total_duration / (nb_loops - 1) as u32;
let avg_mbps =
(total_bytes as f64 / 1_048_576.0) / avg.as_secs_f64();
println!(
"\n Average (excl. warmup): {:?} per run, {:.1} MB/s",
avg, avg_mbps
);
}
}
trait BenchHasher {
fn update(&mut self, data: &[u8]);
fn finalize(&self) -> String;
}
struct XXH32BenchHasher {
state: XXH32State,
}
impl XXH32BenchHasher {
fn new() -> Self {
Self {
state: XXH32State::new(0),
}
}
}
impl BenchHasher for XXH32BenchHasher {
fn update(&mut self, data: &[u8]) {
self.state.update(data);
}
fn finalize(&self) -> String {
format!("{:08x}", self.state.digest())
}
}
struct XXH64BenchHasher {
state: XXH64State,
}
impl XXH64BenchHasher {
fn new() -> Self {
Self {
state: XXH64State::new(0),
}
}
}
impl BenchHasher for XXH64BenchHasher {
fn update(&mut self, data: &[u8]) {
self.state.update(data);
}
fn finalize(&self) -> String {
format!("{:016x}", self.state.digest())
}
}
struct XXH128BenchHasher {
state: XXH3_128State,
}
impl XXH128BenchHasher {
fn new() -> Self {
Self {
state: XXH3_128State::new(),
}
}
}
impl BenchHasher for XXH128BenchHasher {
fn update(&mut self, data: &[u8]) {
self.state.update(data);
}
fn finalize(&self) -> String {
let h = self.state.digest();
format!("{:016x}{:016x}", h.hi, h.lo)
}
}
fn run_hash_mode(cfg: &CliConfig) -> i32 {
let mut exit_code = 0i32;
for file in &cfg.files {
match hash_file(file, cfg.variant) {
Ok(hash) => {
let display = if file == "-" { "" } else { file };
if file == "-" {
println!("{}", hash);
} else {
println!("{} {}", hash, display);
}
}
Err(e) => {
eprintln!("Error: cannot hash '{}': {}", file, e);
exit_code = 1;
}
}
}
exit_code
}
pub fn run_cli() -> i32 {
let cfg = parse_args();
if cfg.benchmark_mode {
run_benchmark(&cfg);
0
} else if cfg.check_mode {
run_check_mode(&cfg)
} else {
run_hash_mode(&cfg)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_xxh32_empty() {
assert_eq!(xxh32(b"", 0), 0x02CC_5D05);
}
#[test]
fn test_xxh32_seeded_empty() {
assert_eq!(xxh32(b"", 1234), 0x72D4_C4B7);
}
#[test]
fn test_xxh32_basic() {
assert_eq!(xxh32(b"a", 0), 0x550D_7456);
}
#[test]
fn test_xxh32_fox() {
let input = b"The quick brown fox jumps over the lazy dog";
assert_eq!(xxh32(input, 0), 0x0E24_2BD7);
}
#[test]
fn test_xxh32_16bytes() {
assert_eq!(xxh32(b"1234567890123456", 0), 0x532A_94FB);
}
#[test]
fn test_xxh32_32bytes() {
let input = b"12345678901234567890123456789012";
assert_eq!(xxh32(input, 0), 0x29CD_DC5B);
}
#[test]
fn test_xxh32_31bytes() {
let input = b"1234567890123456789012345678901";
assert_eq!(xxh32(input, 0), 0x032E_05CC);
}
#[test]
fn test_xxh32_256bytes() {
let input = vec![b'x'; 256];
assert_eq!(xxh32(&input, 0), 0x6E34_6492);
}
#[test]
fn test_xxh32_with_seed() {
let input = b"hello world";
assert_eq!(xxh32(input, 42), 0xF620_EA18);
}
#[test]
fn test_xxh32_null_bytes() {
let input = vec![0u8; 100];
assert_eq!(xxh32(&input, 0), 0x1A78_1EEB);
}
#[test]
fn test_xxh64_empty() {
assert_eq!(xxh64(b"", 0), 0xEF46_DB37_51D8_E999);
}
#[test]
fn test_xxh64_seeded_empty() {
assert_eq!(xxh64(b"", 1234), 0x6533_C2B7_4A56_E037);
}
#[test]
fn test_xxh64_basic() {
assert_eq!(xxh64(b"a", 0), 0xD24E_C4F1_A98C_6E5B);
}
#[test]
fn test_xxh64_fox() {
let input = b"The quick brown fox jumps over the lazy dog";
assert_eq!(xxh64(input, 0), 0x0E24_2BD7_DE74_2FF5);
}
#[test]
fn test_xxh64_32bytes() {
let input = b"12345678901234567890123456789012";
assert_eq!(xxh64(input, 0), 0xBDAD_D4C0_CB75_F473);
}
#[test]
fn test_xxh64_256bytes() {
let input = vec![b'x'; 256];
assert_eq!(xxh64(&input, 0), 0x2C3A_7611_2E09_E820);
}
#[test]
fn test_xxh64_with_seed() {
let input = b"hello world";
assert_eq!(xxh64(input, 42), 0x8194_6C63_3C44_0843);
}
#[test]
fn test_xxh3_64_empty() {
let h = xxh3_64(b"");
assert_ne!(h, 0);
assert_eq!(xxh3_64(b""), h);
}
#[test]
fn test_xxh3_64_basic() {
let h1 = xxh3_64(b"a");
let h2 = xxh3_64(b"a");
assert_eq!(h1, h2);
assert_ne!(h1, 0);
}
#[test]
fn test_xxh3_64_different_inputs() {
let h1 = xxh3_64(b"hello");
let h2 = xxh3_64(b"world");
let h3 = xxh3_64(b"hello!");
assert_ne!(h1, h2);
assert_ne!(h1, h3);
assert_ne!(h2, h3);
}
#[test]
fn test_xxh3_64_short_inputs() {
for len in 0..=16 {
let data = vec![len as u8; len];
let h = xxh3_64(&data);
assert_ne!(h, 0, "zero hash for len={}", len);
assert_eq!(xxh3_64(&data), h, "non-deterministic for len={}", len);
}
}
#[test]
fn test_xxh3_64_medium_inputs() {
for len in [17, 32, 64, 100, 128, 200, 240].iter() {
let data = vec![(*len as u8).wrapping_mul(3); *len];
let h = xxh3_64(&data);
assert_ne!(h, 0, "zero hash for len={}", len);
assert_eq!(xxh3_64(&data), h);
}
}
#[test]
fn test_xxh3_64_long_inputs() {
for len in [241, 256, 512, 1000, 4096].iter() {
let data = vec![(*len as u8) % 256; *len];
let h = xxh3_64(&data);
assert_ne!(h, 0, "zero hash for len={}", len);
assert_eq!(xxh3_64(&data), h);
}
}
#[test]
fn test_xxh3_128_empty() {
let h = xxh3_128(b"");
assert_ne!(h.lo, 0);
assert_ne!(h.hi, 0);
assert_eq!(xxh3_128(b""), h);
}
#[test]
fn test_xxh3_128_basic() {
let h1 = xxh3_128(b"hello world");
let h2 = xxh3_128(b"hello world");
assert_eq!(h1, h2);
assert_ne!(h1.lo, 0);
assert_ne!(h1.hi, 0);
}
#[test]
fn test_xxh3_128_different_inputs() {
let h1 = xxh3_128(b"foo");
let h2 = xxh3_128(b"bar");
assert_ne!(h1.lo, h2.lo);
}
#[test]
fn test_streaming_xxh32_vs_oneshot() {
for len in [0, 1, 4, 7, 15, 16, 17, 31, 32, 33, 64, 100, 256, 1000] {
let data: Vec<u8> = (0..len).map(|i| (i % 251) as u8).collect();
let oneshot = xxh32(&data, 0);
for chunk in [1, 4, 7, 16, 31, 64, 128] {
let mut state = XXH32State::new(0);
let mut offset = 0;
while offset < len {
let end = cmp::min(offset + chunk, len);
state.update(&data[offset..end]);
offset = end;
}
let streaming = state.digest();
assert_eq!(
oneshot, streaming,
"XXH32 mismatch: len={}, chunk={}",
len, chunk
);
}
}
}
#[test]
fn test_streaming_xxh64_vs_oneshot() {
for len in [0, 1, 7, 8, 15, 31, 32, 33, 63, 64, 65, 128, 256, 1000] {
let data: Vec<u8> = (0..len).map(|i| (i % 251) as u8).collect();
let oneshot = xxh64(&data, 0);
for chunk in [1, 8, 16, 32, 64, 128] {
let mut state = XXH64State::new(0);
let mut offset = 0;
while offset < len {
let end = cmp::min(offset + chunk, len);
state.update(&data[offset..end]);
offset = end;
}
let streaming = state.digest();
assert_eq!(
oneshot, streaming,
"XXH64 mismatch: len={}, chunk={}",
len, chunk
);
}
}
}
#[test]
fn test_streaming_xxh3_64_vs_oneshot() {
for len in [0, 1, 7, 8, 16, 17, 31, 64, 65, 128, 129, 240, 241, 256, 512, 1000]
{
let data: Vec<u8> = (0..len).map(|i| (i % 251) as u8).collect();
let oneshot = xxh3_64(&data);
for chunk in [1, 7, 16, 64, 128, 256] {
let mut state = XXH3_64State::new();
let mut offset = 0;
while offset < len {
let end = cmp::min(offset + chunk, len);
state.update(&data[offset..end]);
offset = end;
}
let streaming = state.digest();
assert_eq!(
oneshot, streaming,
"XXH3_64 mismatch: len={}, chunk={}",
len, chunk
);
}
}
}
#[test]
fn test_streaming_xxh3_128_vs_oneshot() {
for len in [0, 1, 7, 16, 17, 64, 128, 129, 240, 241, 256, 512] {
let data: Vec<u8> = (0..len).map(|i| (i % 251) as u8).collect();
let oneshot = xxh3_128(&data);
for chunk in [1, 16, 64, 256] {
let mut state = XXH3_128State::new();
let mut offset = 0;
while offset < len {
let end = cmp::min(offset + chunk, len);
state.update(&data[offset..end]);
offset = end;
}
let streaming = state.digest();
assert_eq!(
oneshot, streaming,
"XXH3_128 mismatch: len={}, chunk={}",
len, chunk
);
}
}
}
#[test]
fn test_streaming_reset_xxh32() {
let mut state = XXH32State::new(0);
state.update(b"hello");
let h1 = state.digest();
state.reset();
state.update(b"hello");
let h2 = state.digest();
assert_eq!(h1, h2);
}
#[test]
fn test_streaming_reset_xxh64() {
let mut state = XXH64State::new(0);
state.update(b"hello");
let h1 = state.digest();
state.reset();
state.update(b"hello");
let h2 = state.digest();
assert_eq!(h1, h2);
}
#[test]
fn test_streaming_reset_xxh3_64() {
let mut state = XXH3_64State::new();
state.update(b"hello world");
let h1 = state.digest();
state.reset();
state.update(b"hello world");
let h2 = state.digest();
assert_eq!(h1, h2);
}
#[test]
fn test_streaming_reset_xxh3_128() {
let mut state = XXH3_128State::new();
state.update(b"hello world");
let h1 = state.digest();
state.reset();
state.update(b"hello world");
let h2 = state.digest();
assert_eq!(h1, h2);
}
#[test]
fn test_canonical32_roundtrip() {
for &val in &[0u32, 1, 0x12345678, 0xFFFFFFFF, 0x02CC5D05, 0xDEAD_BEEF] {
let c = Canonical32::from_u32(val);
assert_eq!(c.to_u32(), val);
let expected = val.to_be_bytes();
assert_eq!(c.as_bytes(), &expected);
}
}
#[test]
fn test_canonical64_roundtrip() {
for &val in
&[0u64, 1, 0x123456789ABCDEF0, 0xFFFFFFFFFFFFFFFF, 0xEF46DB3751D8E999]
{
let c = Canonical64::from_u64(val);
assert_eq!(c.to_u64(), val);
let expected = val.to_be_bytes();
assert_eq!(c.as_bytes(), &expected);
}
}
#[test]
fn test_canonical128_roundtrip() {
let val = XXH128::new(0xDEAD_BEEF_CAFE_BABE, 0x1234_5678_9ABC_DEF0);
let c = Canonical128::from_xxh128(val);
assert_eq!(c.to_xxh128(), val);
let expected_hi = val.hi.to_be_bytes();
let expected_lo = val.lo.to_be_bytes();
let mut expected = [0u8; 16];
expected[..8].copy_from_slice(&expected_hi);
expected[8..].copy_from_slice(&expected_lo);
assert_eq!(c.as_bytes(), &expected);
}
#[test]
fn test_display_format() {
let c32 = Canonical32::from_u32(0x02CC5D05);
assert_eq!(format!("{}", c32), "02cc5d05");
let c64 = Canonical64::from_u64(0xEF46DB3751D8E999);
assert_eq!(format!("{}", c64), "ef46db3751d8e999");
let h = XXH128::new(0xDEAD_BEEF_CAFE_BABE, 0x1234_5678_9ABC_DEF0);
let c128 = Canonical128::from_xxh128(h);
assert_eq!(
format!("{}", c128),
"123456789abcdef0deadbeefcafebabe"
);
}
#[test]
fn test_xxh128_display() {
let h = XXH128::new(0xDEAD_BEEF_CAFE_BABE, 0x1234_5678_9ABC_DEF0);
assert_eq!(
format!("{}", h),
"123456789abcdef0deadbeefcafebabe"
);
}
#[test]
fn test_no_zero_hash_for_nonempty_input() {
let inputs = [
b"a" as &[u8],
b"ab",
b"abc",
b"abcd",
b"abcde",
b"abcdef",
b"abcdefg",
b"abcdefgh",
b"hello world",
b"12345678901234567890",
];
for input in &inputs {
assert_ne!(xxh32(input, 0), 0);
assert_ne!(xxh64(input, 0), 0);
assert_ne!(xxh3_64(input), 0);
let h128 = xxh3_128(input);
assert!(h128.lo != 0 || h128.hi != 0);
}
}
#[test]
fn test_xxh32_seeded_different_values() {
let input = b"test input";
let h0 = xxh32(input, 0);
let h1 = xxh32(input, 1);
let h2 = xxh32(input, 0xFFFFFFFF);
assert_ne!(h0, h1);
assert_ne!(h1, h2);
assert_ne!(h0, h2);
}
#[test]
fn test_xxh64_seeded_different_values() {
let input = b"test input";
let h0 = xxh64(input, 0);
let h1 = xxh64(input, 1);
let h2 = xxh64(input, 0xFFFFFFFF_FFFFFFFF);
assert_ne!(h0, h1);
assert_ne!(h1, h2);
assert_ne!(h0, h2);
}
#[test]
fn test_avalanche_effect_32() {
let h1 = xxh32(b"hello", 0);
let h2 = xxh32(b"hellp", 0); let diff = h1 ^ h2;
let bit_count = diff.count_ones();
assert!(
bit_count >= 10,
"Avalanche too weak: only {} bits differ for 1-byte change",
bit_count
);
}
#[test]
fn test_avalanche_effect_64() {
let h1 = xxh64(b"hello world!", 0);
let h2 = xxh64(b"Hello world!", 0); let diff = h1 ^ h2;
let bit_count = diff.count_ones();
assert!(
bit_count >= 20,
"Avalanche too weak: only {} bits differ for 1-byte change",
bit_count
);
}
#[test]
fn test_avalanche_effect_xxh3() {
let h1 = xxh3_64(b"hello world!");
let h2 = xxh3_64(b"Hello world!");
let diff = h1 ^ h2;
let bit_count = diff.count_ones();
assert!(
bit_count >= 15,
"Avalanche too weak: only {} bits differ for 1-byte change",
bit_count
);
}
#[test]
fn test_cli_variant_detection() {
assert_eq!(
HashVariant::from_argv0("xxh32sum"),
HashVariant::XXH32
);
assert_eq!(
HashVariant::from_argv0("xxh64sum"),
HashVariant::XXH64
);
assert_eq!(
HashVariant::from_argv0("xxh128sum"),
HashVariant::XXH128
);
assert_eq!(
HashVariant::from_argv0("/usr/bin/xxh64sum"),
HashVariant::XXH64
);
}
#[test]
fn test_parse_checksum_line_valid() {
let (hash, file, binary) =
parse_checksum_line("abcdef1234567890 myfile.txt").unwrap();
assert_eq!(hash, "abcdef1234567890");
assert_eq!(file, "myfile.txt");
assert!(!binary);
}
#[test]
fn test_parse_checksum_line_binary() {
let (hash, file, binary) =
parse_checksum_line("abcdef1234567890 *myfile.bin").unwrap();
assert_eq!(hash, "abcdef1234567890");
assert_eq!(file, "myfile.bin");
assert!(binary);
}
#[test]
fn test_parse_checksum_line_invalid() {
assert!(parse_checksum_line("not a valid line").is_err());
assert!(parse_checksum_line("").is_err());
assert!(parse_checksum_line("abc").is_err());
}
#[test]
fn test_parse_block_size() {
assert_eq!(parse_block_size("64"), 64);
assert_eq!(parse_block_size("64K"), 64 * 1024);
assert_eq!(parse_block_size("64KB"), 64 * 1024);
assert_eq!(parse_block_size("1M"), 1024 * 1024);
assert_eq!(parse_block_size("2mb"), 2 * 1024 * 1024);
}
#[test]
fn test_large_input_consistency() {
let size = 1024 * 1024;
let data: Vec<u8> = (0..size).map(|i| (i.wrapping_mul(37)) as u8).collect();
let h32 = xxh32(&data, 0);
let h64 = xxh64(&data, 0);
let h3_64 = xxh3_64(&data);
let h3_128 = xxh3_128(&data);
let mut s32 = XXH32State::new(0);
s32.update(&data);
assert_eq!(s32.digest(), h32);
let mut s64 = XXH64State::new(0);
s64.update(&data);
assert_eq!(s64.digest(), h64);
let mut s3_64 = XXH3_64State::new();
s3_64.update(&data);
assert_eq!(s3_64.digest(), h3_64);
let mut s3_128 = XXH3_128State::new();
s3_128.update(&data);
assert_eq!(s3_128.digest(), h3_128);
assert_ne!(h32, 0);
assert_ne!(h64, 0);
assert_ne!(h3_64, 0);
assert_ne!(h3_128.lo, 0);
assert_ne!(h3_128.hi, 0);
}
#[test]
fn test_extreme_seeds() {
let input = b"extreme seed test";
let _h32 = xxh32(input, u32::MAX);
let _h64 = xxh64(input, u64::MAX);
}
#[test]
fn test_xxh3_64_with_custom_secret() {
let secret = [0x55u8; 192];
let input = b"custom secret test";
let h_default = xxh3_64(input);
let h_custom = xxh3_64_with_secret(input, &secret, 0);
assert_ne!(h_default, h_custom);
assert_ne!(h_custom, 0);
assert_eq!(xxh3_64_with_secret(input, &secret, 0), h_custom);
}
#[test]
fn test_xxh3_128_with_custom_secret() {
let secret = [0x55u8; 192];
let input = b"custom secret 128 test";
let h_default = xxh3_128(input);
let h_custom = xxh3_128_with_secret(input, &secret, 0);
assert_ne!(h_default, h_custom);
assert_ne!(h_custom.lo, 0);
assert_ne!(h_custom.hi, 0);
}
#[test]
fn test_xxh3_empty_secret_uses_default() {
let input = b"empty secret test";
let h_default = xxh3_64(input);
let h_empty = xxh3_64_with_secret(input, &[], 0);
assert_eq!(h_default, h_empty);
}
#[test]
fn test_xxh3_secret_short_expansion() {
let short_secret = [0xAAu8; 64];
let full_secret = {
let mut s = [0u8; 192];
for i in 0..192 {
s[i] = short_secret[i % 64];
}
s
};
let input = b"short secret test";
let h1 = xxh3_64_with_secret(input, &short_secret, 0);
let h2 = xxh3_64_with_secret(input, &full_secret, 0);
assert_eq!(h1, h2);
}
}