use unicode_normalization::UnicodeNormalization;
use crate::hash::NameHash;
pub fn fold_name(name: &[u8]) -> Vec<u8> {
match std::str::from_utf8(name) {
Ok(s) => {
let nfd: String = s.nfd().collect();
caseless::default_case_fold_str(&nfd).into_bytes()
}
Err(_) => {
name.iter()
.map(|&b| {
if (0x41..=0x5A).contains(&b) {
b + 0x20
} else {
b
}
})
.collect()
}
}
}
const SIP_C: [u64; 4] = [
0x7367_6165_6e65_7265, 0x6c6f_7265_6d69_7073, 0x656c_7564_6f6d_6976,
0x6479_7465_6272_6f79,
];
#[inline]
fn rotl(x: u64, b: u32) -> u64 {
x.rotate_left(b)
}
#[inline]
fn sipround(v0: &mut u64, v1: &mut u64, v2: &mut u64, v3: &mut u64) {
*v0 = v0.wrapping_add(*v1);
*v1 = rotl(*v1, 13);
*v1 ^= *v0;
*v0 = rotl(*v0, 32);
*v2 = v2.wrapping_add(*v3);
*v3 = rotl(*v3, 16);
*v3 ^= *v2;
*v0 = v0.wrapping_add(*v3);
*v3 = rotl(*v3, 21);
*v3 ^= *v0;
*v2 = v2.wrapping_add(*v1);
*v1 = rotl(*v1, 17);
*v1 ^= *v2;
*v2 = rotl(*v2, 32);
}
pub fn siphash_2_4(data: &[u8], key: &[u8; 16]) -> u64 {
let k0 = u64::from_le_bytes(key[0..8].try_into().unwrap());
let k1 = u64::from_le_bytes(key[8..16].try_into().unwrap());
let mut v0: u64 = k0 ^ 0x736f_6d65_7073_6575;
let mut v1: u64 = k1 ^ 0x646f_7261_6e64_6f6d;
let mut v2: u64 = k0 ^ 0x6c79_6765_6e65_7261;
let mut v3: u64 = k1 ^ 0x7465_6462_7974_6573;
let _ = SIP_C;
let len = data.len();
let mut pos = 0usize;
while len - pos >= 8 {
let m = u64::from_le_bytes(data[pos..pos + 8].try_into().unwrap());
v3 ^= m;
sipround(&mut v0, &mut v1, &mut v2, &mut v3);
sipround(&mut v0, &mut v1, &mut v2, &mut v3);
v0 ^= m;
pos += 8;
}
let mut b: u64 = (len as u64) << 56;
let rem = len - pos;
for (i, &byte) in data[pos..].iter().enumerate() {
b |= (byte as u64) << (i * 8);
}
let _ = rem; v3 ^= b;
sipround(&mut v0, &mut v1, &mut v2, &mut v3);
sipround(&mut v0, &mut v1, &mut v2, &mut v3);
v0 ^= b;
v2 ^= 0xff;
for _ in 0..4 {
sipround(&mut v0, &mut v1, &mut v2, &mut v3);
}
v0 ^ v1 ^ v2 ^ v3
}
pub fn casefold_name_hash(name: &[u8], seed: &[u32; 4]) -> NameHash {
let folded = fold_name(name);
let mut key = [0u8; 16];
for i in 0..4 {
key[i * 4..i * 4 + 4].copy_from_slice(&seed[i].to_le_bytes());
}
let h64 = siphash_2_4(&folded, &key);
let major = (h64 as u32) & !1; let minor = (h64 >> 32) as u32;
NameHash { major, minor }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fold_ascii_uppercase() {
assert_eq!(fold_name(b"HELLO"), b"hello".to_vec());
assert_eq!(fold_name(b"Hello"), b"hello".to_vec());
}
#[test]
fn fold_unicode_sharp_s() {
assert_eq!(fold_name(&[0xC3, 0x9F]), b"ss".to_vec());
}
#[test]
fn fold_unicode_latin_upper() {
let folded_upper = fold_name("Ñ".as_bytes());
let folded_lower = fold_name("ñ".as_bytes());
assert_eq!(
folded_upper, folded_lower,
"Ñ and ñ must produce the same hash input"
);
}
#[test]
fn fold_invalid_utf8_ascii_folds() {
assert_eq!(fold_name(&[0xFF, 0x41, 0x42]), vec![0xFF, 0x61, 0x62]);
}
#[test]
fn fold_empty_name() {
assert_eq!(fold_name(b""), Vec::<u8>::new());
}
#[test]
fn casefold_hash_is_case_insensitive() {
let seed = [1u32, 2, 3, 4];
let a = casefold_name_hash(b"README", &seed);
let b = casefold_name_hash(b"readme", &seed);
let c = casefold_name_hash(b"ReadMe", &seed);
assert_eq!(a.major, b.major);
assert_eq!(a.major, c.major);
}
#[test]
fn casefold_hash_differs_on_different_names() {
let seed = [1u32, 2, 3, 4];
let a = casefold_name_hash(b"hello", &seed);
let b = casefold_name_hash(b"hellw", &seed);
assert_ne!(a.major, b.major);
}
#[test]
fn siphash_empty_input() {
let key = [0u8; 16];
let a = siphash_2_4(b"", &key);
let b = siphash_2_4(b"", &key);
assert_eq!(a, b);
}
#[test]
fn siphash_rfc_zero_key_empty_matches_impl_specific() {
let key = [0u8; 16];
let h = siphash_2_4(b"abc", &key);
assert_ne!(h, 0);
}
#[test]
fn casefold_low_bit_is_zero() {
let seed = [0xdeadbeefu32, 0, 0, 0];
for name in [b"foo".as_slice(), b"BAR", b"mixedCase"] {
let h = casefold_name_hash(name, &seed);
assert_eq!(h.major & 1, 0);
}
}
}