use crate::common::{hash::xxhash, Reconcilable, Result, SetDifference, SketchError};
#[derive(Clone)]
pub struct RatelessIBLT {
num_cells: usize,
cells: Vec<IBLTCell>,
hash_functions: usize,
cell_size: usize,
}
#[derive(Clone, Debug)]
struct IBLTCell {
sum: Vec<u8>,
count: i32,
key_sum: Vec<u8>,
}
impl IBLTCell {
fn new(cell_size: usize) -> Self {
Self {
sum: vec![0u8; cell_size],
count: 0,
key_sum: vec![0u8; cell_size],
}
}
fn is_singleton(&self) -> bool {
self.count == 1 || self.count == -1
}
fn is_empty(&self) -> bool {
self.count == 0
}
fn add(&mut self, key: &[u8], value: &[u8]) {
Self::xor_data(&mut self.key_sum, key);
Self::xor_data(&mut self.sum, value);
self.count += 1;
}
fn remove(&mut self, key: &[u8], value: &[u8]) {
Self::xor_data(&mut self.key_sum, key);
Self::xor_data(&mut self.sum, value);
self.count -= 1;
}
fn xor_data(buffer: &mut Vec<u8>, data: &[u8]) {
if data.len() > buffer.len() {
buffer.resize(data.len(), 0);
}
for (i, &byte) in data.iter().enumerate() {
buffer[i] ^= byte;
}
}
fn extract_pair(&self) -> (Vec<u8>, Vec<u8>) {
let key = Self::trim_zeros(&self.key_sum);
let value = Self::trim_zeros(&self.sum);
(key, value)
}
fn trim_zeros(data: &[u8]) -> Vec<u8> {
let mut end = data.len();
while end > 0 && data[end - 1] == 0 {
end -= 1;
}
data[..end].to_vec()
}
}
#[derive(Debug, Clone)]
pub struct RatelessIBLTStats {
pub num_cells: usize,
pub cell_size: usize,
}
impl RatelessIBLT {
pub fn new(expected_diff: usize, cell_size: usize) -> Result<Self> {
if expected_diff == 0 {
return Err(SketchError::InvalidParameter {
param: "expected_diff".to_string(),
value: "0".to_string(),
constraint: "must be > 0".to_string(),
});
}
if cell_size < 8 {
return Err(SketchError::InvalidParameter {
param: "cell_size".to_string(),
value: cell_size.to_string(),
constraint: "must be >= 8".to_string(),
});
}
let c_factor = 2.0;
let num_cells = ((expected_diff as f64 * c_factor).ceil() as usize).max(8);
let cells = (0..num_cells).map(|_| IBLTCell::new(cell_size)).collect();
Ok(Self {
num_cells,
cells,
hash_functions: 3, cell_size,
})
}
pub fn insert(&mut self, key: &[u8], value: &[u8]) -> Result<()> {
let positions = self.hash_key(key);
for pos in positions {
self.cells[pos].add(key, value);
}
Ok(())
}
pub fn delete(&mut self, key: &[u8], value: &[u8]) -> Result<()> {
let positions = self.hash_key(key);
for pos in positions {
self.cells[pos].remove(key, value);
}
Ok(())
}
pub fn stats(&self) -> RatelessIBLTStats {
RatelessIBLTStats {
num_cells: self.num_cells,
cell_size: self.cell_size,
}
}
fn hash_key(&self, key: &[u8]) -> Vec<usize> {
let mut positions = Vec::with_capacity(self.hash_functions);
for i in 0..self.hash_functions {
let hash = xxhash(key, i as u64);
let pos = (hash as usize) % self.num_cells;
positions.push(pos);
}
positions
}
}
impl Reconcilable for RatelessIBLT {
fn subtract(&mut self, other: &Self) -> Result<()> {
if self.num_cells != other.num_cells {
return Err(SketchError::IncompatibleSketches {
reason: format!(
"Different number of cells: {} vs {}",
self.num_cells, other.num_cells
),
});
}
if self.cell_size != other.cell_size {
return Err(SketchError::IncompatibleSketches {
reason: format!(
"Different cell sizes: {} vs {}",
self.cell_size, other.cell_size
),
});
}
for (i, other_cell) in other.cells.iter().enumerate() {
let cell = &mut self.cells[i];
for j in 0..cell.sum.len().min(other_cell.sum.len()) {
cell.sum[j] ^= other_cell.sum[j];
}
for j in 0..cell.key_sum.len().min(other_cell.key_sum.len()) {
cell.key_sum[j] ^= other_cell.key_sum[j];
}
cell.count -= other_cell.count;
}
Ok(())
}
fn decode(&self) -> Result<SetDifference> {
let mut working = self.clone();
let mut to_insert = Vec::new();
let mut to_remove = Vec::new();
let max_iterations = self.num_cells * 100;
let mut iterations = 0;
let mut prev_singleton_count = usize::MAX;
let mut stuck_count = 0;
loop {
iterations += 1;
if iterations > max_iterations {
return Err(SketchError::ReconciliationError {
reason: "Decode iteration limit exceeded".to_string(),
});
}
let singletons: Vec<usize> = working
.cells
.iter()
.enumerate()
.filter(|(_, cell)| cell.is_singleton())
.map(|(idx, _)| idx)
.collect();
let singleton_count = singletons.len();
if singleton_count == 0 {
let all_empty = working.cells.iter().all(|cell| cell.is_empty());
if all_empty {
break;
} else {
let non_empty = working.cells.iter().filter(|c| !c.is_empty()).count();
return Err(SketchError::ReconciliationError {
reason: format!(
"Unable to decode: {} non-empty cells remain without singletons",
non_empty
),
});
}
}
if singleton_count == prev_singleton_count {
stuck_count += 1;
if stuck_count > 10 {
return Err(SketchError::ReconciliationError {
reason: "Decode stalled: no progress after multiple iterations".to_string(),
});
}
} else {
stuck_count = 0;
}
prev_singleton_count = singleton_count;
if let Some(&idx) = singletons.first() {
let cell = &working.cells[idx];
let count = cell.count;
let (key, value) = cell.extract_pair();
if count > 0 {
to_insert.push((key.clone(), value.clone()));
} else {
to_remove.push((key.clone(), value.clone()));
}
let positions = working.hash_key(&key);
for pos in positions {
if count > 0 {
working.cells[pos].remove(&key, &value);
} else {
working.cells[pos].add(&key, &value);
}
}
}
}
Ok(SetDifference {
to_insert,
to_remove,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_construction() {
let iblt = RatelessIBLT::new(100, 32);
assert!(iblt.is_ok());
}
#[test]
fn test_basic_insert() {
let mut iblt = RatelessIBLT::new(10, 32).unwrap();
let result = iblt.insert(b"key", b"value");
assert!(result.is_ok());
}
#[test]
fn test_basic_decode() {
let mut iblt = RatelessIBLT::new(10, 32).unwrap();
iblt.insert(b"key", b"value").unwrap();
let diff = iblt.decode().unwrap();
assert_eq!(diff.to_insert.len(), 1);
}
#[test]
fn test_basic_subtraction() {
let mut iblt1 = RatelessIBLT::new(10, 32).unwrap();
let iblt2 = RatelessIBLT::new(10, 32).unwrap();
iblt1.insert(b"key", b"value").unwrap();
iblt1.subtract(&iblt2).unwrap();
let diff = iblt1.decode().unwrap();
assert_eq!(diff.to_insert.len(), 1);
}
#[test]
fn test_cell_is_singleton() {
let mut cell = IBLTCell::new(32);
assert!(!cell.is_singleton());
cell.count = 1;
assert!(cell.is_singleton());
cell.count = -1;
assert!(cell.is_singleton());
cell.count = 2;
assert!(!cell.is_singleton());
}
#[test]
fn test_cell_xor_operations() {
let mut cell = IBLTCell::new(32);
cell.add(b"key1", b"value1");
assert_eq!(cell.count, 1);
cell.add(b"key1", b"value1");
assert_eq!(cell.count, 2);
cell.remove(b"key1", b"value1");
assert_eq!(cell.count, 1);
}
}