pub fn jenkins_lookup3(data: &[u8]) -> u32 {
hashlittle(data, 0)
}
pub fn crc32(data: &[u8]) -> u32 {
#[cfg(feature = "fast-checksum")]
{
crc32fast::hash(data)
}
#[cfg(not(feature = "fast-checksum"))]
{
crc32_software(data)
}
}
pub fn crc32_software(data: &[u8]) -> u32 {
let mut crc: u32 = 0xFFFFFFFF;
for &byte in data {
let index = ((crc ^ byte as u32) & 0xFF) as usize;
crc = CRC32_TABLE[index] ^ (crc >> 8);
}
crc ^ 0xFFFFFFFF
}
#[rustfmt::skip]
const CRC32_TABLE: [u32; 256] = {
let mut table = [0u32; 256];
let mut i = 0u32;
while i < 256 {
let mut crc = i;
let mut j = 0;
while j < 8 {
if crc & 1 != 0 {
crc = 0xEDB88320 ^ (crc >> 1);
} else {
crc >>= 1;
}
j += 1;
}
table[i as usize] = crc;
i += 1;
}
table
};
fn rot(x: u32, k: u32) -> u32 {
x.rotate_left(k)
}
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);
}
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));
}
fn read_u32_le(data: &[u8], offset: usize) -> u32 {
u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
])
}
fn hashlittle(data: &[u8], initval: u32) -> u32 {
let length = data.len();
let mut a: u32 = 0xdeadbeefu32.wrapping_add(length as u32).wrapping_add(initval);
let mut b: u32 = a;
let mut c: u32 = a;
let mut offset = 0;
let mut remaining = length;
while remaining > 12 {
a = a.wrapping_add(read_u32_le(data, offset));
b = b.wrapping_add(read_u32_le(data, offset + 4));
c = c.wrapping_add(read_u32_le(data, offset + 8));
mix(&mut a, &mut b, &mut c);
offset += 12;
remaining -= 12;
}
let tail = &data[offset..];
match remaining {
12 => {
a = a.wrapping_add(read_u32_le(tail, 0));
b = b.wrapping_add(read_u32_le(tail, 4));
c = c.wrapping_add(read_u32_le(tail, 8));
}
11 => {
c = c.wrapping_add((tail[10] as u32) << 16);
c = c.wrapping_add((tail[9] as u32) << 8);
c = c.wrapping_add(tail[8] as u32);
b = b.wrapping_add(read_u32_le(tail, 4));
a = a.wrapping_add(read_u32_le(tail, 0));
}
10 => {
c = c.wrapping_add((tail[9] as u32) << 8);
c = c.wrapping_add(tail[8] as u32);
b = b.wrapping_add(read_u32_le(tail, 4));
a = a.wrapping_add(read_u32_le(tail, 0));
}
9 => {
c = c.wrapping_add(tail[8] as u32);
b = b.wrapping_add(read_u32_le(tail, 4));
a = a.wrapping_add(read_u32_le(tail, 0));
}
8 => {
b = b.wrapping_add(read_u32_le(tail, 4));
a = a.wrapping_add(read_u32_le(tail, 0));
}
7 => {
b = b.wrapping_add((tail[6] as u32) << 16);
b = b.wrapping_add((tail[5] as u32) << 8);
b = b.wrapping_add(tail[4] as u32);
a = a.wrapping_add(read_u32_le(tail, 0));
}
6 => {
b = b.wrapping_add((tail[5] as u32) << 8);
b = b.wrapping_add(tail[4] as u32);
a = a.wrapping_add(read_u32_le(tail, 0));
}
5 => {
b = b.wrapping_add(tail[4] as u32);
a = a.wrapping_add(read_u32_le(tail, 0));
}
4 => {
a = a.wrapping_add(read_u32_le(tail, 0));
}
3 => {
a = a.wrapping_add((tail[2] as u32) << 16);
a = a.wrapping_add((tail[1] as u32) << 8);
a = a.wrapping_add(tail[0] as u32);
}
2 => {
a = a.wrapping_add((tail[1] as u32) << 8);
a = a.wrapping_add(tail[0] as u32);
}
1 => {
a = a.wrapping_add(tail[0] as u32);
}
0 => return c,
_ => unreachable!(),
}
final_mix(&mut a, &mut b, &mut c);
c
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_input() {
let h = jenkins_lookup3(b"");
assert_eq!(h, jenkins_lookup3(b""));
}
#[test]
fn known_values() {
let h1 = jenkins_lookup3(b"hello");
let h2 = jenkins_lookup3(b"hello");
assert_eq!(h1, h2);
let h3 = jenkins_lookup3(b"world");
assert_ne!(h1, h3);
}
#[test]
fn twelve_byte_boundary() {
let h = jenkins_lookup3(b"abcdefghijkl");
assert_eq!(h, jenkins_lookup3(b"abcdefghijkl"));
}
#[test]
fn longer_than_12() {
let h = jenkins_lookup3(b"abcdefghijklmnop");
assert_eq!(h, jenkins_lookup3(b"abcdefghijklmnop"));
}
#[test]
fn all_tail_lengths() {
for len in 1..=12 {
let data: Vec<u8> = (0..len).map(|i| i as u8).collect();
let h1 = jenkins_lookup3(&data);
let h2 = jenkins_lookup3(&data);
assert_eq!(h1, h2, "failed for length {len}");
}
}
#[test]
fn verify_against_hdf5_file() {
let file_data: &[u8] = include_bytes!("../tests/fixtures/v2_groups.h5");
let stored = u32::from_le_bytes([
file_data[44], file_data[45], file_data[46], file_data[47],
]);
let computed = jenkins_lookup3(&file_data[0..44]);
assert_eq!(computed, stored, "Jenkins lookup3 should match HDF5 superblock checksum");
}
#[test]
fn crc32_empty() {
assert_eq!(crc32(b""), 0);
}
#[test]
fn crc32_known_value() {
assert_eq!(crc32(b"123456789"), 0xCBF43926);
}
#[test]
fn crc32_software_matches() {
let data: Vec<u8> = (0..1000).map(|i| (i % 256) as u8).collect();
let hw = crc32(&data);
let sw = crc32_software(&data);
assert_eq!(hw, sw);
}
#[test]
fn crc32_deterministic() {
let data = b"hello world";
assert_eq!(crc32(data), crc32(data));
}
}