#[inline(always)]
fn rot(x: u32, k: u32) -> u32 {
(x << k) | (x >> (32u32.wrapping_sub(k)))
}
#[inline(always)]
fn mix(a: &mut u32, b: &mut u32, c: &mut u32) {
*a = a.wrapping_sub(*c);
*a ^= rot(*c, 4);
*c = c.wrapping_add(*b);
*b = b.wrapping_sub(*a);
*b ^= rot(*a, 6);
*a = a.wrapping_add(*c);
*c = c.wrapping_sub(*b);
*c ^= rot(*b, 8);
*b = b.wrapping_add(*a);
*a = a.wrapping_sub(*c);
*a ^= rot(*c, 16);
*c = c.wrapping_add(*b);
*b = b.wrapping_sub(*a);
*b ^= rot(*a, 19);
*a = a.wrapping_add(*c);
*c = c.wrapping_sub(*b);
*c ^= rot(*b, 4);
*b = b.wrapping_add(*a);
}
#[inline(always)]
fn final_mix(a: &mut u32, b: &mut u32, c: &mut u32) {
*c ^= *b;
*c = c.wrapping_sub(rot(*b, 14));
*a ^= *c;
*a = a.wrapping_sub(rot(*c, 11));
*b ^= *a;
*b = b.wrapping_sub(rot(*a, 25));
*c ^= *b;
*c = c.wrapping_sub(rot(*b, 16));
*a ^= *c;
*a = a.wrapping_sub(rot(*c, 4));
*b ^= *a;
*b = b.wrapping_sub(rot(*a, 14));
*c ^= *b;
*c = c.wrapping_sub(rot(*b, 24));
}
pub fn jenkins_lookup3(data: &[u8], initval: u32) -> u32 {
let length = data.len() as u32;
let mut a: u32 = 0xdeadbeefu32.wrapping_add(length).wrapping_add(initval);
let mut b: u32 = a;
let mut c: u32 = a;
if data.is_empty() {
final_mix(&mut a, &mut b, &mut c);
return c;
}
let mut offset: usize = 0;
let mut remaining = data.len();
while remaining > 12 {
a = a.wrapping_add(u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
]));
b = b.wrapping_add(u32::from_le_bytes([
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
]));
c = c.wrapping_add(u32::from_le_bytes([
data[offset + 8],
data[offset + 9],
data[offset + 10],
data[offset + 11],
]));
mix(&mut a, &mut b, &mut c);
offset += 12;
remaining -= 12;
}
let k = &data[offset..];
if remaining == 0 {
return c;
}
if remaining >= 12 {
c = c.wrapping_add((k[11] as u32) << 24);
}
if remaining >= 11 {
c = c.wrapping_add((k[10] as u32) << 16);
}
if remaining >= 10 {
c = c.wrapping_add((k[9] as u32) << 8);
}
if remaining >= 9 {
c = c.wrapping_add(k[8] as u32);
}
if remaining >= 8 {
b = b.wrapping_add((k[7] as u32) << 24);
}
if remaining >= 7 {
b = b.wrapping_add((k[6] as u32) << 16);
}
if remaining >= 6 {
b = b.wrapping_add((k[5] as u32) << 8);
}
if remaining >= 5 {
b = b.wrapping_add(k[4] as u32);
}
if remaining >= 4 {
a = a.wrapping_add((k[3] as u32) << 24);
}
if remaining >= 3 {
a = a.wrapping_add((k[2] as u32) << 16);
}
if remaining >= 2 {
a = a.wrapping_add((k[1] as u32) << 8);
}
a = a.wrapping_add(k[0] as u32);
final_mix(&mut a, &mut b, &mut c);
c
}
pub fn checksum_metadata(data: &[u8]) -> u32 {
jenkins_lookup3(data, 0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_jenkins_hello() {
let hash = jenkins_lookup3(b"Hello", 0);
assert_ne!(hash, 0, "hash should not be zero for non-empty input");
assert_eq!(hash, jenkins_lookup3(b"Hello", 0));
}
#[test]
fn test_jenkins_deterministic() {
let data = b"The quick brown fox jumps over the lazy dog";
let h1 = jenkins_lookup3(data, 0);
let h2 = jenkins_lookup3(data, 0);
assert_eq!(h1, h2);
}
#[test]
fn test_jenkins_different_initval() {
let data = b"Hello";
let h0 = jenkins_lookup3(data, 0);
let h1 = jenkins_lookup3(data, 1);
assert_ne!(h0, h1, "different initvals should produce different hashes");
}
#[test]
fn test_jenkins_empty() {
let hash = jenkins_lookup3(b"", 0);
assert_eq!(hash, jenkins_lookup3(b"", 0));
}
#[test]
fn test_checksum_metadata() {
let data = b"test data for checksum";
assert_eq!(checksum_metadata(data), jenkins_lookup3(data, 0));
}
#[test]
fn test_jenkins_exactly_12_bytes() {
let data = b"abcdefghijkl"; let hash = jenkins_lookup3(data, 0);
assert_eq!(hash, jenkins_lookup3(data, 0));
}
#[test]
fn test_jenkins_13_bytes() {
let data = b"abcdefghijklm"; let hash = jenkins_lookup3(data, 0);
assert_eq!(hash, jenkins_lookup3(data, 0));
}
#[test]
fn test_jenkins_various_lengths() {
for len in 1..=24 {
let data: Vec<u8> = (0..len).map(|i| (i & 0xFF) as u8).collect();
let hash = jenkins_lookup3(&data, 0);
assert_eq!(hash, jenkins_lookup3(&data, 0));
}
}
#[test]
fn test_jenkins_no_collisions_small_inputs() {
let cases: &[&[u8]] = &[
b"",
b"a",
b"ab",
b"abc",
b"abcd",
b"abcde",
b"abcdef",
b"abcdefg",
b"abcdefgh",
b"abcdefghi",
b"abcdefghij",
b"abcdefghijk",
b"abcdefghijkl",
b"abcdefghijklm",
];
let mut hashes: Vec<u32> = Vec::new();
for case in cases {
let h = jenkins_lookup3(case, 0);
for (i, &prev) in hashes.iter().enumerate() {
assert_ne!(
h,
prev,
"unexpected collision between input #{} and #{}: 0x{:08x}",
hashes.len(),
i,
h
);
}
hashes.push(h);
}
}
#[test]
fn test_jenkins_reference_vectors() {
assert_eq!(jenkins_lookup3(b"Hello", 0), 0xc7bc405b, "Hello initval=0");
assert_eq!(jenkins_lookup3(b"Hello", 1), 0xdc096650, "Hello initval=1");
assert_eq!(
jenkins_lookup3(b"Four score and seven years", 0),
0x769e8a62,
"Four score initval=0"
);
assert_eq!(
jenkins_lookup3(b"Four score and seven years", 1),
0xf9f2ffcb,
"Four score initval=1"
);
assert_eq!(jenkins_lookup3(b"a", 0), 0x58d68708, "a initval=0");
assert_eq!(jenkins_lookup3(b"abc", 0), 0x0e397631, "abc initval=0");
assert_eq!(
jenkins_lookup3(b"abcdefghijkl", 0),
0x4012f87b,
"12 bytes initval=0"
);
assert_eq!(
jenkins_lookup3(b"abcdefghijklm", 0),
0x928128f9,
"13 bytes initval=0"
);
}
}