const C1: u64 = 0x87c3_7b91_1142_53d5;
const C2: u64 = 0x4cf5_ad43_2745_937f;
fn fmix64(mut k: u64) -> u64 {
k ^= k >> 33;
k = k.wrapping_mul(0xff51_afd7_ed55_8ccd);
k ^= k >> 33;
k = k.wrapping_mul(0xc4ce_b9fe_1a85_ec53);
k ^= k >> 33;
k
}
#[allow(clippy::indexing_slicing)]
fn read_u64_le(data: &[u8], base: usize) -> u64 {
let mut buf = [0u8; 8];
if let Some(chunk) = data.get(base..(base + 8).min(data.len())) {
buf[..chunk.len()].copy_from_slice(chunk);
}
u64::from_le_bytes(buf)
}
#[allow(clippy::indexing_slicing)]
pub(crate) fn murmur3_x64_128(data: &[u8], seed: u64) -> (u64, u64) {
let mut h1 = seed;
let mut h2 = seed;
let nblocks = data.len() / 16;
for i in 0..nblocks {
let base = i * 16;
let mut k1 = read_u64_le(data, base);
let mut k2 = read_u64_le(data, base + 8);
k1 = k1.wrapping_mul(C1).rotate_left(31).wrapping_mul(C2);
h1 ^= k1;
h1 = h1
.rotate_left(27)
.wrapping_add(h2)
.wrapping_mul(5)
.wrapping_add(0x52dc_e729);
k2 = k2.wrapping_mul(C2).rotate_left(33).wrapping_mul(C1);
h2 ^= k2;
h2 = h2
.rotate_left(31)
.wrapping_add(h1)
.wrapping_mul(5)
.wrapping_add(0x3849_5ab5);
}
let tail = &data[nblocks * 16..];
let tlen = tail.len();
let mut k1 = 0u64;
let mut k2 = 0u64;
if tlen >= 9 {
for i in (8..tlen).rev() {
k2 ^= (tail[i] as u64) << (8 * (i - 8));
}
k2 = k2.wrapping_mul(C2).rotate_left(33).wrapping_mul(C1);
h2 ^= k2;
}
if tlen >= 1 {
let upto = tlen.min(8);
for i in (0..upto).rev() {
k1 ^= (tail[i] as u64) << (8 * i);
}
k1 = k1.wrapping_mul(C1).rotate_left(31).wrapping_mul(C2);
h1 ^= k1;
}
let len = data.len() as u64;
h1 ^= len;
h2 ^= len;
h1 = h1.wrapping_add(h2);
h2 = h2.wrapping_add(h1);
h1 = fmix64(h1);
h2 = fmix64(h2);
h1 = h1.wrapping_add(h2);
h2 = h2.wrapping_add(h1);
(h1, h2)
}
pub(crate) fn hash64(data: &[u8]) -> u64 {
murmur3_x64_128(data, 0).0
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
#[test]
fn deterministic_across_calls() {
assert_eq!(
murmur3_x64_128(b"127.0.0.1:9000", 0),
murmur3_x64_128(b"127.0.0.1:9000", 0)
);
assert_eq!(hash64(b"user-42"), hash64(b"user-42"));
}
#[test]
fn distinct_inputs_diverge() {
assert_ne!(hash64(b"a:1"), hash64(b"a:2"));
assert_ne!(hash64(b""), hash64(b"\0"));
let (l, h) = murmur3_x64_128(b"some-backend-name", 0);
assert_ne!(l, h, "the two 64-bit halves are independent, not equal");
}
#[test]
fn handles_all_tail_lengths_without_panic() {
for len in 0..40usize {
let data: Vec<u8> = (0..len as u8).collect();
let _ = murmur3_x64_128(&data, 0);
}
}
#[test]
fn spreads_sequential_keys() {
let m = 97u64;
let mut slots = HashSet::new();
for i in 0..80 {
slots.insert(hash64(format!("user-{i}").as_bytes()) % m);
}
assert!(
slots.len() > 40,
"sequential keys should spread across slots"
);
}
}