use crate::error::RejectReason;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RemoveBitmap(Vec<u8>);
impl RemoveBitmap {
pub fn from_indices(indices: &[u32]) -> Option<Self> {
if indices.is_empty() {
return None;
}
let max = *indices.iter().max().unwrap();
let mut bytes = vec![0u8; (max / 8) as usize + 1];
for &i in indices {
bytes[(i / 8) as usize] |= 1u8 << (i % 8);
}
Some(Self(bytes))
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, RejectReason> {
if bytes.is_empty() || bytes.last() == Some(&0) {
return Err(RejectReason::BitmapNotCanonical);
}
Ok(Self(bytes.to_vec()))
}
pub fn indices(&self) -> Vec<u32> {
let mut out = Vec::new();
for (byte_index, byte) in self.0.iter().enumerate() {
for bit_index in 0..8u32 {
if byte & (1u8 << bit_index) != 0 {
out.push(byte_index as u32 * 8 + bit_index);
}
}
}
out
}
pub fn max_index(&self) -> u32 {
*self
.indices()
.last()
.expect("canonical bitmap is non-empty")
}
pub fn validate_against(
&self,
entry_count: u32,
section: &'static str,
) -> Result<(), RejectReason> {
if entry_count == 0 || self.max_index() >= entry_count {
return Err(RejectReason::BitmapIndexOutOfRange(section));
}
Ok(())
}
pub fn bytes(&self) -> &[u8] {
&self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn spec_examples() {
assert_eq!(RemoveBitmap::from_indices(&[0]).unwrap().bytes(), &[0x01]);
assert_eq!(RemoveBitmap::from_indices(&[1]).unwrap().bytes(), &[0x02]);
assert_eq!(RemoveBitmap::from_indices(&[2]).unwrap().bytes(), &[0x04]);
assert_eq!(RemoveBitmap::from_indices(&[7]).unwrap().bytes(), &[0x80]);
assert_eq!(
RemoveBitmap::from_indices(&[8]).unwrap().bytes(),
&[0x00, 0x01]
);
}
#[test]
fn noop_is_omitted() {
assert!(RemoveBitmap::from_indices(&[]).is_none());
}
#[test]
fn roundtrip_indices() {
let bm = RemoveBitmap::from_indices(&[0, 3, 9]).unwrap();
assert_eq!(bm.indices(), vec![0, 3, 9]);
let parsed = RemoveBitmap::from_bytes(bm.bytes()).unwrap();
assert_eq!(parsed, bm);
}
#[test]
fn rejects_non_canonical() {
assert_eq!(
RemoveBitmap::from_bytes(&[]).unwrap_err(),
RejectReason::BitmapNotCanonical
);
assert_eq!(
RemoveBitmap::from_bytes(&[0x01, 0x00]).unwrap_err(),
RejectReason::BitmapNotCanonical
);
assert_eq!(
RemoveBitmap::from_bytes(&[0x00]).unwrap_err(),
RejectReason::BitmapNotCanonical
);
}
#[test]
fn rejects_out_of_range() {
let bm = RemoveBitmap::from_indices(&[1]).unwrap();
assert!(bm.validate_against(2, "invariants").is_ok());
assert_eq!(
bm.validate_against(1, "invariants").unwrap_err(),
RejectReason::BitmapIndexOutOfRange("invariants")
);
}
}