Skip to main content

commonware_utils/bitmap/
prunable.rs

1//! A prunable wrapper around BitMap that tracks pruned chunks.
2
3use super::BitMap;
4use bytes::{Buf, BufMut};
5use commonware_codec::{EncodeSize, Error as CodecError, Read, ReadExt, Write};
6use thiserror::Error;
7
8/// Errors that can occur when working with a prunable bitmap.
9#[derive(Debug, Error, Clone, PartialEq, Eq)]
10pub enum Error {
11    /// The provided pruned_chunks value would overflow.
12    #[error("pruned_chunks * CHUNK_SIZE_BITS overflows u64")]
13    PrunedChunksOverflow,
14}
15
16/// A prunable bitmap that stores data in chunks of N bytes.
17///
18/// # Panics
19///
20/// Operations panic if `bit / CHUNK_SIZE_BITS > usize::MAX`. On 32-bit systems
21/// with N=32, this occurs at bit >= 1,099,511,627,776.
22#[derive(Clone, Debug)]
23pub struct Prunable<const N: usize> {
24    /// The underlying BitMap storing the actual bits.
25    bitmap: BitMap<N>,
26
27    /// The number of bitmap chunks that have been pruned.
28    ///
29    /// # Invariant
30    ///
31    /// Must satisfy: `pruned_chunks as u64 * CHUNK_SIZE_BITS + bitmap.len() <= u64::MAX`
32    pruned_chunks: usize,
33}
34
35impl<const N: usize> Prunable<N> {
36    /// The size of a chunk in bits.
37    pub const CHUNK_SIZE_BITS: u64 = BitMap::<N>::CHUNK_SIZE_BITS;
38
39    /* Constructors */
40
41    /// Create a new empty prunable bitmap.
42    pub const fn new() -> Self {
43        Self {
44            bitmap: BitMap::new(),
45            pruned_chunks: 0,
46        }
47    }
48
49    /// Create a new empty prunable bitmap with the given number of pruned chunks.
50    ///
51    /// # Errors
52    ///
53    /// Returns an error if `pruned_chunks` violates the invariant that
54    /// `pruned_chunks as u64 * CHUNK_SIZE_BITS` must not overflow u64.
55    pub fn new_with_pruned_chunks(pruned_chunks: usize) -> Result<Self, Error> {
56        // Validate the invariant: pruned_chunks * CHUNK_SIZE_BITS must fit in u64
57        let pruned_chunks_u64 = pruned_chunks as u64;
58        pruned_chunks_u64
59            .checked_mul(Self::CHUNK_SIZE_BITS)
60            .ok_or(Error::PrunedChunksOverflow)?;
61
62        Ok(Self {
63            bitmap: BitMap::new(),
64            pruned_chunks,
65        })
66    }
67
68    /* Length */
69
70    /// Return the number of bits in the bitmap, irrespective of any pruning.
71    #[inline]
72    pub const fn len(&self) -> u64 {
73        let pruned_bits = (self.pruned_chunks as u64)
74            .checked_mul(Self::CHUNK_SIZE_BITS)
75            .expect("invariant violated: pruned_chunks * CHUNK_SIZE_BITS overflows u64");
76
77        pruned_bits
78            .checked_add(self.bitmap.len())
79            .expect("invariant violated: pruned_bits + bitmap.len() overflows u64")
80    }
81
82    /// Return true if the bitmap is empty.
83    #[inline]
84    pub const fn is_empty(&self) -> bool {
85        self.len() == 0
86    }
87
88    /// Returns true if the bitmap length is aligned to a chunk boundary.
89    #[inline]
90    pub const fn is_chunk_aligned(&self) -> bool {
91        self.len().is_multiple_of(Self::CHUNK_SIZE_BITS)
92    }
93
94    /// Return the number of chunks, including pruned chunks.
95    #[inline]
96    pub fn chunks_len(&self) -> usize {
97        self.pruned_chunks + self.bitmap.chunks_len()
98    }
99
100    /// Return the number of pruned chunks.
101    #[inline]
102    pub const fn pruned_chunks(&self) -> usize {
103        self.pruned_chunks
104    }
105
106    /// Return the number of complete (fully filled) chunks in the bitmap, accounting
107    /// for pruning. A chunk is complete when all CHUNK_SIZE_BITS bits have been written.
108    #[inline]
109    pub fn complete_chunks(&self) -> usize {
110        self.pruned_chunks
111            + self
112                .bitmap
113                .chunks_len()
114                .saturating_sub(if self.is_chunk_aligned() { 0 } else { 1 })
115    }
116
117    /// Return the number of pruned bits.
118    #[inline]
119    pub const fn pruned_bits(&self) -> u64 {
120        (self.pruned_chunks as u64)
121            .checked_mul(Self::CHUNK_SIZE_BITS)
122            .expect("invariant violated: pruned_chunks * CHUNK_SIZE_BITS overflows u64")
123    }
124
125    /* Getters */
126
127    /// Get the value of a bit.
128    ///
129    /// # Warning
130    ///
131    /// Panics if the bit doesn't exist or has been pruned.
132    #[inline]
133    pub fn get_bit(&self, bit: u64) -> bool {
134        let chunk_num = Self::to_chunk_index(bit);
135        assert!(chunk_num >= self.pruned_chunks, "bit pruned: {bit}");
136
137        // Adjust bit to account for pruning
138        self.bitmap.get(bit - self.pruned_bits())
139    }
140
141    /// Returns the bitmap chunk containing the specified bit.
142    ///
143    /// # Warning
144    ///
145    /// Panics if the bit doesn't exist or has been pruned.
146    #[inline]
147    pub fn get_chunk_containing(&self, bit: u64) -> &[u8; N] {
148        let chunk_num = Self::to_chunk_index(bit);
149        assert!(chunk_num >= self.pruned_chunks, "bit pruned: {bit}");
150
151        // Adjust bit to account for pruning
152        self.bitmap.get_chunk_containing(bit - self.pruned_bits())
153    }
154
155    /// Get the value of a bit from its chunk.
156    /// `bit` is an index into the entire bitmap, not just the chunk.
157    #[inline]
158    pub const fn get_bit_from_chunk(chunk: &[u8; N], bit: u64) -> bool {
159        BitMap::<N>::get_bit_from_chunk(chunk, bit)
160    }
161
162    /// Return the last chunk of the bitmap and its size in bits.
163    #[inline]
164    pub fn last_chunk(&self) -> (&[u8; N], u64) {
165        self.bitmap.last_chunk()
166    }
167
168    /* Setters */
169
170    /// Set the value of the given bit.
171    ///
172    /// # Warning
173    ///
174    /// Panics if the bit doesn't exist or has been pruned.
175    pub fn set_bit(&mut self, bit: u64, value: bool) {
176        let chunk_num = Self::to_chunk_index(bit);
177        assert!(chunk_num >= self.pruned_chunks, "bit pruned: {bit}");
178
179        // Adjust bit to account for pruning
180        self.bitmap.set(bit - self.pruned_bits(), value);
181    }
182
183    /// Add a single bit to the end of the bitmap.
184    pub fn push(&mut self, bit: bool) {
185        self.bitmap.push(bit);
186    }
187
188    /// Extend the bitmap to `new_len` total bits (including pruned bits), filling new positions
189    /// with zero. No-op if `new_len <= self.len()`.
190    pub fn extend_to(&mut self, new_len: u64) {
191        let current = self.len();
192        if new_len > current {
193            self.bitmap.extend_to(new_len - self.pruned_bits());
194        }
195    }
196
197    /// Remove and return the last bit from the bitmap.
198    ///
199    /// # Warning
200    ///
201    /// Panics if the bitmap is empty.
202    pub fn pop(&mut self) -> bool {
203        self.bitmap.pop()
204    }
205
206    /// Shrink the bitmap to `new_len` total bits (including pruned bits).
207    ///
208    /// # Panics
209    ///
210    /// Panics if `new_len > self.len()` or `new_len < self.pruned_bits()`.
211    pub fn truncate(&mut self, new_len: u64) {
212        assert!(
213            new_len >= self.pruned_bits(),
214            "cannot truncate into pruned region"
215        );
216        self.bitmap.truncate(new_len - self.pruned_bits());
217    }
218
219    /// Add a byte to the bitmap.
220    ///
221    /// # Warning
222    ///
223    /// Panics if self.next_bit is not byte aligned.
224    pub fn push_byte(&mut self, byte: u8) {
225        self.bitmap.push_byte(byte);
226    }
227
228    /// Add a chunk of bits to the bitmap.
229    ///
230    /// # Warning
231    ///
232    /// Panics if self.next_bit is not chunk aligned.
233    pub fn push_chunk(&mut self, chunk: &[u8; N]) {
234        self.bitmap.push_chunk(chunk);
235    }
236
237    /// Remove and return the last complete chunk from the bitmap.
238    ///
239    /// # Warning
240    ///
241    /// Panics if the bitmap has fewer than `CHUNK_SIZE_BITS` bits or if not chunk-aligned.
242    pub fn pop_chunk(&mut self) -> [u8; N] {
243        self.bitmap.pop_chunk()
244    }
245
246    /* Pruning */
247
248    /// Prune all complete chunks before the chunk containing the given bit.
249    ///
250    /// The chunk containing `bit` and all subsequent chunks are retained. All chunks
251    /// before it are pruned.
252    ///
253    /// If `bit` equals the bitmap length, this prunes all complete chunks while retaining
254    /// the empty trailing chunk, preparing the bitmap for appending new data.
255    ///
256    /// # Warning
257    ///
258    /// Panics if `bit` is greater than the bitmap length.
259    pub fn prune_to_bit(&mut self, bit: u64) {
260        assert!(
261            bit <= self.len(),
262            "bit {} out of bounds (len: {})",
263            bit,
264            self.len()
265        );
266
267        let chunk = Self::to_chunk_index(bit);
268        if chunk < self.pruned_chunks {
269            return;
270        }
271
272        let chunks_to_prune = chunk - self.pruned_chunks;
273        self.bitmap.prune_chunks(chunks_to_prune);
274        self.pruned_chunks = chunk;
275    }
276
277    /* Indexing Helpers */
278
279    /// Convert a bit into a bitmask for the byte containing that bit.
280    #[inline]
281    pub const fn chunk_byte_bitmask(bit: u64) -> u8 {
282        BitMap::<N>::chunk_byte_bitmask(bit)
283    }
284
285    /// Convert a bit into the index of the byte within a chunk containing the bit.
286    #[inline]
287    pub const fn chunk_byte_offset(bit: u64) -> usize {
288        BitMap::<N>::chunk_byte_offset(bit)
289    }
290
291    /// Convert a bit into the index of the chunk it belongs to.
292    ///
293    /// # Panics
294    ///
295    /// Panics if `bit / CHUNK_SIZE_BITS > usize::MAX`.
296    #[inline]
297    pub fn to_chunk_index(bit: u64) -> usize {
298        BitMap::<N>::to_chunk_index(bit)
299    }
300
301    /// Get a reference to a chunk by its absolute index (includes pruned chunks).
302    ///
303    /// # Panics
304    ///
305    /// Panics if the chunk has been pruned or is out of bounds.
306    #[inline]
307    pub fn get_chunk(&self, chunk: usize) -> &[u8; N] {
308        assert!(
309            chunk >= self.pruned_chunks,
310            "chunk {chunk} is pruned (pruned_chunks: {})",
311            self.pruned_chunks
312        );
313        self.bitmap.get_chunk(chunk - self.pruned_chunks)
314    }
315
316    /// Overwrite a chunk's data by its absolute chunk index.
317    ///
318    /// # Panics
319    ///
320    /// Panics if the chunk is pruned or out of bounds, or if this is the last partial
321    /// chunk and `chunk_data` has non-zero bits beyond `len()`.
322    pub fn set_chunk_by_index(&mut self, chunk_index: usize, chunk_data: &[u8; N]) {
323        assert!(
324            chunk_index >= self.pruned_chunks,
325            "cannot set pruned chunk {chunk_index} (pruned_chunks: {})",
326            self.pruned_chunks
327        );
328        let bitmap_chunk_idx = chunk_index - self.pruned_chunks;
329
330        // Reject non-zero trailing bits in the last partial chunk to maintain the
331        // canonicalization invariant required by the codec.
332        if bitmap_chunk_idx + 1 == self.bitmap.chunks_len() {
333            let trailing = self.bitmap.len() % Self::CHUNK_SIZE_BITS;
334            if trailing != 0 {
335                let last_byte = ((trailing - 1) / 8) as usize;
336                let bits_in_last_byte = (trailing % 8) as u32;
337                let byte_mask = if bits_in_last_byte != 0 {
338                    !((1u8 << bits_in_last_byte) - 1)
339                } else {
340                    0
341                };
342                assert!(
343                    chunk_data[last_byte] & byte_mask == 0
344                        && chunk_data[last_byte + 1..] == [0u8; N][last_byte + 1..],
345                    "non-zero bits beyond bitmap length"
346                );
347            }
348        }
349
350        self.bitmap.set_chunk_by_index(bitmap_chunk_idx, chunk_data);
351    }
352
353    /// Unprune chunks by prepending them back to the front of the bitmap.
354    ///
355    /// The caller must provide chunks in **reverse** order: to restore chunks with
356    /// indices [0, 1, 2], pass them as [2, 1, 0]. This is necessary because each chunk
357    /// is prepended to the front, so the last chunk provided becomes the first chunk
358    /// in the bitmap.
359    ///
360    /// # Panics
361    ///
362    /// Panics if chunks.len() > self.pruned_chunks.
363    pub(super) fn unprune_chunks(&mut self, chunks: &[[u8; N]]) {
364        assert!(
365            chunks.len() <= self.pruned_chunks,
366            "cannot unprune {} chunks (only {} pruned)",
367            chunks.len(),
368            self.pruned_chunks
369        );
370
371        for chunk in chunks.iter() {
372            self.bitmap.prepend_chunk(chunk);
373        }
374
375        self.pruned_chunks -= chunks.len();
376    }
377}
378
379impl<const N: usize> super::Readable<N> for Prunable<N> {
380    fn complete_chunks(&self) -> usize {
381        Self::complete_chunks(self)
382    }
383    fn get_chunk(&self, chunk: usize) -> [u8; N] {
384        *Self::get_chunk(self, chunk)
385    }
386    fn last_chunk(&self) -> ([u8; N], u64) {
387        let (c, n) = Self::last_chunk(self);
388        (*c, n)
389    }
390    fn pruned_chunks(&self) -> usize {
391        self.pruned_chunks
392    }
393    fn len(&self) -> u64 {
394        Self::len(self)
395    }
396}
397
398impl<const N: usize> Default for Prunable<N> {
399    fn default() -> Self {
400        Self::new()
401    }
402}
403
404impl<const N: usize> Write for Prunable<N> {
405    fn write(&self, buf: &mut impl BufMut) {
406        (self.pruned_chunks as u64).write(buf);
407        self.bitmap.write(buf);
408    }
409}
410
411impl<const N: usize> Read for Prunable<N> {
412    // Max length for the unpruned portion of the bitmap.
413    type Cfg = u64;
414
415    fn read_cfg(buf: &mut impl Buf, max_len: &Self::Cfg) -> Result<Self, CodecError> {
416        let pruned_chunks_u64 = u64::read(buf)?;
417
418        // Validate that pruned_chunks * CHUNK_SIZE_BITS doesn't overflow u64
419        let pruned_bits =
420            pruned_chunks_u64
421                .checked_mul(Self::CHUNK_SIZE_BITS)
422                .ok_or(CodecError::Invalid(
423                    "Prunable",
424                    "pruned_chunks would overflow when computing pruned_bits",
425                ))?;
426
427        let pruned_chunks = usize::try_from(pruned_chunks_u64)
428            .map_err(|_| CodecError::Invalid("Prunable", "pruned_chunks doesn't fit in usize"))?;
429
430        let bitmap = BitMap::<N>::read_cfg(buf, max_len)?;
431
432        // Validate that total length (pruned_bits + bitmap.len()) doesn't overflow u64
433        pruned_bits
434            .checked_add(bitmap.len())
435            .ok_or(CodecError::Invalid(
436                "Prunable",
437                "total bitmap length (pruned + unpruned) would overflow u64",
438            ))?;
439
440        Ok(Self {
441            bitmap,
442            pruned_chunks,
443        })
444    }
445}
446
447impl<const N: usize> EncodeSize for Prunable<N> {
448    fn encode_size(&self) -> usize {
449        (self.pruned_chunks as u64).encode_size() + self.bitmap.encode_size()
450    }
451}
452
453#[cfg(feature = "arbitrary")]
454impl<const N: usize> arbitrary::Arbitrary<'_> for Prunable<N> {
455    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
456        let mut bitmap = Self {
457            bitmap: BitMap::<N>::arbitrary(u)?,
458            pruned_chunks: 0,
459        };
460        let prune_to = u.int_in_range(0..=bitmap.len())?;
461        bitmap.prune_to_bit(prune_to);
462        Ok(bitmap)
463    }
464}
465
466#[cfg(test)]
467mod tests {
468    use super::{super::Readable, *};
469    use crate::hex;
470    use bytes::BytesMut;
471    use commonware_codec::Encode;
472
473    #[test]
474    fn test_new() {
475        let prunable: Prunable<32> = Prunable::new();
476        assert_eq!(prunable.len(), 0);
477        assert_eq!(prunable.pruned_bits(), 0);
478        assert_eq!(prunable.pruned_chunks(), 0);
479        assert!(prunable.is_empty());
480        assert_eq!(prunable.chunks_len(), 0); // No chunks when empty
481    }
482
483    #[test]
484    fn test_new_with_pruned_chunks() {
485        let prunable: Prunable<2> = Prunable::new_with_pruned_chunks(1).unwrap();
486        assert_eq!(prunable.len(), 16);
487        assert_eq!(prunable.pruned_bits(), 16);
488        assert_eq!(prunable.pruned_chunks(), 1);
489        assert_eq!(prunable.chunks_len(), 1);
490    }
491
492    #[test]
493    fn test_new_with_pruned_chunks_overflow() {
494        // Try to create a Prunable with pruned_chunks that would overflow
495        let overflowing_pruned_chunks = (u64::MAX / Prunable::<4>::CHUNK_SIZE_BITS) as usize + 1;
496        let result = Prunable::<4>::new_with_pruned_chunks(overflowing_pruned_chunks);
497
498        assert!(matches!(result, Err(Error::PrunedChunksOverflow)));
499    }
500
501    #[test]
502    fn test_push_and_get_bits() {
503        let mut prunable: Prunable<4> = Prunable::new();
504
505        // Add some bits
506        prunable.push(true);
507        prunable.push(false);
508        prunable.push(true);
509
510        assert_eq!(prunable.len(), 3);
511        assert!(!prunable.is_empty());
512        assert!(prunable.get_bit(0));
513        assert!(!prunable.get_bit(1));
514        assert!(prunable.get_bit(2));
515    }
516
517    #[test]
518    fn test_push_byte() {
519        let mut prunable: Prunable<4> = Prunable::new();
520
521        // Add a byte
522        prunable.push_byte(0xFF);
523        assert_eq!(prunable.len(), 8);
524
525        // All bits should be set
526        for i in 0..8 {
527            assert!(prunable.get_bit(i as u64));
528        }
529
530        prunable.push_byte(0x00);
531        assert_eq!(prunable.len(), 16);
532
533        // Next 8 bits should be clear
534        for i in 8..16 {
535            assert!(!prunable.get_bit(i as u64));
536        }
537    }
538
539    #[test]
540    fn test_push_chunk() {
541        let mut prunable: Prunable<4> = Prunable::new();
542        let chunk = hex!("0xAABBCCDD");
543
544        prunable.push_chunk(&chunk);
545        assert_eq!(prunable.len(), 32); // 4 bytes * 8 bits
546
547        let retrieved_chunk = prunable.get_chunk_containing(0);
548        assert_eq!(retrieved_chunk, &chunk);
549    }
550
551    #[test]
552    fn test_set_bit() {
553        let mut prunable: Prunable<4> = Prunable::new();
554
555        // Add some bits
556        prunable.push(false);
557        prunable.push(false);
558        prunable.push(false);
559
560        assert!(!prunable.get_bit(1));
561
562        // Set a bit
563        prunable.set_bit(1, true);
564        assert!(prunable.get_bit(1));
565
566        // Set it back
567        prunable.set_bit(1, false);
568        assert!(!prunable.get_bit(1));
569    }
570
571    #[test]
572    fn test_pruning_basic() {
573        let mut prunable: Prunable<4> = Prunable::new();
574
575        // Add multiple chunks (4 bytes each)
576        let chunk1 = hex!("0x01020304");
577        let chunk2 = hex!("0x05060708");
578        let chunk3 = hex!("0x090A0B0C");
579
580        prunable.push_chunk(&chunk1);
581        prunable.push_chunk(&chunk2);
582        prunable.push_chunk(&chunk3);
583
584        assert_eq!(prunable.len(), 96); // 3 chunks * 32 bits
585        assert_eq!(prunable.pruned_chunks(), 0);
586
587        // Prune to second chunk (bit 32 is start of second chunk)
588        prunable.prune_to_bit(32);
589        assert_eq!(prunable.pruned_chunks(), 1);
590        assert_eq!(prunable.pruned_bits(), 32);
591        assert_eq!(prunable.len(), 96); // Total count unchanged
592
593        // Can still access non-pruned bits
594        assert_eq!(prunable.get_chunk_containing(32), &chunk2);
595        assert_eq!(prunable.get_chunk_containing(64), &chunk3);
596
597        // Prune to third chunk
598        prunable.prune_to_bit(64);
599        assert_eq!(prunable.pruned_chunks(), 2);
600        assert_eq!(prunable.pruned_bits(), 64);
601        assert_eq!(prunable.len(), 96);
602
603        // Can still access the third chunk
604        assert_eq!(prunable.get_chunk_containing(64), &chunk3);
605    }
606
607    #[test]
608    #[should_panic(expected = "bit pruned")]
609    fn test_get_pruned_bit_panics() {
610        let mut prunable: Prunable<4> = Prunable::new();
611
612        // Add two chunks
613        prunable.push_chunk(&[1, 2, 3, 4]);
614        prunable.push_chunk(&[5, 6, 7, 8]);
615
616        // Prune first chunk
617        prunable.prune_to_bit(32);
618
619        // Try to access pruned bit - should panic
620        prunable.get_bit(0);
621    }
622
623    #[test]
624    #[should_panic(expected = "bit pruned")]
625    fn test_get_pruned_chunk_panics() {
626        let mut prunable: Prunable<4> = Prunable::new();
627
628        // Add two chunks
629        prunable.push_chunk(&[1, 2, 3, 4]);
630        prunable.push_chunk(&[5, 6, 7, 8]);
631
632        // Prune first chunk
633        prunable.prune_to_bit(32);
634
635        // Try to access pruned chunk - should panic
636        prunable.get_chunk_containing(0);
637    }
638
639    #[test]
640    #[should_panic(expected = "bit pruned")]
641    fn test_set_pruned_bit_panics() {
642        let mut prunable: Prunable<4> = Prunable::new();
643
644        // Add two chunks
645        prunable.push_chunk(&[1, 2, 3, 4]);
646        prunable.push_chunk(&[5, 6, 7, 8]);
647
648        // Prune first chunk
649        prunable.prune_to_bit(32);
650
651        // Try to set pruned bit - should panic
652        prunable.set_bit(0, true);
653    }
654
655    #[test]
656    #[should_panic(expected = "bit 25 out of bounds (len: 24)")]
657    fn test_prune_to_bit_out_of_bounds() {
658        let mut prunable: Prunable<1> = Prunable::new();
659
660        // Add 3 bytes (24 bits total)
661        prunable.push_byte(1);
662        prunable.push_byte(2);
663        prunable.push_byte(3);
664
665        // Try to prune to a bit beyond the bitmap
666        prunable.prune_to_bit(25);
667    }
668
669    #[test]
670    fn test_pruning_with_partial_chunk() {
671        let mut prunable: Prunable<4> = Prunable::new();
672
673        // Add two full chunks and some partial bits
674        prunable.push_chunk(&[0xFF; 4]);
675        prunable.push_chunk(&[0xAA; 4]);
676        prunable.push(true);
677        prunable.push(false);
678        prunable.push(true);
679
680        assert_eq!(prunable.len(), 67); // 64 + 3 bits
681
682        // Prune to second chunk
683        prunable.prune_to_bit(32);
684        assert_eq!(prunable.pruned_chunks(), 1);
685        assert_eq!(prunable.len(), 67);
686
687        // Can still access the partial bits
688        assert!(prunable.get_bit(64));
689        assert!(!prunable.get_bit(65));
690        assert!(prunable.get_bit(66));
691    }
692
693    #[test]
694    fn test_prune_idempotent() {
695        let mut prunable: Prunable<4> = Prunable::new();
696
697        // Add chunks
698        prunable.push_chunk(&[1, 2, 3, 4]);
699        prunable.push_chunk(&[5, 6, 7, 8]);
700
701        // Prune to bit 32
702        prunable.prune_to_bit(32);
703        assert_eq!(prunable.pruned_chunks(), 1);
704
705        // Pruning to same or earlier point should be no-op
706        prunable.prune_to_bit(32);
707        assert_eq!(prunable.pruned_chunks(), 1);
708
709        prunable.prune_to_bit(16);
710        assert_eq!(prunable.pruned_chunks(), 1);
711    }
712
713    #[test]
714    fn test_push_after_pruning() {
715        let mut prunable: Prunable<4> = Prunable::new();
716
717        // Add initial chunks
718        prunable.push_chunk(&[1, 2, 3, 4]);
719        prunable.push_chunk(&[5, 6, 7, 8]);
720
721        // Prune first chunk
722        prunable.prune_to_bit(32);
723        assert_eq!(prunable.len(), 64);
724        assert_eq!(prunable.pruned_chunks(), 1);
725
726        // Add more data
727        prunable.push_chunk(&[9, 10, 11, 12]);
728        assert_eq!(prunable.len(), 96); // 32 pruned + 64 active
729
730        // New chunk should be accessible
731        assert_eq!(prunable.get_chunk_containing(64), &[9, 10, 11, 12]);
732    }
733
734    #[test]
735    fn test_chunk_calculations() {
736        // Test chunk_num calculation
737        assert_eq!(Prunable::<4>::to_chunk_index(0), 0);
738        assert_eq!(Prunable::<4>::to_chunk_index(31), 0);
739        assert_eq!(Prunable::<4>::to_chunk_index(32), 1);
740        assert_eq!(Prunable::<4>::to_chunk_index(63), 1);
741        assert_eq!(Prunable::<4>::to_chunk_index(64), 2);
742
743        // Test chunk_byte_offset
744        assert_eq!(Prunable::<4>::chunk_byte_offset(0), 0);
745        assert_eq!(Prunable::<4>::chunk_byte_offset(8), 1);
746        assert_eq!(Prunable::<4>::chunk_byte_offset(16), 2);
747        assert_eq!(Prunable::<4>::chunk_byte_offset(24), 3);
748        assert_eq!(Prunable::<4>::chunk_byte_offset(32), 0); // Wraps to next chunk
749
750        // Test chunk_byte_bitmask
751        assert_eq!(Prunable::<4>::chunk_byte_bitmask(0), 0b00000001);
752        assert_eq!(Prunable::<4>::chunk_byte_bitmask(1), 0b00000010);
753        assert_eq!(Prunable::<4>::chunk_byte_bitmask(7), 0b10000000);
754        assert_eq!(Prunable::<4>::chunk_byte_bitmask(8), 0b00000001); // Next byte
755    }
756
757    #[test]
758    fn test_last_chunk_with_pruning() {
759        let mut prunable: Prunable<4> = Prunable::new();
760
761        // Add chunks
762        prunable.push_chunk(&[1, 2, 3, 4]);
763        prunable.push_chunk(&[5, 6, 7, 8]);
764        prunable.push(true);
765        prunable.push(false);
766
767        let (_, next_bit) = prunable.last_chunk();
768        assert_eq!(next_bit, 2);
769
770        // Store the chunk data for comparison
771        let chunk_data = *prunable.last_chunk().0;
772
773        // Pruning shouldn't affect last_chunk
774        prunable.prune_to_bit(32);
775        let (chunk2, next_bit2) = prunable.last_chunk();
776        assert_eq!(next_bit2, 2);
777        assert_eq!(&chunk_data, chunk2);
778    }
779
780    #[test]
781    fn test_different_chunk_sizes() {
782        // Test with different chunk sizes
783        let mut p8: Prunable<8> = Prunable::new();
784        let mut p16: Prunable<16> = Prunable::new();
785        let mut p32: Prunable<32> = Prunable::new();
786
787        // Add same pattern to each
788        for i in 0..10 {
789            p8.push(i % 2 == 0);
790            p16.push(i % 2 == 0);
791            p32.push(i % 2 == 0);
792        }
793
794        // All should have same bit count
795        assert_eq!(p8.len(), 10);
796        assert_eq!(p16.len(), 10);
797        assert_eq!(p32.len(), 10);
798
799        // All should have same bit values
800        for i in 0..10 {
801            let expected = i % 2 == 0;
802            if expected {
803                assert!(p8.get_bit(i));
804                assert!(p16.get_bit(i));
805                assert!(p32.get_bit(i));
806            } else {
807                assert!(!p8.get_bit(i));
808                assert!(!p16.get_bit(i));
809                assert!(!p32.get_bit(i));
810            }
811        }
812    }
813
814    #[test]
815    fn test_get_bit_from_chunk() {
816        let chunk: [u8; 4] = [0b10101010, 0b11001100, 0b11110000, 0b00001111];
817
818        // Test first byte
819        assert!(!Prunable::<4>::get_bit_from_chunk(&chunk, 0));
820        assert!(Prunable::<4>::get_bit_from_chunk(&chunk, 1));
821        assert!(!Prunable::<4>::get_bit_from_chunk(&chunk, 2));
822        assert!(Prunable::<4>::get_bit_from_chunk(&chunk, 3));
823
824        // Test second byte
825        assert!(!Prunable::<4>::get_bit_from_chunk(&chunk, 8));
826        assert!(!Prunable::<4>::get_bit_from_chunk(&chunk, 9));
827        assert!(Prunable::<4>::get_bit_from_chunk(&chunk, 10));
828        assert!(Prunable::<4>::get_bit_from_chunk(&chunk, 11));
829    }
830
831    #[test]
832    fn test_get_chunk() {
833        let mut prunable: Prunable<4> = Prunable::new();
834        let chunk1 = hex!("0x11223344");
835        let chunk2 = hex!("0x55667788");
836        let chunk3 = hex!("0x99AABBCC");
837
838        prunable.push_chunk(&chunk1);
839        prunable.push_chunk(&chunk2);
840        prunable.push_chunk(&chunk3);
841
842        // Before pruning
843        assert_eq!(prunable.get_chunk(0), &chunk1);
844        assert_eq!(prunable.get_chunk(1), &chunk2);
845        assert_eq!(prunable.get_chunk(2), &chunk3);
846
847        // After pruning
848        prunable.prune_to_bit(32);
849        assert_eq!(prunable.get_chunk(1), &chunk2);
850        assert_eq!(prunable.get_chunk(2), &chunk3);
851    }
852
853    #[test]
854    fn test_pop() {
855        let mut prunable: Prunable<4> = Prunable::new();
856
857        prunable.push(true);
858        prunable.push(false);
859        prunable.push(true);
860        assert_eq!(prunable.len(), 3);
861
862        assert!(prunable.pop());
863        assert_eq!(prunable.len(), 2);
864
865        assert!(!prunable.pop());
866        assert_eq!(prunable.len(), 1);
867
868        assert!(prunable.pop());
869        assert_eq!(prunable.len(), 0);
870        assert!(prunable.is_empty());
871
872        for i in 0..100 {
873            prunable.push(i % 3 == 0);
874        }
875        assert_eq!(prunable.len(), 100);
876
877        for i in (0..100).rev() {
878            let expected = i % 3 == 0;
879            assert_eq!(prunable.pop(), expected);
880            assert_eq!(prunable.len(), i);
881        }
882
883        assert!(prunable.is_empty());
884    }
885
886    #[test]
887    fn test_truncate() {
888        let mut prunable: Prunable<4> = Prunable::new();
889        let expected: Vec<bool> = (0..96).map(|i| i % 3 == 0).collect();
890        for &bit in &expected {
891            prunable.push(bit);
892        }
893
894        prunable.truncate(65);
895        assert_eq!(prunable.len(), 65);
896        for i in 0..65 {
897            assert_eq!(prunable.get_bit(i), expected[i as usize]);
898        }
899
900        prunable.prune_to_bit(32);
901        assert_eq!(prunable.pruned_bits(), 32);
902
903        prunable.truncate(64);
904        assert_eq!(prunable.len(), 64);
905        for i in 32..64 {
906            assert_eq!(prunable.get_bit(i), expected[i as usize]);
907        }
908    }
909
910    #[test]
911    #[should_panic(expected = "cannot truncate into pruned region")]
912    fn test_truncate_into_pruned_region_panics() {
913        let mut prunable: Prunable<4> = Prunable::new();
914        prunable.push_chunk(&[0xFF; 4]);
915        prunable.push_chunk(&[0xFF; 4]);
916        prunable.prune_to_bit(32);
917        prunable.truncate(31);
918    }
919
920    #[test]
921    fn test_pop_chunk() {
922        let mut prunable: Prunable<4> = Prunable::new();
923        const CHUNK_SIZE: u64 = Prunable::<4>::CHUNK_SIZE_BITS;
924
925        // Test 1: Pop a single chunk and verify it returns the correct data
926        let chunk1 = hex!("0xAABBCCDD");
927        prunable.push_chunk(&chunk1);
928        assert_eq!(prunable.len(), CHUNK_SIZE);
929        let popped = prunable.pop_chunk();
930        assert_eq!(popped, chunk1);
931        assert_eq!(prunable.len(), 0);
932        assert!(prunable.is_empty());
933
934        // Test 2: Pop multiple chunks in reverse order
935        let chunk2 = hex!("0x11223344");
936        let chunk3 = hex!("0x55667788");
937        let chunk4 = hex!("0x99AABBCC");
938
939        prunable.push_chunk(&chunk2);
940        prunable.push_chunk(&chunk3);
941        prunable.push_chunk(&chunk4);
942        assert_eq!(prunable.len(), CHUNK_SIZE * 3);
943
944        assert_eq!(prunable.pop_chunk(), chunk4);
945        assert_eq!(prunable.len(), CHUNK_SIZE * 2);
946
947        assert_eq!(prunable.pop_chunk(), chunk3);
948        assert_eq!(prunable.len(), CHUNK_SIZE);
949
950        assert_eq!(prunable.pop_chunk(), chunk2);
951        assert_eq!(prunable.len(), 0);
952
953        // Test 3: Verify data integrity when popping chunks
954        prunable = Prunable::new();
955        let first_chunk = hex!("0xAABBCCDD");
956        let second_chunk = hex!("0x11223344");
957        prunable.push_chunk(&first_chunk);
958        prunable.push_chunk(&second_chunk);
959
960        // Pop the second chunk, verify it and that first chunk is intact
961        assert_eq!(prunable.pop_chunk(), second_chunk);
962        assert_eq!(prunable.len(), CHUNK_SIZE);
963
964        for i in 0..CHUNK_SIZE {
965            let byte_idx = (i / 8) as usize;
966            let bit_idx = i % 8;
967            let expected = (first_chunk[byte_idx] >> bit_idx) & 1 == 1;
968            assert_eq!(prunable.get_bit(i), expected);
969        }
970
971        assert_eq!(prunable.pop_chunk(), first_chunk);
972        assert_eq!(prunable.len(), 0);
973    }
974
975    #[test]
976    #[should_panic(expected = "cannot pop chunk when not chunk aligned")]
977    fn test_pop_chunk_not_aligned() {
978        let mut prunable: Prunable<4> = Prunable::new();
979
980        // Push a full chunk plus one bit
981        prunable.push_chunk(&[0xFF; 4]);
982        prunable.push(true);
983
984        // Should panic because not chunk-aligned
985        prunable.pop_chunk();
986    }
987
988    #[test]
989    #[should_panic(expected = "cannot pop chunk: bitmap has fewer than CHUNK_SIZE_BITS bits")]
990    fn test_pop_chunk_insufficient_bits() {
991        let mut prunable: Prunable<4> = Prunable::new();
992
993        // Push only a few bits (less than a full chunk)
994        prunable.push(true);
995        prunable.push(false);
996
997        // Should panic because we don't have a full chunk to pop
998        prunable.pop_chunk();
999    }
1000
1001    #[test]
1002    fn test_write_read_empty() {
1003        let original: Prunable<4> = Prunable::new();
1004        let encoded = original.encode();
1005
1006        let decoded = Prunable::<4>::read_cfg(&mut encoded.as_ref(), &u64::MAX).unwrap();
1007        assert_eq!(decoded.len(), original.len());
1008        assert_eq!(decoded.pruned_chunks(), original.pruned_chunks());
1009        assert!(decoded.is_empty());
1010    }
1011
1012    #[test]
1013    fn test_write_read_non_empty() {
1014        let mut original: Prunable<4> = Prunable::new();
1015        original.push_chunk(&hex!("0xAABBCCDD"));
1016        original.push_chunk(&hex!("0x11223344"));
1017        original.push(true);
1018        original.push(false);
1019        original.push(true);
1020
1021        let encoded = original.encode();
1022        let decoded = Prunable::<4>::read_cfg(&mut encoded.as_ref(), &u64::MAX).unwrap();
1023
1024        assert_eq!(decoded.len(), original.len());
1025        assert_eq!(decoded.pruned_chunks(), original.pruned_chunks());
1026        assert_eq!(decoded.len(), 67);
1027
1028        // Verify all bits match
1029        for i in 0..original.len() {
1030            assert_eq!(decoded.get_bit(i), original.get_bit(i));
1031        }
1032    }
1033
1034    #[test]
1035    fn test_write_read_with_pruning() {
1036        let mut original: Prunable<4> = Prunable::new();
1037        original.push_chunk(&hex!("0x01020304"));
1038        original.push_chunk(&hex!("0x05060708"));
1039        original.push_chunk(&hex!("0x090A0B0C"));
1040
1041        // Prune first chunk
1042        original.prune_to_bit(32);
1043        assert_eq!(original.pruned_chunks(), 1);
1044        assert_eq!(original.len(), 96);
1045
1046        let encoded = original.encode();
1047        let decoded = Prunable::<4>::read_cfg(&mut encoded.as_ref(), &u64::MAX).unwrap();
1048
1049        assert_eq!(decoded.len(), original.len());
1050        assert_eq!(decoded.pruned_chunks(), original.pruned_chunks());
1051        assert_eq!(decoded.pruned_chunks(), 1);
1052        assert_eq!(decoded.len(), 96);
1053
1054        // Verify remaining chunks match
1055        assert_eq!(decoded.get_chunk_containing(32), &hex!("0x05060708"));
1056        assert_eq!(decoded.get_chunk_containing(64), &hex!("0x090A0B0C"));
1057    }
1058
1059    #[test]
1060    fn test_write_read_with_pruning_2() {
1061        let mut original: Prunable<4> = Prunable::new();
1062
1063        // Add several chunks
1064        for i in 0..5 {
1065            let chunk = [
1066                (i * 4) as u8,
1067                (i * 4 + 1) as u8,
1068                (i * 4 + 2) as u8,
1069                (i * 4 + 3) as u8,
1070            ];
1071            original.push_chunk(&chunk);
1072        }
1073
1074        // Keep only last two chunks
1075        original.prune_to_bit(96); // Prune first 3 chunks
1076        assert_eq!(original.pruned_chunks(), 3);
1077        assert_eq!(original.len(), 160);
1078
1079        let encoded = original.encode();
1080        let decoded = Prunable::<4>::read_cfg(&mut encoded.as_ref(), &u64::MAX).unwrap();
1081
1082        assert_eq!(decoded.len(), original.len());
1083        assert_eq!(decoded.pruned_chunks(), 3);
1084
1085        // Verify remaining accessible bits match
1086        for i in 96..original.len() {
1087            assert_eq!(decoded.get_bit(i), original.get_bit(i));
1088        }
1089    }
1090
1091    #[test]
1092    fn test_encode_size_matches() {
1093        let mut prunable: Prunable<4> = Prunable::new();
1094        prunable.push_chunk(&[1, 2, 3, 4]);
1095        prunable.push_chunk(&[5, 6, 7, 8]);
1096        prunable.push(true);
1097
1098        let size = prunable.encode_size();
1099        let encoded = prunable.encode();
1100
1101        assert_eq!(size, encoded.len());
1102    }
1103
1104    #[test]
1105    fn test_encode_size_with_pruning() {
1106        let mut prunable: Prunable<4> = Prunable::new();
1107        prunable.push_chunk(&[1, 2, 3, 4]);
1108        prunable.push_chunk(&[5, 6, 7, 8]);
1109        prunable.push_chunk(&[9, 10, 11, 12]);
1110
1111        prunable.prune_to_bit(32);
1112
1113        let size = prunable.encode_size();
1114        let encoded = prunable.encode();
1115
1116        assert_eq!(size, encoded.len());
1117    }
1118
1119    #[test]
1120    fn test_read_max_len_validation() {
1121        let mut original: Prunable<4> = Prunable::new();
1122        for _ in 0..10 {
1123            original.push(true);
1124        }
1125
1126        let encoded = original.encode();
1127
1128        // Should succeed with sufficient max_len
1129        assert!(Prunable::<4>::read_cfg(&mut encoded.as_ref(), &100).is_ok());
1130
1131        // Should fail with insufficient max_len
1132        let result = Prunable::<4>::read_cfg(&mut encoded.as_ref(), &5);
1133        assert!(result.is_err());
1134    }
1135
1136    #[test]
1137    fn test_codec_roundtrip_different_chunk_sizes() {
1138        // Test with different chunk sizes
1139        let mut p8: Prunable<8> = Prunable::new();
1140        let mut p16: Prunable<16> = Prunable::new();
1141        let mut p32: Prunable<32> = Prunable::new();
1142
1143        for i in 0..100 {
1144            let bit = i % 3 == 0;
1145            p8.push(bit);
1146            p16.push(bit);
1147            p32.push(bit);
1148        }
1149
1150        // Roundtrip each
1151        let encoded8 = p8.encode();
1152        let decoded8 = Prunable::<8>::read_cfg(&mut encoded8.as_ref(), &u64::MAX).unwrap();
1153        assert_eq!(decoded8.len(), p8.len());
1154
1155        let encoded16 = p16.encode();
1156        let decoded16 = Prunable::<16>::read_cfg(&mut encoded16.as_ref(), &u64::MAX).unwrap();
1157        assert_eq!(decoded16.len(), p16.len());
1158
1159        let encoded32 = p32.encode();
1160        let decoded32 = Prunable::<32>::read_cfg(&mut encoded32.as_ref(), &u64::MAX).unwrap();
1161        assert_eq!(decoded32.len(), p32.len());
1162    }
1163
1164    #[test]
1165    fn test_read_pruned_chunks_overflow() {
1166        let mut buf = BytesMut::new();
1167
1168        // Write a pruned_chunks value that would overflow when multiplied by CHUNK_SIZE_BITS
1169        let overflowing_pruned_chunks = (u64::MAX / Prunable::<4>::CHUNK_SIZE_BITS) + 1;
1170        overflowing_pruned_chunks.write(&mut buf);
1171
1172        // Write a valid bitmap (empty)
1173        0u64.write(&mut buf); // len = 0
1174
1175        // Try to read - should fail with overflow error
1176        let result = Prunable::<4>::read_cfg(&mut buf.as_ref(), &u64::MAX);
1177        match result {
1178            Err(CodecError::Invalid(type_name, msg)) => {
1179                assert_eq!(type_name, "Prunable");
1180                assert_eq!(
1181                    msg,
1182                    "pruned_chunks would overflow when computing pruned_bits"
1183                );
1184            }
1185            Ok(_) => panic!("Expected error but got Ok"),
1186            Err(e) => panic!("Expected Invalid error for pruned_bits overflow, got: {e:?}"),
1187        }
1188    }
1189
1190    #[test]
1191    fn test_read_total_length_overflow() {
1192        let mut buf = BytesMut::new();
1193
1194        // Make pruned_bits as large as possible without overflowing
1195        let max_safe_pruned_chunks = u64::MAX / Prunable::<4>::CHUNK_SIZE_BITS;
1196        let pruned_bits = max_safe_pruned_chunks * Prunable::<4>::CHUNK_SIZE_BITS;
1197
1198        // Make bitmap_len large enough that adding it overflows
1199        let remaining_space = u64::MAX - pruned_bits;
1200        let bitmap_len = remaining_space + 1; // Go over by 1 to trigger overflow
1201
1202        // Write the serialized data
1203        max_safe_pruned_chunks.write(&mut buf);
1204        bitmap_len.write(&mut buf);
1205
1206        // Write bitmap chunk data
1207        let num_chunks = bitmap_len.div_ceil(Prunable::<4>::CHUNK_SIZE_BITS);
1208        for _ in 0..(num_chunks * 4) {
1209            0u8.write(&mut buf);
1210        }
1211
1212        // Try to read - should fail because pruned_bits + bitmap_len overflows u64
1213        let result = Prunable::<4>::read_cfg(&mut buf.as_ref(), &u64::MAX);
1214        match result {
1215            Err(CodecError::Invalid(type_name, msg)) => {
1216                assert_eq!(type_name, "Prunable");
1217                assert_eq!(
1218                    msg,
1219                    "total bitmap length (pruned + unpruned) would overflow u64"
1220                );
1221            }
1222            Ok(_) => panic!("Expected error but got Ok"),
1223            Err(e) => panic!("Expected Invalid error for total length overflow, got: {e:?}"),
1224        }
1225    }
1226
1227    #[test]
1228    fn test_is_chunk_aligned() {
1229        // Empty bitmap is chunk aligned
1230        let prunable: Prunable<4> = Prunable::new();
1231        assert!(prunable.is_chunk_aligned());
1232
1233        // Add bits one at a time and check alignment
1234        let mut prunable: Prunable<4> = Prunable::new();
1235        for i in 1..=32 {
1236            prunable.push(i % 2 == 0);
1237            if i == 32 {
1238                assert!(prunable.is_chunk_aligned()); // Exactly one chunk
1239            } else {
1240                assert!(!prunable.is_chunk_aligned()); // Partial chunk
1241            }
1242        }
1243
1244        // Add another full chunk
1245        for i in 33..=64 {
1246            prunable.push(i % 2 == 0);
1247            if i == 64 {
1248                assert!(prunable.is_chunk_aligned()); // Exactly two chunks
1249            } else {
1250                assert!(!prunable.is_chunk_aligned()); // Partial chunk
1251            }
1252        }
1253
1254        // Test with push_chunk
1255        let mut prunable: Prunable<4> = Prunable::new();
1256        assert!(prunable.is_chunk_aligned());
1257        prunable.push_chunk(&[1, 2, 3, 4]);
1258        assert!(prunable.is_chunk_aligned()); // 32 bits = 1 chunk
1259        prunable.push_chunk(&[5, 6, 7, 8]);
1260        assert!(prunable.is_chunk_aligned()); // 64 bits = 2 chunks
1261        prunable.push(true);
1262        assert!(!prunable.is_chunk_aligned()); // 65 bits = partial chunk
1263
1264        // Test alignment with pruning
1265        let mut prunable: Prunable<4> = Prunable::new();
1266        prunable.push_chunk(&[1, 2, 3, 4]);
1267        prunable.push_chunk(&[5, 6, 7, 8]);
1268        prunable.push_chunk(&[9, 10, 11, 12]);
1269        assert!(prunable.is_chunk_aligned()); // 96 bits = 3 chunks
1270
1271        // Prune first chunk - still aligned (64 bits remaining)
1272        prunable.prune_to_bit(32);
1273        assert!(prunable.is_chunk_aligned());
1274        assert_eq!(prunable.len(), 96);
1275
1276        // Add a partial chunk
1277        prunable.push(true);
1278        prunable.push(false);
1279        assert!(!prunable.is_chunk_aligned()); // 98 bits total
1280
1281        // Prune to align again
1282        prunable.prune_to_bit(64);
1283        assert!(!prunable.is_chunk_aligned()); // 98 bits total (34 bits remaining)
1284
1285        // Test with new_with_pruned_chunks
1286        let prunable: Prunable<4> = Prunable::new_with_pruned_chunks(2).unwrap();
1287        assert!(prunable.is_chunk_aligned()); // 64 bits pruned, 0 bits in bitmap
1288
1289        let mut prunable: Prunable<4> = Prunable::new_with_pruned_chunks(1).unwrap();
1290        assert!(prunable.is_chunk_aligned()); // 32 bits pruned, 0 bits in bitmap
1291        prunable.push(true);
1292        assert!(!prunable.is_chunk_aligned()); // 33 bits total
1293
1294        // Test with push_byte
1295        let mut prunable: Prunable<4> = Prunable::new();
1296        for _ in 0..4 {
1297            prunable.push_byte(0xFF);
1298        }
1299        assert!(prunable.is_chunk_aligned()); // 32 bits = 1 chunk
1300
1301        // Test after pop
1302        prunable.pop();
1303        assert!(!prunable.is_chunk_aligned()); // 31 bits
1304
1305        // Pop back to alignment
1306        for _ in 0..31 {
1307            prunable.pop();
1308        }
1309        assert!(prunable.is_chunk_aligned()); // 0 bits
1310    }
1311
1312    #[test]
1313    fn test_ones_iter_with_pruning() {
1314        // 3 chunks of 32 bits each = 96 bits total.
1315        let mut p = Prunable::<4>::new();
1316        for _ in 0..96 {
1317            p.push(false);
1318        }
1319        // Set bits in chunks 1 and 2 (absolute indices 40, 55, 64, 90).
1320        p.set_bit(40, true);
1321        p.set_bit(55, true);
1322        p.set_bit(64, true);
1323        p.set_bit(90, true);
1324
1325        // Prune first chunk (32 bits).
1326        p.prune_to_bit(32);
1327        assert_eq!(p.pruned_chunks(), 1);
1328        assert_eq!(p.pruned_bits(), 32);
1329
1330        let ones: Vec<u64> = Readable::ones_iter_from(&p, p.pruned_bits()).collect();
1331        assert_eq!(ones, vec![40, 55, 64, 90]);
1332    }
1333
1334    #[test]
1335    fn test_ones_iter_from_midway_with_pruning() {
1336        let mut p = Prunable::<4>::new();
1337        for _ in 0..96 {
1338            p.push(false);
1339        }
1340        p.set_bit(40, true);
1341        p.set_bit(55, true);
1342        p.set_bit(64, true);
1343        p.set_bit(90, true);
1344
1345        p.prune_to_bit(32);
1346
1347        // Start past the first set bit in the unpruned region.
1348        let ones: Vec<u64> = Readable::ones_iter_from(&p, 50).collect();
1349        assert_eq!(ones, vec![55, 64, 90]);
1350
1351        // Start past all set bits.
1352        let ones: Vec<u64> = Readable::ones_iter_from(&p, 91).collect();
1353        assert!(ones.is_empty());
1354    }
1355
1356    #[test]
1357    fn test_ones_iter_all_ones_with_pruning() {
1358        let mut p = Prunable::<4>::new();
1359        for _ in 0..96 {
1360            p.push(true);
1361        }
1362
1363        // Prune 2 chunks (64 bits).
1364        p.prune_to_bit(64);
1365        assert_eq!(p.pruned_chunks(), 2);
1366
1367        let ones: Vec<u64> = Readable::ones_iter_from(&p, p.pruned_bits()).collect();
1368        let expected: Vec<u64> = (64..96).collect();
1369        assert_eq!(ones, expected);
1370    }
1371
1372    #[test]
1373    fn test_ones_iter_empty_after_pruning() {
1374        let mut p = Prunable::<4>::new();
1375        for _ in 0..64 {
1376            p.push(false);
1377        }
1378
1379        // Prune first chunk, leaving 32 zero bits.
1380        p.prune_to_bit(32);
1381        assert_eq!(p.pruned_chunks(), 1);
1382
1383        let ones: Vec<u64> = Readable::ones_iter_from(&p, p.pruned_bits()).collect();
1384        assert!(ones.is_empty());
1385    }
1386
1387    #[test]
1388    fn test_ones_iter_from_pruned_region_fast_forwards() {
1389        let mut p = Prunable::<4>::new();
1390        for _ in 0..64 {
1391            p.push(true);
1392        }
1393        p.prune_to_bit(32);
1394
1395        // Starting in the pruned region fast-forwards past it.
1396        let ones: Vec<u64> = Readable::ones_iter_from(&p, 0).collect();
1397        let expected: Vec<u64> = (32..64).collect();
1398        assert_eq!(ones, expected);
1399    }
1400
1401    #[test]
1402    fn test_extend_to() {
1403        let mut p = Prunable::<4>::new();
1404        for i in 0..30u64 {
1405            p.push(i % 2 == 0);
1406        }
1407
1408        // No-op when target is smaller.
1409        p.extend_to(5);
1410        assert_eq!(p.len(), 30);
1411
1412        // Extends across a chunk boundary, new bits are zero.
1413        p.extend_to(65);
1414        assert_eq!(p.len(), 65);
1415        for i in 0..30 {
1416            assert_eq!(p.get_bit(i), i % 2 == 0, "original bit {i}");
1417        }
1418        for i in 30..65 {
1419            assert!(!p.get_bit(i), "extended bit {i} should be false");
1420        }
1421
1422        // Works correctly with a pruned prefix.
1423        p.prune_to_bit(32);
1424        p.extend_to(100);
1425        assert_eq!(p.len(), 100);
1426        assert_eq!(p.pruned_bits(), 32);
1427        for i in 65..100 {
1428            assert!(!p.get_bit(i), "extended bit {i} should be false");
1429        }
1430    }
1431
1432    // set_chunk_by_index must reject non-zero bits beyond len() in the last partial chunk.
1433
1434    #[test]
1435    fn test_set_chunk_by_index_accepts_valid_partial_chunk() {
1436        let mut p = Prunable::<4>::new();
1437        p.extend_to(35); // 1 full chunk + 3 bits in chunk 1
1438                         // Only the low 3 bits of byte 0 are valid; rest must be zero.
1439        p.set_chunk_by_index(1, &[0b0000_0111, 0, 0, 0]);
1440        assert_eq!(p.get_chunk(1), &[0b0000_0111, 0, 0, 0]);
1441    }
1442
1443    #[test]
1444    fn test_set_chunk_by_index_accepts_full_chunk() {
1445        let mut p = Prunable::<4>::new();
1446        p.extend_to(64); // 2 full chunks
1447                         // Full chunk: any byte pattern is valid.
1448        p.set_chunk_by_index(1, &[0xFF; 4]);
1449        assert_eq!(p.get_chunk(1), &[0xFF; 4]);
1450    }
1451
1452    #[test]
1453    #[should_panic(expected = "non-zero bits beyond bitmap length")]
1454    fn test_set_chunk_by_index_rejects_high_bits_in_last_byte() {
1455        let mut p = Prunable::<4>::new();
1456        p.extend_to(35); // 3 valid bits in chunk 1
1457                         // Bit 3 (0b0000_1000) is beyond len.
1458        p.set_chunk_by_index(1, &[0b0000_1000, 0, 0, 0]);
1459    }
1460
1461    #[test]
1462    #[should_panic(expected = "non-zero bits beyond bitmap length")]
1463    fn test_set_chunk_by_index_rejects_nonzero_trailing_bytes() {
1464        let mut p = Prunable::<4>::new();
1465        p.extend_to(35); // 3 valid bits in chunk 1
1466                         // Byte 1 is entirely beyond len.
1467        p.set_chunk_by_index(1, &[0, 0xFF, 0, 0]);
1468    }
1469
1470    #[test]
1471    #[should_panic(expected = "non-zero bits beyond bitmap length")]
1472    fn test_set_chunk_by_index_rejects_nonzero_final_byte() {
1473        let mut p = Prunable::<4>::new();
1474        p.extend_to(35); // 3 valid bits in chunk 1
1475        p.set_chunk_by_index(1, &[0, 0, 0, 1]);
1476    }
1477
1478    #[test]
1479    fn test_set_chunk_by_index_roundtrips_codec() {
1480        let mut p = Prunable::<4>::new();
1481        p.extend_to(35);
1482        p.set_chunk_by_index(1, &[0b0000_0101, 0, 0, 0]);
1483
1484        let encoded = p.encode();
1485        let decoded = Prunable::<4>::read_cfg(&mut encoded.as_ref(), &u64::MAX)
1486            .expect("valid chunk should round-trip");
1487        assert_eq!(decoded.len(), 35);
1488        assert_eq!(decoded.get_chunk(1), &[0b0000_0101, 0, 0, 0]);
1489    }
1490
1491    #[cfg(feature = "arbitrary")]
1492    mod conformance {
1493        use super::*;
1494        use commonware_codec::conformance::CodecConformance;
1495
1496        commonware_conformance::conformance_tests! {
1497            CodecConformance<Prunable<16>>,
1498        }
1499    }
1500}