use std::path::Path;
const K: u32 = 7;
const RATE: f64 = 0.01;
const LARGEST: usize = 2_000_000;
pub(super) struct Bloom {
k: u32,
bits: Vec<u8>,
m: u64,
}
impl Bloom {
pub(super) fn new(count: usize) -> Self {
let n = count.max(1) as f64;
let m = ((-n * RATE.ln()) / (2f64.ln() * 2f64.ln()))
.ceil()
.max(64.0) as u64;
Self {
k: K,
bits: vec![0; m.div_ceil(8) as usize],
m,
}
}
pub(super) fn for_blobs(root: &Path) -> Option<Self> {
let names: Vec<String> = std::fs::read_dir(root.join("blobs"))
.ok()?
.flatten()
.filter_map(|item| item.file_name().into_string().ok())
.filter(|name| crate::record::valid_hash(name))
.take(LARGEST + 1)
.collect();
if names.is_empty() || names.len() > LARGEST {
return None;
}
let mut bloom = Self::new(names.len());
for name in &names {
bloom.insert(name);
}
Some(bloom)
}
fn positions(&self, hash: &str) -> impl Iterator<Item = u64> + '_ {
let bytes = decode(hash);
let h1 = u64::from_le_bytes(bytes[..8].try_into().unwrap());
let h2 = u64::from_le_bytes(bytes[8..16].try_into().unwrap()) | 1;
(0..u64::from(self.k)).map(move |i| h1.wrapping_add(i.wrapping_mul(h2)) % self.m)
}
pub(super) fn insert(&mut self, hash: &str) {
let positions: Vec<u64> = self.positions(hash).collect();
for bit in positions {
self.bits[(bit >> 3) as usize] |= 1 << (bit & 7);
}
}
#[cfg(test)]
pub(super) fn contains(&self, hash: &str) -> bool {
self.positions(hash)
.all(|bit| self.bits[(bit >> 3) as usize] & (1 << (bit & 7)) != 0)
}
pub(super) fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(16 + self.bits.len());
bytes.extend_from_slice(&1u32.to_le_bytes());
bytes.extend_from_slice(&self.k.to_le_bytes());
bytes.extend_from_slice(&self.m.to_le_bytes());
bytes.extend_from_slice(&self.bits);
bytes
}
}
fn decode(hash: &str) -> [u8; 32] {
let mut bytes = [0; 32];
for (index, pair) in hash.as_bytes().chunks(2).take(32).enumerate() {
let digit = |byte: u8| (byte as char).to_digit(16).unwrap_or(0) as u8;
bytes[index] = digit(pair[0]) << 4 | pair.get(1).map_or(0, |byte| digit(*byte));
}
bytes
}
#[cfg(test)]
mod tests {
use super::*;
pub(crate) const VECTORS: [&str; 3] = [
"0000000000000000000000000000000000000000000000000000000000000000",
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
];
#[test]
fn a_filter_holds_what_was_inserted() {
let mut bloom = Bloom::new(3);
for hash in VECTORS {
bloom.insert(hash);
}
assert!(VECTORS.iter().all(|hash| bloom.contains(hash)));
assert!(!bloom.contains(&"1".repeat(64)));
}
#[test]
fn the_layout_matches_the_protocol() {
let mut bloom = Bloom::new(3);
for hash in VECTORS {
bloom.insert(hash);
}
let bytes = bloom.to_bytes();
assert_eq!(&bytes[..4], &1u32.to_le_bytes());
assert_eq!(&bytes[4..8], &7u32.to_le_bytes());
assert_eq!(u64::from_le_bytes(bytes[8..16].try_into().unwrap()), 64);
assert_eq!(hex(&bytes[16..]), EXPECTED);
}
fn hex(bytes: &[u8]) -> String {
bytes.iter().map(|byte| format!("{byte:02x}")).collect()
}
const EXPECTED: &str = "ff000000000000fe";
}