pic_continuity/authority/
bitmap.rs1use crate::error::RejectReason;
27
28#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct RemoveBitmap(Vec<u8>);
32
33impl RemoveBitmap {
34 pub fn from_indices(indices: &[u32]) -> Option<Self> {
37 if indices.is_empty() {
38 return None;
39 }
40 let max = *indices.iter().max().unwrap();
41 let mut bytes = vec![0u8; (max / 8) as usize + 1];
42 for &i in indices {
43 bytes[(i / 8) as usize] |= 1u8 << (i % 8);
44 }
45 Some(Self(bytes))
46 }
47
48 pub fn from_bytes(bytes: &[u8]) -> Result<Self, RejectReason> {
51 if bytes.is_empty() || bytes.last() == Some(&0) {
52 return Err(RejectReason::BitmapNotCanonical);
53 }
54 Ok(Self(bytes.to_vec()))
55 }
56
57 pub fn indices(&self) -> Vec<u32> {
59 let mut out = Vec::new();
60 for (byte_index, byte) in self.0.iter().enumerate() {
61 for bit_index in 0..8u32 {
62 if byte & (1u8 << bit_index) != 0 {
63 out.push(byte_index as u32 * 8 + bit_index);
64 }
65 }
66 }
67 out
68 }
69
70 pub fn max_index(&self) -> u32 {
72 *self
74 .indices()
75 .last()
76 .expect("canonical bitmap is non-empty")
77 }
78
79 pub fn validate_against(
82 &self,
83 entry_count: u32,
84 section: &'static str,
85 ) -> Result<(), RejectReason> {
86 if entry_count == 0 || self.max_index() >= entry_count {
87 return Err(RejectReason::BitmapIndexOutOfRange(section));
88 }
89 Ok(())
90 }
91
92 pub fn bytes(&self) -> &[u8] {
94 &self.0
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn spec_examples() {
104 assert_eq!(RemoveBitmap::from_indices(&[0]).unwrap().bytes(), &[0x01]);
105 assert_eq!(RemoveBitmap::from_indices(&[1]).unwrap().bytes(), &[0x02]);
106 assert_eq!(RemoveBitmap::from_indices(&[2]).unwrap().bytes(), &[0x04]);
107 assert_eq!(RemoveBitmap::from_indices(&[7]).unwrap().bytes(), &[0x80]);
108 assert_eq!(
109 RemoveBitmap::from_indices(&[8]).unwrap().bytes(),
110 &[0x00, 0x01]
111 );
112 }
113
114 #[test]
115 fn noop_is_omitted() {
116 assert!(RemoveBitmap::from_indices(&[]).is_none());
117 }
118
119 #[test]
120 fn roundtrip_indices() {
121 let bm = RemoveBitmap::from_indices(&[0, 3, 9]).unwrap();
122 assert_eq!(bm.indices(), vec![0, 3, 9]);
123 let parsed = RemoveBitmap::from_bytes(bm.bytes()).unwrap();
124 assert_eq!(parsed, bm);
125 }
126
127 #[test]
128 fn rejects_non_canonical() {
129 assert_eq!(
130 RemoveBitmap::from_bytes(&[]).unwrap_err(),
131 RejectReason::BitmapNotCanonical
132 );
133 assert_eq!(
134 RemoveBitmap::from_bytes(&[0x01, 0x00]).unwrap_err(),
135 RejectReason::BitmapNotCanonical
136 );
137 assert_eq!(
138 RemoveBitmap::from_bytes(&[0x00]).unwrap_err(),
139 RejectReason::BitmapNotCanonical
140 );
141 }
142
143 #[test]
144 fn rejects_out_of_range() {
145 let bm = RemoveBitmap::from_indices(&[1]).unwrap();
146 assert!(bm.validate_against(2, "invariants").is_ok());
147 assert_eq!(
148 bm.validate_against(1, "invariants").unwrap_err(),
149 RejectReason::BitmapIndexOutOfRange("invariants")
150 );
151 }
152}