pub type SipState = [u64; 4];
pub const SIP_IV: SipState = [
0x736f_6d65_7073_6575,
0x646f_7261_6e64_6f6d,
0x6c79_6765_6e65_7261,
0x7465_6462_7974_6573,
];
pub const SIP_C: usize = 2;
pub const SIP_D: usize = 4;
#[inline(always)]
pub fn sip_init(k0: u64, k1: u64) -> SipState {
[
SIP_IV[0] ^ k0,
SIP_IV[1] ^ k1,
SIP_IV[2] ^ k0,
SIP_IV[3] ^ k1,
]
}
#[inline(always)]
pub fn sipround(v: &mut SipState) {
v[0] = v[0].wrapping_add(v[1]);
v[1] = v[1].rotate_left(13);
v[1] ^= v[0];
v[0] = v[0].rotate_left(32);
v[2] = v[2].wrapping_add(v[3]);
v[3] = v[3].rotate_left(16);
v[3] ^= v[2];
v[0] = v[0].wrapping_add(v[3]);
v[3] = v[3].rotate_left(21);
v[3] ^= v[0];
v[2] = v[2].wrapping_add(v[1]);
v[1] = v[1].rotate_left(17);
v[1] ^= v[2];
v[2] = v[2].rotate_left(32);
}
#[inline(always)]
pub fn sip_absorb(v: &mut SipState, m: u64) {
v[3] ^= m;
let mut i = 0;
while i < SIP_C {
sipround(v);
i += 1;
}
v[0] ^= m;
}
#[inline(always)]
pub fn sip_finish(mut v: SipState, len: usize, tail: u64) -> u64 {
let b = ((len as u64) << 56) | tail;
v[3] ^= b;
let mut i = 0;
while i < SIP_C {
sipround(&mut v);
i += 1;
}
v[0] ^= b;
v[2] ^= 0xff;
i = 0;
while i < SIP_D {
sipround(&mut v);
i += 1;
}
v[0] ^ v[1] ^ v[2] ^ v[3]
}
#[cfg(feature = "user")]
pub fn siphash(k0: u64, k1: u64, data: &[u8]) -> u64 {
let mut v = sip_init(k0, k1);
let len = data.len();
let mut i = 0;
while i + 8 <= len {
let m = u64::from_le_bytes([
data[i],
data[i + 1],
data[i + 2],
data[i + 3],
data[i + 4],
data[i + 5],
data[i + 6],
data[i + 7],
]);
sip_absorb(&mut v, m);
i += 8;
}
let mut tail: u64 = 0;
let mut shift = 0;
while i < len {
tail |= (data[i] as u64) << shift;
shift += 8;
i += 1;
}
sip_finish(v, len, tail)
}
#[cfg(all(test, feature = "user"))]
mod tests {
use super::*;
fn streaming_walk(k0: u64, k1: u64, path: &[u8]) -> (u64, u64, u64, Vec<u64>) {
let mut v = sip_init(k0, k1); let mut full_len = 0usize;
let mut tail: u64 = 0; let mut tail_n = 0usize;
let mut c = sip_init(k0, k1); let mut comp_len = 0usize;
let mut comp_tail: u64 = 0;
let mut comp_n = 0usize;
let mut folder = 0u64;
let mut ancestors = Vec::new();
let absorb = |v: &mut SipState, tail: &mut u64, tn: &mut usize, len: &mut usize, b: u8| {
*tail |= (b as u64) << (8 * *tn);
*tn += 1;
*len += 1;
if *tn == 8 {
sip_absorb(v, *tail);
*tail = 0;
*tn = 0;
}
};
for &b in path {
absorb(&mut v, &mut tail, &mut tail_n, &mut full_len, b);
if b == b'/' {
folder = sip_finish(v, full_len, tail);
ancestors.push(folder);
c = sip_init(k0, k1);
comp_tail = 0;
comp_n = 0;
comp_len = 0;
} else {
absorb(&mut c, &mut comp_tail, &mut comp_n, &mut comp_len, b);
}
}
let full = sip_finish(v, full_len, tail);
let basename = sip_finish(c, comp_len, comp_tail);
(full, folder, basename, ancestors)
}
#[test]
fn matches_published_2_4_vector() {
let k0 = 0x0706_0504_0302_0100;
let k1 = 0x0f0e_0d0c_0b0a_0908;
let msg: Vec<u8> = (0u8..15).collect();
assert_eq!(siphash(k0, k1, &msg), 0xa129_ca61_49be_45e5);
}
#[test]
fn distinct_inputs_and_keys_differ() {
assert_ne!(siphash(1, 2, b"/usr/bin/curl"), siphash(1, 2, b"/usr/bin/wget"));
assert_ne!(siphash(1, 2, b"/usr/bin/curl"), siphash(9, 9, b"/usr/bin/curl"));
}
#[test]
fn streaming_walk_matches_one_shot() {
let (k0, k1) = (0xdead_beef_0bad_f00d, 0x0123_4567_89ab_cdef);
for path in [
&b"/a/bb/ccc/exe"[..],
b"/",
b"x",
b"/home/u/Downloads/installer.sh",
b"no-slashes-here",
b"/exactly8/aligned/", ] {
let (full, folder, basename, ancestors) = streaming_walk(k0, k1, path);
assert_eq!(full, siphash(k0, k1, path), "full {path:?}");
let slashes: Vec<usize> = path
.iter()
.enumerate()
.filter(|(_, &b)| b == b'/')
.map(|(i, _)| i)
.collect();
assert_eq!(ancestors.len(), slashes.len(), "ancestor count {path:?}");
for (k, &end) in slashes.iter().enumerate() {
assert_eq!(ancestors[k], siphash(k0, k1, &path[..=end]), "anc {k} {path:?}");
}
if let Some(&last) = slashes.last() {
assert_eq!(folder, siphash(k0, k1, &path[..=last]), "folder {path:?}");
assert_eq!(basename, siphash(k0, k1, &path[last + 1..]), "base {path:?}");
} else {
assert_eq!(basename, siphash(k0, k1, path), "base-noslash {path:?}");
}
}
}
}