use std::fs::File;
use std::io::{Read, Write};
use std::path::Path;
use crate::{BloomBitVec, BloomConfig, BloomFilter, ScalableBloomFilter};
const FORMAT_VERSION: u8 = 2;
#[inline]
fn read_u64<R: Read>(r: &mut R) -> std::io::Result<u64> {
let mut buf = [0u8; 8];
r.read_exact(&mut buf)?;
Ok(u64::from_le_bytes(buf))
}
#[inline]
fn write_u64<W: Write>(w: &mut W, v: u64) -> std::io::Result<()> {
w.write_all(&v.to_le_bytes())?;
Ok(())
}
fn write_f64<W: Write>(w: &mut W, v: f64) -> std::io::Result<()> {
w.write_all(&v.to_le_bytes())?;
Ok(())
}
fn read_f64<R: Read>(r: &mut R) -> std::io::Result<f64> {
let mut buf = [0u8; 8];
r.read_exact(&mut buf)?;
Ok(f64::from_le_bytes(buf))
}
pub fn save<P: AsRef<Path>>(filter: &BloomFilter, path: P) -> std::io::Result<()> {
let mut file = File::create(path)?;
file.write_all(&[FORMAT_VERSION])?;
file.write_all(&[0])?;
write_u64(&mut file, filter.size as u64)?;
write_u64(&mut file, filter.hashes as u64)?;
write_u64(&mut file, filter.items as u64)?;
let raw: Vec<u8> = filter.bits.clone().into_vec();
write_u64(&mut file, raw.len() as u64)?;
file.write_all(&raw)?;
Ok(())
}
pub fn load<P: AsRef<Path>>(path: P) -> Option<BloomFilter> {
let mut file = File::open(path).ok()?;
let mut header = [0u8; 2];
file.read_exact(&mut header).ok()?;
if header[0] != FORMAT_VERSION || header[1] != 0 {
return None;
}
let size = read_u64(&mut file).ok()? as usize;
let hashes = read_u64(&mut file).ok()? as usize;
let items = read_u64(&mut file).ok()? as usize;
if size == 0 || hashes == 0 {
return None;
}
let raw_len = read_u64(&mut file).ok()? as usize;
let mut raw = vec![0u8; raw_len];
file.read_exact(&mut raw).ok()?;
let bits = BloomBitVec::from_vec(raw);
if bits.len() < size {
return None;
}
Some(BloomFilter {
bits,
size,
hashes,
items,
target_path: None,
needs_save: false,
})
}
pub fn save_scalable<P: AsRef<Path>>(
scalable: &ScalableBloomFilter,
path: P,
) -> std::io::Result<()> {
let mut file = File::create(path)?;
file.write_all(&[FORMAT_VERSION])?;
file.write_all(&[1])?;
write_u64(&mut file, scalable.config.expected_items as u64)?;
write_f64(&mut file, scalable.config.false_positive_rate)?;
write_f64(&mut file, scalable.tightening_ratio)?;
write_u64(&mut file, scalable.growth_factor as u64)?;
let layers = scalable.filters.len();
write_u64(&mut file, layers as u64)?;
for i in 0..layers {
let filter = &scalable.filters[i];
write_u64(&mut file, filter.size as u64)?;
write_u64(&mut file, filter.hashes as u64)?;
write_u64(&mut file, filter.items as u64)?;
let raw: Vec<u8> = filter.bits.clone().into_vec();
write_u64(&mut file, raw.len() as u64)?;
file.write_all(&raw)?;
}
Ok(())
}
pub fn load_scalable<P: AsRef<Path>>(path: P) -> Option<ScalableBloomFilter> {
let mut file = File::open(path).ok()?;
let mut header = [0u8; 2];
file.read_exact(&mut header).ok()?;
if header[0] != FORMAT_VERSION || header[1] != 1 {
return None;
}
let exp_items = read_u64(&mut file).ok()? as usize;
let fp_rate = read_f64(&mut file).ok()?;
let tightening = read_f64(&mut file).ok()?;
let growth = read_u64(&mut file).ok()? as usize;
let config = BloomConfig::new(exp_items, fp_rate);
let layers = read_u64(&mut file).ok()? as usize;
let mut filters = Vec::with_capacity(layers);
for _ in 0..layers {
let size = read_u64(&mut file).ok()? as usize;
let hashes = read_u64(&mut file).ok()? as usize;
let items = read_u64(&mut file).ok()? as usize;
let raw_len = read_u64(&mut file).ok()? as usize;
let mut raw = vec![0u8; raw_len];
file.read_exact(&mut raw).ok()?;
let bits = BloomBitVec::from_vec(raw);
filters.push(BloomFilter {
bits,
size,
hashes,
items,
target_path: None,
needs_save: false,
});
}
Some(ScalableBloomFilter {
filters,
config,
tightening_ratio: tightening,
growth_factor: growth,
target_path: None,
needs_save: false,
})
}