use std::collections::HashSet;
const FNV_OFFSET: u64 = 14_695_981_039_346_656_037;
const FNV_OFFSET_2: u64 = 0x517c_c1b7_2722_0a95;
const FNV_PRIME: u64 = 1_099_511_628_211;
const K: u32 = 7;
fn fnv1a_seeded(data: &[u8], offset: u64) -> u64 {
let mut hash = offset;
for &byte in data {
hash ^= u64::from(byte);
hash = hash.wrapping_mul(FNV_PRIME);
}
hash
}
pub fn bloom_filter_build(values: &[&str], false_positive_rate: f64) -> Vec<u8> {
if values.is_empty() {
return Vec::new();
}
let unique: HashSet<&str> = values.iter().copied().collect();
let n = unique.len().max(1);
let m_f = -(n as f64) * false_positive_rate.ln() / (std::f64::consts::LN_2.powi(2));
let m = (m_f as usize).max(8);
let m_bytes = m.div_ceil(8);
let m_bits = (m_bytes * 8) as u64;
let mut bits = vec![0u8; m_bytes];
for &val in &unique {
let h1 = fnv1a_seeded(val.as_bytes(), FNV_OFFSET);
let h2 = fnv1a_seeded(val.as_bytes(), FNV_OFFSET_2);
for i in 0..K {
let idx = h1.wrapping_add(u64::from(i).wrapping_mul(h2)) % m_bits;
bits[(idx / 8) as usize] |= 1 << (idx % 8);
}
}
bits
}
pub fn bloom_filter_contains(filter: &[u8], value: &str) -> bool {
if filter.is_empty() {
return false;
}
let m_bits = (filter.len() * 8) as u64;
let h1 = fnv1a_seeded(value.as_bytes(), FNV_OFFSET);
let h2 = fnv1a_seeded(value.as_bytes(), FNV_OFFSET_2);
for i in 0..K {
let idx = h1.wrapping_add(u64::from(i).wrapping_mul(h2)) % m_bits;
if filter[(idx / 8) as usize] & (1 << (idx % 8)) == 0 {
return false;
}
}
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build_contains_known_values() {
let values = vec!["server-1", "server-2", "server-3", "us-east-1", "us-west-2"];
let filter = bloom_filter_build(&values, 0.01);
for &v in &values {
assert!(
bloom_filter_contains(&filter, v),
"bloom filter should contain '{v}'"
);
}
}
#[test]
fn rejects_unknown_values() {
let owned: Vec<String> = (0..100).map(|i| format!("host-{i}")).collect();
let values: Vec<&str> = owned.iter().map(String::as_str).collect();
let filter = bloom_filter_build(&values, 0.01);
let mut false_positives = 0;
for i in 1000..2000 {
let test_val = format!("unknown-{i}");
if bloom_filter_contains(&filter, &test_val) {
false_positives += 1;
}
}
assert!(
false_positives < 50,
"too many false positives: {false_positives}/1000 (expected <50)"
);
}
#[test]
fn empty_filter() {
let filter = bloom_filter_build(&[], 0.01);
assert!(filter.is_empty());
assert!(!bloom_filter_contains(&filter, "anything"));
}
#[test]
fn single_value() {
let filter = bloom_filter_build(&["hello"], 0.01);
assert!(bloom_filter_contains(&filter, "hello"));
assert!(!bloom_filter_contains(&filter, "world"));
}
#[test]
fn duplicate_values_handled() {
let values = vec!["a", "a", "b", "b", "c", "c"];
let filter = bloom_filter_build(&values, 0.01);
assert!(bloom_filter_contains(&filter, "a"));
assert!(bloom_filter_contains(&filter, "b"));
assert!(bloom_filter_contains(&filter, "c"));
}
}