subms_bloom_filter/
lib.rs1#[cfg(feature = "harness")]
30pub mod recipe;
31
32#[cfg(any(feature = "counting", feature = "scalable", feature = "partitioned"))]
39pub mod features;
40
41#[cfg(feature = "counting")]
42pub use features::counting::CountingBloomFilter;
43#[cfg(feature = "partitioned")]
44pub use features::partitioned::PartitionedBloomFilter;
45#[cfg(feature = "scalable")]
46pub use features::scalable::ScalableBloomFilter;
47
48use std::io::{self, Write};
49
50pub(crate) const FNV_OFFSET: u64 = 0xcbf29ce484222325;
51pub(crate) const FNV_PRIME: u64 = 0x100000001b3;
52
53pub struct BloomFilter {
54 bit_count: u32,
55 k: u32,
56 bits: Vec<u64>,
57}
58
59impl BloomFilter {
60 pub fn new(expected_entries: usize) -> Self {
63 let bit_count = expected_entries.saturating_mul(10).max(64) as u32;
64 let words = (bit_count as usize).div_ceil(64);
65 Self {
66 bit_count,
67 k: 7,
68 bits: vec![0u64; words],
69 }
70 }
71
72 pub fn add(&mut self, key: &str) {
73 let h = fnv1a64(key);
74 let h1 = h as u32;
75 let h2 = ((h >> 32) as u32) | 1;
76 for i in 0..self.k {
77 let idx = h1.wrapping_add(i.wrapping_mul(h2)) % self.bit_count;
78 self.bits[(idx / 64) as usize] |= 1u64 << (idx % 64);
79 }
80 }
81
82 pub fn might_contain(&self, key: &str) -> bool {
83 let h = fnv1a64(key);
84 let h1 = h as u32;
85 let h2 = ((h >> 32) as u32) | 1;
86 for i in 0..self.k {
87 let idx = h1.wrapping_add(i.wrapping_mul(h2)) % self.bit_count;
88 if self.bits[(idx / 64) as usize] & (1u64 << (idx % 64)) == 0 {
89 return false;
90 }
91 }
92 true
93 }
94
95 pub fn bit_count(&self) -> u32 {
96 self.bit_count
97 }
98
99 pub fn k(&self) -> u32 {
100 self.k
101 }
102
103 pub fn set_bits(&self) -> u64 {
106 self.bits.iter().map(|w| w.count_ones() as u64).sum()
107 }
108
109 pub fn approximate_element_count(&self) -> u64 {
114 let m = self.bit_count as f64;
115 let x = self.set_bits() as f64;
116 if x >= m {
117 return u64::MAX;
118 }
119 let n = -(m / self.k as f64) * (1.0 - x / m).ln();
120 n.round() as u64
121 }
122
123 pub fn estimated_fpp(&self) -> f64 {
127 let ratio = self.set_bits() as f64 / self.bit_count as f64;
128 ratio.powi(self.k as i32)
129 }
130
131 pub fn is_compatible(&self, other: &BloomFilter) -> bool {
134 self.bit_count == other.bit_count
135 && self.k == other.k
136 && self.bits.len() == other.bits.len()
137 }
138
139 pub fn union(&mut self, other: &BloomFilter) -> Result<(), GeometryMismatch> {
143 if !self.is_compatible(other) {
144 return Err(GeometryMismatch {
145 lhs: (self.bit_count, self.k),
146 rhs: (other.bit_count, other.k),
147 });
148 }
149 for (dst, src) in self.bits.iter_mut().zip(&other.bits) {
150 *dst |= *src;
151 }
152 Ok(())
153 }
154
155 pub fn clear(&mut self) {
159 self.bits.fill(0);
160 }
161
162 pub fn write_to<W: Write>(&self, out: &mut W) -> io::Result<()> {
163 out.write_all(&self.bit_count.to_be_bytes())?;
164 out.write_all(&self.k.to_be_bytes())?;
165 out.write_all(&(self.bits.len() as u32).to_be_bytes())?;
166 for w in &self.bits {
167 out.write_all(&w.to_be_bytes())?;
168 }
169 Ok(())
170 }
171
172 pub fn parse(buf: &[u8]) -> io::Result<Self> {
175 if buf.len() < 12 {
176 return Err(io::Error::new(
177 io::ErrorKind::InvalidData,
178 "bloom section too short",
179 ));
180 }
181 let bit_count = u32::from_be_bytes(buf[0..4].try_into().unwrap());
182 let k = u32::from_be_bytes(buf[4..8].try_into().unwrap());
183 let words = u32::from_be_bytes(buf[8..12].try_into().unwrap()) as usize;
184 if buf.len() < 12 + words * 8 {
185 return Err(io::Error::new(
186 io::ErrorKind::InvalidData,
187 "bloom section truncated",
188 ));
189 }
190 let mut bits = Vec::with_capacity(words);
191 for i in 0..words {
192 let off = 12 + i * 8;
193 bits.push(u64::from_be_bytes(buf[off..off + 8].try_into().unwrap()));
194 }
195 Ok(Self { bit_count, k, bits })
196 }
197}
198
199#[derive(Debug, Clone, Copy, PartialEq, Eq)]
204pub struct GeometryMismatch {
205 pub lhs: (u32, u32),
207 pub rhs: (u32, u32),
209}
210
211impl std::fmt::Display for GeometryMismatch {
212 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
213 write!(
214 f,
215 "incompatible bloom geometry: m={} k={} vs m={} k={}",
216 self.lhs.0, self.lhs.1, self.rhs.0, self.rhs.1
217 )
218 }
219}
220
221impl std::error::Error for GeometryMismatch {}
222
223pub(crate) fn fnv1a64(key: &str) -> u64 {
224 let mut h = FNV_OFFSET;
225 for &b in key.as_bytes() {
226 h ^= b as u64;
227 h = h.wrapping_mul(FNV_PRIME);
228 }
229 h
230}
231
232#[cfg(test)]
236#[path = "lib_tests.rs"]
237mod lib_tests;
238
239#[cfg(test)]
240#[path = "sample_app_tests.rs"]
241mod sample_app_tests;