use std::collections::{HashMap, HashSet};
use std::hash::{BuildHasher, Hasher};
use std::sync::OnceLock;
use super::ObjectId;
pub(crate) type IdMap<V> = HashMap<ObjectId, V, IdHashBuilder>;
pub(crate) type IdSet = HashSet<ObjectId, IdHashBuilder>;
pub(crate) type PeerIdMap<V> = HashMap<ObjectId, V>;
pub(crate) type PeerIdSet = HashSet<ObjectId>;
#[derive(Clone, Copy, Debug)]
pub(crate) struct IdHashBuilder {
keys: &'static [u64; 4],
}
impl Default for IdHashBuilder {
fn default() -> Self {
static KEYS: OnceLock<[u64; 4]> = OnceLock::new();
let keys = KEYS.get_or_init(|| {
let random = std::hash::RandomState::new();
odd_multipliers(std::array::from_fn(|n| random.hash_one(n)))
});
Self { keys }
}
}
fn odd_multipliers(mut keys: [u64; 4]) -> [u64; 4] {
keys[1] |= 1;
keys[3] |= 1;
keys
}
impl BuildHasher for IdHashBuilder {
type Hasher = IdHasher;
fn build_hasher(&self) -> IdHasher {
IdHasher {
keys: *self.keys,
state: 0,
}
}
}
#[derive(Debug)]
pub(crate) struct IdHasher {
keys: [u64; 4],
state: u64,
}
impl IdHasher {
fn absorb(&mut self, word: u64) {
let [k0, k1, ..] = self.keys;
self.state = folded_multiply(self.state ^ word ^ k0, k1);
}
}
fn folded_multiply(left: u64, right: u64) -> u64 {
let product = u128::from(left) * u128::from(right);
(product as u64) ^ ((product >> 64) as u64)
}
impl Hasher for IdHasher {
fn write(&mut self, bytes: &[u8]) {
let (words, tail) = bytes.as_chunks::<8>();
for word in words {
self.absorb(u64::from_le_bytes(*word));
}
let mut word = [0; 8];
for (slot, byte) in word.iter_mut().zip(tail) {
*slot = *byte;
}
self.absorb(u64::from_le_bytes(word) ^ ((tail.len() as u64) << 56));
}
fn write_u8(&mut self, value: u8) {
self.absorb(u64::from(value));
}
fn write_usize(&mut self, value: usize) {
self.absorb(value as u64);
}
fn finish(&self) -> u64 {
let [.., k2, k3] = self.keys;
folded_multiply(self.state ^ k2, k3)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn equal_ids_hash_equally_and_formats_stay_distinct_keys() {
let builder = IdHashBuilder::default();
let sha1 = ObjectId::Sha1([7; 20]);
let sha256 = ObjectId::Sha256([7; 32]);
assert_eq!(
builder.hash_one(sha1),
IdHashBuilder::default().hash_one(sha1)
);
let mut set = IdSet::default();
assert!(set.insert(sha1));
assert!(set.insert(sha256));
assert!(!set.insert(ObjectId::Sha1([7; 20])));
assert_eq!(set.len(), 2);
}
fn id(len: usize, at: usize, n: u32) -> ObjectId {
let mut digest = [0xa5; 32];
digest[at..at + 4].copy_from_slice(&n.to_be_bytes());
ObjectId::from_bytes(
if len == 20 {
crate::ObjectFormat::Sha1
} else {
crate::ObjectFormat::Sha256
},
&digest[..len],
)
.unwrap()
}
#[test]
fn ids_differing_anywhere_spread_across_buckets_and_fingerprints() {
let mut state = 0x2545_f491_4f6c_dd1d_u64;
let mut next = || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
for _ in 0..50 {
let keys = odd_multipliers(std::array::from_fn(|_| next()));
let builder = IdHashBuilder {
keys: Box::leak(Box::new(keys)),
};
for len in [20, 32] {
for at in (0..=len - 4).step_by(2) {
let mut buckets = HashSet::new();
let mut fingerprints = HashSet::new();
for n in 0..1024_u32 {
let hash = builder.hash_one(id(len, at, n));
buckets.insert(hash & 0xfff);
fingerprints.insert(hash >> 57);
}
assert!(buckets.len() > 800, "{len}/{at}: {} buckets", buckets.len());
assert!(
fingerprints.len() > 120,
"{len}/{at}: {}",
fingerprints.len()
);
}
}
}
}
#[test]
fn same_prefix_ids_do_not_collide() {
let builder = IdHashBuilder::default();
let hashes: HashSet<_> = (0..4096).map(|n| builder.hash_one(id(20, 16, n))).collect();
assert_eq!(hashes.len(), 4096);
}
#[test]
fn short_writes_include_their_length() {
let builder = IdHashBuilder::default();
let mut short = builder.build_hasher();
short.write(&[0]);
let mut empty = builder.build_hasher();
empty.write(&[]);
assert_ne!(short.finish(), empty.finish());
}
}