use super::Bitmap;
use bytes::{Buf, BufMut};
use commonware_codec::{EncodeSize, Error as CodecError, RangeCfg, Read, ReadExt, Write};
use core::ops::Range;
const CONTAINER_SIZE: u64 = 1 << 16;
const CONTAINER_MASK: u64 = CONTAINER_SIZE - 1;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Prunable {
bitmap: Bitmap,
pruned_below: u64,
}
impl Prunable {
pub const fn new() -> Self {
Self {
bitmap: Bitmap::new(),
pruned_below: 0,
}
}
pub fn len(&self) -> u64 {
self.bitmap.len()
}
pub fn is_empty(&self) -> bool {
self.bitmap.is_empty()
}
pub const fn pruned_below(&self) -> u64 {
self.pruned_below
}
pub fn insert(&mut self, value: u64) -> bool {
assert!(
value >= self.pruned_below,
"value pruned: {value} < pruned_below {}",
self.pruned_below
);
self.bitmap.insert(value)
}
pub fn insert_range(&mut self, range: Range<u64>) -> u64 {
let Range { start, end } = range;
if start >= end {
return 0;
}
assert!(
start >= self.pruned_below,
"start pruned: {start} < pruned_below {}",
self.pruned_below
);
self.bitmap.insert_range(start..end)
}
pub fn contains(&self, value: u64) -> bool {
assert!(
value >= self.pruned_below,
"value pruned: {value} < pruned_below {}",
self.pruned_below
);
self.bitmap.contains(value)
}
pub fn prune(&mut self, threshold: u64) -> u64 {
let aligned = threshold & !CONTAINER_MASK;
if aligned <= self.pruned_below {
return self.pruned_below;
}
let target_key = aligned >> 16;
self.bitmap.truncate_containers_below(target_key);
self.pruned_below = aligned;
aligned
}
pub fn min(&self) -> Option<u64> {
self.bitmap.min()
}
pub fn max(&self) -> Option<u64> {
self.bitmap.max()
}
pub fn iter(&self) -> impl Iterator<Item = u64> + '_ {
self.bitmap.iter()
}
pub fn iter_range(&self, range: Range<u64>) -> impl Iterator<Item = u64> + '_ {
let Range { start, end } = range;
if start >= end {
return self.bitmap.iter_range(0..0);
}
assert!(
start >= self.pruned_below,
"start pruned: {start} < pruned_below {}",
self.pruned_below
);
self.bitmap.iter_range(start..end)
}
pub fn clear(&mut self) {
self.bitmap.clear();
}
}
impl Extend<u64> for Prunable {
fn extend<I: IntoIterator<Item = u64>>(&mut self, iter: I) {
for value in iter {
self.insert(value);
}
}
}
impl FromIterator<u64> for Prunable {
fn from_iter<I: IntoIterator<Item = u64>>(iter: I) -> Self {
let mut p = Self::new();
p.extend(iter);
p
}
}
impl Write for Prunable {
fn write(&self, buf: &mut impl BufMut) {
self.pruned_below.write(buf);
self.bitmap.write(buf);
}
}
impl EncodeSize for Prunable {
fn encode_size(&self) -> usize {
self.pruned_below.encode_size() + self.bitmap.encode_size()
}
}
impl Read for Prunable {
type Cfg = RangeCfg<usize>;
fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, CodecError> {
let pruned_below = u64::read(buf)?;
if pruned_below & CONTAINER_MASK != 0 {
return Err(CodecError::Invalid(
"Prunable",
"pruned_below must be a multiple of 65536",
));
}
let bitmap = Bitmap::read_cfg(buf, cfg)?;
let target_key = pruned_below >> 16;
if let Some((&first_key, _)) = bitmap.containers.first_key_value() {
if first_key < target_key {
return Err(CodecError::Invalid(
"Prunable",
"container key below pruned_below",
));
}
}
Ok(Self {
bitmap,
pruned_below,
})
}
}
#[cfg(feature = "arbitrary")]
impl arbitrary::Arbitrary<'_> for Prunable {
fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
let mut p = Self {
bitmap: Bitmap::arbitrary(u)?,
pruned_below: 0,
};
let max = p.bitmap.max().unwrap_or(0);
let threshold = u.int_in_range(0..=max)?;
p.prune(threshold);
Ok(p)
}
}
#[cfg(test)]
mod tests {
use super::*;
use commonware_codec::{Decode, Encode};
#[test]
fn test_new_and_empty() {
let p = Prunable::new();
assert!(p.is_empty());
assert_eq!(p.len(), 0);
assert_eq!(p.pruned_below(), 0);
}
#[test]
fn test_insert_and_contains() {
let mut p = Prunable::new();
assert!(p.insert(42));
assert!(p.insert(1_000_000));
assert!(!p.insert(42));
assert!(p.contains(42));
assert!(p.contains(1_000_000));
assert!(!p.contains(43));
assert_eq!(p.len(), 2);
}
#[test]
fn test_prune_drops_containers() {
let mut p = Prunable::new();
p.insert(100); p.insert(70_000); p.insert(140_000); p.insert(330_000); assert_eq!(p.len(), 4);
let returned = p.prune(131_072);
assert_eq!(returned, 131_072);
assert_eq!(p.pruned_below(), 131_072);
assert_eq!(p.len(), 2);
assert!(p.contains(140_000));
assert!(p.contains(330_000));
}
#[test]
fn test_prune_rounds_down() {
let mut p = Prunable::new();
p.insert(100); p.insert(70_000);
let returned = p.prune(70_000);
assert_eq!(returned, 65_536);
assert_eq!(p.pruned_below(), 65_536);
assert_eq!(p.len(), 1);
assert!(p.contains(70_000));
}
#[test]
fn test_prune_partial_container_lingers() {
let mut p = Prunable::new();
p.insert(65_500); p.insert(70_000);
p.prune(70_000);
assert!(!p.bitmap.contains(65_500));
assert!(p.contains(70_000));
}
#[test]
fn test_prune_idempotent_below_watermark() {
let mut p = Prunable::new();
p.insert(70_000);
p.prune(65_536);
assert_eq!(p.pruned_below(), 65_536);
let returned = p.prune(0);
assert_eq!(returned, 65_536);
assert_eq!(p.pruned_below(), 65_536);
}
#[test]
fn test_prune_then_insert_above() {
let mut p = Prunable::new();
p.prune(131_072);
p.insert(200_000);
assert!(p.contains(200_000));
assert_eq!(p.len(), 1);
}
#[test]
fn test_prune_zero_no_op() {
let mut p = Prunable::new();
p.insert(100);
let returned = p.prune(0);
assert_eq!(returned, 0);
assert_eq!(p.pruned_below(), 0);
assert_eq!(p.len(), 1);
}
#[test]
#[should_panic(expected = "value pruned")]
fn test_panic_on_insert_below_pruned() {
let mut p = Prunable::new();
p.prune(131_072);
p.insert(50);
}
#[test]
#[should_panic(expected = "value pruned")]
fn test_panic_on_contains_below_pruned() {
let mut p = Prunable::new();
p.prune(131_072);
let _ = p.contains(50);
}
#[test]
#[should_panic(expected = "start pruned")]
fn test_panic_on_insert_range_below_pruned() {
let mut p = Prunable::new();
p.prune(131_072);
p.insert_range(50..100);
}
#[test]
#[should_panic(expected = "start pruned")]
fn test_panic_on_iter_range_below_pruned() {
let mut p = Prunable::new();
p.prune(131_072);
let _ = p.iter_range(50..200_000);
}
#[test]
fn test_empty_insert_ranges_below_pruned_are_no_ops() {
let mut p = Prunable::new();
p.insert(200_000);
p.prune(131_072);
assert_eq!(p.insert_range(50..50), 0);
let start = 50u64;
let end = 10u64;
assert_eq!(p.insert_range(start..end), 0);
assert_eq!(p.len(), 1);
}
#[test]
fn test_empty_iter_ranges_below_pruned_are_empty() {
let mut p = Prunable::new();
p.insert(200_000);
p.prune(131_072);
assert!(p.iter_range(50..50).next().is_none());
let start = 50u64;
let end = 10u64;
assert!(p.iter_range(start..end).next().is_none());
}
#[test]
fn test_iter() {
let mut p = Prunable::new();
for v in [200_000u64, 100, 70_000, 5] {
p.insert(v);
}
p.prune(131_072);
let collected: Vec<u64> = p.iter().collect();
assert_eq!(collected, vec![200_000]);
}
#[test]
fn test_clear_preserves_pruned_below() {
let mut p = Prunable::new();
p.insert(200_000);
p.prune(131_072);
p.clear();
assert!(p.is_empty());
assert_eq!(p.pruned_below(), 131_072);
}
#[test]
fn test_from_iter_basic() {
let p: Prunable = [5u64, 10, 100, 65_537].into_iter().collect();
assert_eq!(p.len(), 4);
assert_eq!(p.pruned_below(), 0);
assert!(p.contains(5));
assert!(p.contains(65_537));
}
#[test]
fn test_from_iter_empty() {
let p: Prunable = core::iter::empty::<u64>().collect();
assert!(p.is_empty());
assert_eq!(p.pruned_below(), 0);
}
#[test]
fn test_extend_after_pruning() {
let mut p = Prunable::new();
p.insert(200_000);
p.prune(131_072);
p.extend([200_001u64, 300_000]);
assert_eq!(p.len(), 3);
assert!(p.contains(200_001));
}
#[test]
#[should_panic(expected = "value pruned")]
fn test_extend_below_pruned_panics() {
let mut p = Prunable::new();
p.prune(131_072);
p.extend([200_000u64, 50]);
}
#[test]
fn test_codec_roundtrip_empty() {
let p = Prunable::new();
let encoded = p.encode();
let decoded = Prunable::decode_cfg(encoded, &(..).into()).unwrap();
assert_eq!(p, decoded);
}
#[test]
fn test_codec_roundtrip_with_pruning() {
let mut p = Prunable::new();
p.insert(200_000);
p.insert(300_000);
p.insert(1_000_000);
p.prune(131_072);
let encoded = p.encode();
let decoded = Prunable::decode_cfg(encoded, &(..).into()).unwrap();
assert_eq!(p, decoded);
}
#[test]
fn test_codec_rejects_misaligned_pruned_below() {
use bytes::BytesMut;
let mut buf = BytesMut::new();
100u64.write(&mut buf);
Bitmap::new().write(&mut buf);
let result = Prunable::decode_cfg(buf.freeze(), &(..).into());
assert!(matches!(
result,
Err(CodecError::Invalid("Prunable", msg))
if msg.contains("multiple of 65536")
));
}
#[test]
fn test_codec_rejects_container_below_pruned() {
use bytes::BytesMut;
let mut buf = BytesMut::new();
131_072u64.write(&mut buf);
let mut bitmap = Bitmap::new();
bitmap.insert(50);
bitmap.write(&mut buf);
let result = Prunable::decode_cfg(buf.freeze(), &(..).into());
assert!(matches!(
result,
Err(CodecError::Invalid("Prunable", msg))
if msg.contains("container key below pruned_below")
));
}
#[test]
fn test_encode_size_empty() {
let p = Prunable::new();
assert_eq!(
p.encode_size(),
0u64.encode_size() + Bitmap::new().encode_size()
);
}
#[test]
fn test_encode_size_grows_with_data() {
let s0 = Prunable::new().encode_size();
let mut p = Prunable::new();
p.insert(100);
p.insert(65_537);
p.insert(131_073);
assert!(p.encode_size() > s0);
}
#[test]
fn test_encode_size_unaffected_by_pruning_alone() {
let mut p = Prunable::new();
p.insert(200_000);
let before = p.encode_size();
p.prune(0);
assert_eq!(p.encode_size(), before);
}
#[cfg(feature = "arbitrary")]
mod conformance {
use commonware_codec::conformance::CodecConformance;
commonware_conformance::conformance_tests! {
CodecConformance<super::Prunable>,
}
}
}