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::BufMut;
5use commonware_codec::{Buf, EncodeSize, Error as CodecError, RangeCfg, 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    /// Append every bit of `other`, in order. See [`BitMap::extend_from_bitmap`].
238    pub fn extend_from_bitmap<const M: usize>(&mut self, other: &BitMap<M>) {
239        self.bitmap.extend_from_bitmap(other);
240    }
241
242    /// Remove and return the last complete chunk from the bitmap.
243    ///
244    /// # Warning
245    ///
246    /// Panics if the bitmap has fewer than `CHUNK_SIZE_BITS` bits or if not chunk-aligned.
247    pub fn pop_chunk(&mut self) -> [u8; N] {
248        self.bitmap.pop_chunk()
249    }
250
251    /* Pruning */
252
253    /// Prune all complete chunks before the chunk containing the given bit.
254    ///
255    /// The chunk containing `bit` and all subsequent chunks are retained. All chunks
256    /// before it are pruned.
257    ///
258    /// If `bit` equals the bitmap length, this prunes all complete chunks while retaining
259    /// the empty trailing chunk, preparing the bitmap for appending new data.
260    ///
261    /// # Warning
262    ///
263    /// Panics if `bit` is greater than the bitmap length.
264    pub fn prune_to_bit(&mut self, bit: u64) {
265        assert!(
266            bit <= self.len(),
267            "bit {} out of bounds (len: {})",
268            bit,
269            self.len()
270        );
271
272        let chunk = Self::to_chunk_index(bit);
273        if chunk < self.pruned_chunks {
274            return;
275        }
276
277        let chunks_to_prune = chunk - self.pruned_chunks;
278        self.bitmap.prune_chunks(chunks_to_prune);
279        self.pruned_chunks = chunk;
280    }
281
282    /* Indexing Helpers */
283
284    /// Convert a bit into a bitmask for the byte containing that bit.
285    #[inline]
286    pub const fn chunk_byte_bitmask(bit: u64) -> u8 {
287        BitMap::<N>::chunk_byte_bitmask(bit)
288    }
289
290    /// Convert a bit into the index of the byte within a chunk containing the bit.
291    #[inline]
292    pub const fn chunk_byte_offset(bit: u64) -> usize {
293        BitMap::<N>::chunk_byte_offset(bit)
294    }
295
296    /// Convert a bit into the index of the chunk it belongs to.
297    ///
298    /// # Panics
299    ///
300    /// Panics if `bit / CHUNK_SIZE_BITS > usize::MAX`.
301    #[inline]
302    pub fn to_chunk_index(bit: u64) -> usize {
303        BitMap::<N>::to_chunk_index(bit)
304    }
305
306    /// Get a reference to a chunk by its absolute index (includes pruned chunks).
307    ///
308    /// # Panics
309    ///
310    /// Panics if the chunk has been pruned or is out of bounds.
311    #[inline]
312    pub fn get_chunk(&self, chunk: usize) -> &[u8; N] {
313        assert!(
314            chunk >= self.pruned_chunks,
315            "chunk {chunk} is pruned (pruned_chunks: {})",
316            self.pruned_chunks
317        );
318        self.bitmap.get_chunk(chunk - self.pruned_chunks)
319    }
320
321    /// Overwrite a chunk's data by its absolute chunk index.
322    ///
323    /// # Panics
324    ///
325    /// Panics if the chunk is pruned or out of bounds, or if this is the last partial
326    /// chunk and `chunk_data` has non-zero bits beyond `len()`.
327    pub fn set_chunk_by_index(&mut self, chunk_index: usize, chunk_data: &[u8; N]) {
328        assert!(
329            chunk_index >= self.pruned_chunks,
330            "cannot set pruned chunk {chunk_index} (pruned_chunks: {})",
331            self.pruned_chunks
332        );
333        let bitmap_chunk_idx = chunk_index - self.pruned_chunks;
334
335        // Reject non-zero trailing bits in the last partial chunk to maintain the
336        // canonicalization invariant required by the codec.
337        if bitmap_chunk_idx + 1 == self.bitmap.chunks_len() {
338            let trailing = self.bitmap.len() % Self::CHUNK_SIZE_BITS;
339            if trailing != 0 {
340                let last_byte = ((trailing - 1) / 8) as usize;
341                let bits_in_last_byte = (trailing % 8) as u32;
342                let byte_mask = if bits_in_last_byte != 0 {
343                    !((1u8 << bits_in_last_byte) - 1)
344                } else {
345                    0
346                };
347                assert!(
348                    chunk_data[last_byte] & byte_mask == 0
349                        && chunk_data[last_byte + 1..] == [0u8; N][last_byte + 1..],
350                    "non-zero bits beyond bitmap length"
351                );
352            }
353        }
354
355        self.bitmap.set_chunk_by_index(bitmap_chunk_idx, chunk_data);
356    }
357
358    /// Unprune chunks by prepending them back to the front of the bitmap.
359    ///
360    /// The caller must provide chunks in **reverse** order: to restore chunks with
361    /// indices [0, 1, 2], pass them as [2, 1, 0]. This is necessary because each chunk
362    /// is prepended to the front, so the last chunk provided becomes the first chunk
363    /// in the bitmap.
364    ///
365    /// # Panics
366    ///
367    /// Panics if chunks.len() > self.pruned_chunks.
368    pub(super) fn unprune_chunks(&mut self, chunks: &[[u8; N]]) {
369        assert!(
370            chunks.len() <= self.pruned_chunks,
371            "cannot unprune {} chunks (only {} pruned)",
372            chunks.len(),
373            self.pruned_chunks
374        );
375
376        for chunk in chunks.iter() {
377            self.bitmap.prepend_chunk(chunk);
378        }
379
380        self.pruned_chunks -= chunks.len();
381    }
382}
383
384impl<const N: usize> super::Readable<N> for Prunable<N> {
385    fn complete_chunks(&self) -> usize {
386        Self::complete_chunks(self)
387    }
388    fn get_chunk(&self, chunk: usize) -> [u8; N] {
389        *Self::get_chunk(self, chunk)
390    }
391    fn last_chunk(&self) -> ([u8; N], u64) {
392        let (c, n) = Self::last_chunk(self);
393        (*c, n)
394    }
395    fn pruned_chunks(&self) -> usize {
396        self.pruned_chunks
397    }
398    fn len(&self) -> u64 {
399        Self::len(self)
400    }
401}
402
403impl<const N: usize> Default for Prunable<N> {
404    fn default() -> Self {
405        Self::new()
406    }
407}
408
409impl<const N: usize> Write for Prunable<N> {
410    fn write(&self, buf: &mut impl BufMut) {
411        (self.pruned_chunks as u64).write(buf);
412        self.bitmap.write(buf);
413    }
414}
415
416impl<const N: usize> Read for Prunable<N> {
417    /// Accepted range for the number of retained (unpruned) bits.
418    /// [Self::len] also counts pruned bits.
419    type Cfg = RangeCfg<u64>;
420
421    fn read_cfg(buf: &mut impl Buf, range: &Self::Cfg) -> Result<Self, CodecError> {
422        let pruned_chunks_u64 = u64::read(buf)?;
423
424        // Validate that pruned_chunks * CHUNK_SIZE_BITS doesn't overflow u64
425        let pruned_bits =
426            pruned_chunks_u64
427                .checked_mul(Self::CHUNK_SIZE_BITS)
428                .ok_or(CodecError::Invalid(
429                    "Prunable",
430                    "pruned_chunks would overflow when computing pruned_bits",
431                ))?;
432
433        let pruned_chunks = usize::try_from(pruned_chunks_u64)
434            .map_err(|_| CodecError::Invalid("Prunable", "pruned_chunks doesn't fit in usize"))?;
435
436        let bitmap = BitMap::<N>::read_cfg(buf, range)?;
437
438        // Validate that total length (pruned_bits + bitmap.len()) doesn't overflow u64
439        pruned_bits
440            .checked_add(bitmap.len())
441            .ok_or(CodecError::Invalid(
442                "Prunable",
443                "total bitmap length (pruned + unpruned) would overflow u64",
444            ))?;
445
446        Ok(Self {
447            bitmap,
448            pruned_chunks,
449        })
450    }
451}
452
453impl<const N: usize> EncodeSize for Prunable<N> {
454    fn encode_size(&self) -> usize {
455        (self.pruned_chunks as u64).encode_size() + self.bitmap.encode_size()
456    }
457}
458
459#[cfg(feature = "arbitrary")]
460impl<const N: usize> arbitrary::Arbitrary<'_> for Prunable<N> {
461    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
462        let mut bitmap = Self {
463            bitmap: BitMap::<N>::arbitrary(u)?,
464            pruned_chunks: 0,
465        };
466        let prune_to = u.int_in_range(0..=bitmap.len())?;
467        bitmap.prune_to_bit(prune_to);
468        Ok(bitmap)
469    }
470}
471
472#[cfg(test)]
473mod tests {
474    use super::{super::Readable, *};
475    use bytes::BytesMut;
476    use commonware_codec::Encode;
477    use commonware_formatting::hex;
478
479    #[test]
480    fn test_new() {
481        let prunable: Prunable<32> = Prunable::new();
482        assert_eq!(prunable.len(), 0);
483        assert_eq!(prunable.pruned_bits(), 0);
484        assert_eq!(prunable.pruned_chunks(), 0);
485        assert!(prunable.is_empty());
486        assert_eq!(prunable.chunks_len(), 0); // No chunks when empty
487    }
488
489    #[test]
490    fn test_new_with_pruned_chunks() {
491        let prunable: Prunable<2> = Prunable::new_with_pruned_chunks(1).unwrap();
492        assert_eq!(prunable.len(), 16);
493        assert_eq!(prunable.pruned_bits(), 16);
494        assert_eq!(prunable.pruned_chunks(), 1);
495        assert_eq!(prunable.chunks_len(), 1);
496    }
497
498    #[test]
499    fn test_new_with_pruned_chunks_overflow() {
500        // Try to create a Prunable with pruned_chunks that would overflow
501        let overflowing_pruned_chunks = (u64::MAX / Prunable::<4>::CHUNK_SIZE_BITS) as usize + 1;
502        let result = Prunable::<4>::new_with_pruned_chunks(overflowing_pruned_chunks);
503
504        assert!(matches!(result, Err(Error::PrunedChunksOverflow)));
505    }
506
507    #[test]
508    fn test_push_and_get_bits() {
509        let mut prunable: Prunable<4> = Prunable::new();
510
511        // Add some bits
512        prunable.push(true);
513        prunable.push(false);
514        prunable.push(true);
515
516        assert_eq!(prunable.len(), 3);
517        assert!(!prunable.is_empty());
518        assert!(prunable.get_bit(0));
519        assert!(!prunable.get_bit(1));
520        assert!(prunable.get_bit(2));
521    }
522
523    #[test]
524    fn test_push_byte() {
525        let mut prunable: Prunable<4> = Prunable::new();
526
527        // Add a byte
528        prunable.push_byte(0xFF);
529        assert_eq!(prunable.len(), 8);
530
531        // All bits should be set
532        for i in 0..8 {
533            assert!(prunable.get_bit(i as u64));
534        }
535
536        prunable.push_byte(0x00);
537        assert_eq!(prunable.len(), 16);
538
539        // Next 8 bits should be clear
540        for i in 8..16 {
541            assert!(!prunable.get_bit(i as u64));
542        }
543    }
544
545    #[test]
546    fn test_push_chunk() {
547        let mut prunable: Prunable<4> = Prunable::new();
548        let chunk = hex!("0xAABBCCDD");
549
550        prunable.push_chunk(&chunk);
551        assert_eq!(prunable.len(), 32); // 4 bytes * 8 bits
552
553        let retrieved_chunk = prunable.get_chunk_containing(0);
554        assert_eq!(retrieved_chunk, &chunk);
555    }
556
557    #[test]
558    fn test_extend_from_bitmap_after_pruning_matches_push() {
559        // Appending after pruned chunks and a zero-filled gap matches pushing bit by bit.
560        let mut src: BitMap = BitMap::new();
561        for i in 0..1000u64 {
562            src.push(i % 3 == 0 || i % 7 == 0);
563        }
564        for floor in [128, 129, 191, 255, 256, 300] {
565            let mut actual = Prunable::<16>::new();
566            actual.extend_to(128);
567            actual.prune_to_bit(128);
568            actual.extend_to(floor);
569            let mut expected = actual.clone();
570            for bit in src.iter() {
571                expected.push(bit);
572            }
573            actual.extend_from_bitmap(&src);
574            assert_eq!(actual.len(), expected.len());
575            assert_eq!(actual.pruned_bits(), expected.pruned_bits());
576            for bit in actual.pruned_bits()..actual.len() {
577                assert_eq!(
578                    actual.get_bit(bit),
579                    expected.get_bit(bit),
580                    "floor={floor} bit={bit}"
581                );
582            }
583        }
584    }
585
586    #[test]
587    fn test_set_bit() {
588        let mut prunable: Prunable<4> = Prunable::new();
589
590        // Add some bits
591        prunable.push(false);
592        prunable.push(false);
593        prunable.push(false);
594
595        assert!(!prunable.get_bit(1));
596
597        // Set a bit
598        prunable.set_bit(1, true);
599        assert!(prunable.get_bit(1));
600
601        // Set it back
602        prunable.set_bit(1, false);
603        assert!(!prunable.get_bit(1));
604    }
605
606    #[test]
607    fn test_pruning_basic() {
608        let mut prunable: Prunable<4> = Prunable::new();
609
610        // Add multiple chunks (4 bytes each)
611        let chunk1 = hex!("0x01020304");
612        let chunk2 = hex!("0x05060708");
613        let chunk3 = hex!("0x090A0B0C");
614
615        prunable.push_chunk(&chunk1);
616        prunable.push_chunk(&chunk2);
617        prunable.push_chunk(&chunk3);
618
619        assert_eq!(prunable.len(), 96); // 3 chunks * 32 bits
620        assert_eq!(prunable.pruned_chunks(), 0);
621
622        // Prune to second chunk (bit 32 is start of second chunk)
623        prunable.prune_to_bit(32);
624        assert_eq!(prunable.pruned_chunks(), 1);
625        assert_eq!(prunable.pruned_bits(), 32);
626        assert_eq!(prunable.len(), 96); // Total count unchanged
627
628        // Can still access non-pruned bits
629        assert_eq!(prunable.get_chunk_containing(32), &chunk2);
630        assert_eq!(prunable.get_chunk_containing(64), &chunk3);
631
632        // Prune to third chunk
633        prunable.prune_to_bit(64);
634        assert_eq!(prunable.pruned_chunks(), 2);
635        assert_eq!(prunable.pruned_bits(), 64);
636        assert_eq!(prunable.len(), 96);
637
638        // Can still access the third chunk
639        assert_eq!(prunable.get_chunk_containing(64), &chunk3);
640    }
641
642    #[test]
643    #[should_panic(expected = "bit pruned")]
644    fn test_get_pruned_bit_panics() {
645        let mut prunable: Prunable<4> = Prunable::new();
646
647        // Add two chunks
648        prunable.push_chunk(&[1, 2, 3, 4]);
649        prunable.push_chunk(&[5, 6, 7, 8]);
650
651        // Prune first chunk
652        prunable.prune_to_bit(32);
653
654        // Try to access pruned bit - should panic
655        prunable.get_bit(0);
656    }
657
658    #[test]
659    #[should_panic(expected = "bit pruned")]
660    fn test_get_pruned_chunk_panics() {
661        let mut prunable: Prunable<4> = Prunable::new();
662
663        // Add two chunks
664        prunable.push_chunk(&[1, 2, 3, 4]);
665        prunable.push_chunk(&[5, 6, 7, 8]);
666
667        // Prune first chunk
668        prunable.prune_to_bit(32);
669
670        // Try to access pruned chunk - should panic
671        prunable.get_chunk_containing(0);
672    }
673
674    #[test]
675    #[should_panic(expected = "bit pruned")]
676    fn test_set_pruned_bit_panics() {
677        let mut prunable: Prunable<4> = Prunable::new();
678
679        // Add two chunks
680        prunable.push_chunk(&[1, 2, 3, 4]);
681        prunable.push_chunk(&[5, 6, 7, 8]);
682
683        // Prune first chunk
684        prunable.prune_to_bit(32);
685
686        // Try to set pruned bit - should panic
687        prunable.set_bit(0, true);
688    }
689
690    #[test]
691    #[should_panic(expected = "bit 25 out of bounds (len: 24)")]
692    fn test_prune_to_bit_out_of_bounds() {
693        let mut prunable: Prunable<1> = Prunable::new();
694
695        // Add 3 bytes (24 bits total)
696        prunable.push_byte(1);
697        prunable.push_byte(2);
698        prunable.push_byte(3);
699
700        // Try to prune to a bit beyond the bitmap
701        prunable.prune_to_bit(25);
702    }
703
704    #[test]
705    fn test_pruning_with_partial_chunk() {
706        let mut prunable: Prunable<4> = Prunable::new();
707
708        // Add two full chunks and some partial bits
709        prunable.push_chunk(&[0xFF; 4]);
710        prunable.push_chunk(&[0xAA; 4]);
711        prunable.push(true);
712        prunable.push(false);
713        prunable.push(true);
714
715        assert_eq!(prunable.len(), 67); // 64 + 3 bits
716
717        // Prune to second chunk
718        prunable.prune_to_bit(32);
719        assert_eq!(prunable.pruned_chunks(), 1);
720        assert_eq!(prunable.len(), 67);
721
722        // Can still access the partial bits
723        assert!(prunable.get_bit(64));
724        assert!(!prunable.get_bit(65));
725        assert!(prunable.get_bit(66));
726    }
727
728    #[test]
729    fn test_prune_idempotent() {
730        let mut prunable: Prunable<4> = Prunable::new();
731
732        // Add chunks
733        prunable.push_chunk(&[1, 2, 3, 4]);
734        prunable.push_chunk(&[5, 6, 7, 8]);
735
736        // Prune to bit 32
737        prunable.prune_to_bit(32);
738        assert_eq!(prunable.pruned_chunks(), 1);
739
740        // Pruning to same or earlier point should be no-op
741        prunable.prune_to_bit(32);
742        assert_eq!(prunable.pruned_chunks(), 1);
743
744        prunable.prune_to_bit(16);
745        assert_eq!(prunable.pruned_chunks(), 1);
746    }
747
748    #[test]
749    fn test_push_after_pruning() {
750        let mut prunable: Prunable<4> = Prunable::new();
751
752        // Add initial chunks
753        prunable.push_chunk(&[1, 2, 3, 4]);
754        prunable.push_chunk(&[5, 6, 7, 8]);
755
756        // Prune first chunk
757        prunable.prune_to_bit(32);
758        assert_eq!(prunable.len(), 64);
759        assert_eq!(prunable.pruned_chunks(), 1);
760
761        // Add more data
762        prunable.push_chunk(&[9, 10, 11, 12]);
763        assert_eq!(prunable.len(), 96); // 32 pruned + 64 active
764
765        // New chunk should be accessible
766        assert_eq!(prunable.get_chunk_containing(64), &[9, 10, 11, 12]);
767    }
768
769    #[test]
770    fn test_chunk_calculations() {
771        // Test chunk_num calculation
772        assert_eq!(Prunable::<4>::to_chunk_index(0), 0);
773        assert_eq!(Prunable::<4>::to_chunk_index(31), 0);
774        assert_eq!(Prunable::<4>::to_chunk_index(32), 1);
775        assert_eq!(Prunable::<4>::to_chunk_index(63), 1);
776        assert_eq!(Prunable::<4>::to_chunk_index(64), 2);
777
778        // Test chunk_byte_offset
779        assert_eq!(Prunable::<4>::chunk_byte_offset(0), 0);
780        assert_eq!(Prunable::<4>::chunk_byte_offset(8), 1);
781        assert_eq!(Prunable::<4>::chunk_byte_offset(16), 2);
782        assert_eq!(Prunable::<4>::chunk_byte_offset(24), 3);
783        assert_eq!(Prunable::<4>::chunk_byte_offset(32), 0); // Wraps to next chunk
784
785        // Test chunk_byte_bitmask
786        assert_eq!(Prunable::<4>::chunk_byte_bitmask(0), 0b00000001);
787        assert_eq!(Prunable::<4>::chunk_byte_bitmask(1), 0b00000010);
788        assert_eq!(Prunable::<4>::chunk_byte_bitmask(7), 0b10000000);
789        assert_eq!(Prunable::<4>::chunk_byte_bitmask(8), 0b00000001); // Next byte
790    }
791
792    #[test]
793    fn test_last_chunk_with_pruning() {
794        let mut prunable: Prunable<4> = Prunable::new();
795
796        // Add chunks
797        prunable.push_chunk(&[1, 2, 3, 4]);
798        prunable.push_chunk(&[5, 6, 7, 8]);
799        prunable.push(true);
800        prunable.push(false);
801
802        let (_, next_bit) = prunable.last_chunk();
803        assert_eq!(next_bit, 2);
804
805        // Store the chunk data for comparison
806        let chunk_data = *prunable.last_chunk().0;
807
808        // Pruning shouldn't affect last_chunk
809        prunable.prune_to_bit(32);
810        let (chunk2, next_bit2) = prunable.last_chunk();
811        assert_eq!(next_bit2, 2);
812        assert_eq!(&chunk_data, chunk2);
813    }
814
815    #[test]
816    fn test_different_chunk_sizes() {
817        // Test with different chunk sizes
818        let mut p8: Prunable<8> = Prunable::new();
819        let mut p16: Prunable<16> = Prunable::new();
820        let mut p32: Prunable<32> = Prunable::new();
821
822        // Add same pattern to each
823        for i in 0..10 {
824            p8.push(i % 2 == 0);
825            p16.push(i % 2 == 0);
826            p32.push(i % 2 == 0);
827        }
828
829        // All should have same bit count
830        assert_eq!(p8.len(), 10);
831        assert_eq!(p16.len(), 10);
832        assert_eq!(p32.len(), 10);
833
834        // All should have same bit values
835        for i in 0..10 {
836            let expected = i % 2 == 0;
837            if expected {
838                assert!(p8.get_bit(i));
839                assert!(p16.get_bit(i));
840                assert!(p32.get_bit(i));
841            } else {
842                assert!(!p8.get_bit(i));
843                assert!(!p16.get_bit(i));
844                assert!(!p32.get_bit(i));
845            }
846        }
847    }
848
849    #[test]
850    fn test_get_bit_from_chunk() {
851        let chunk: [u8; 4] = [0b10101010, 0b11001100, 0b11110000, 0b00001111];
852
853        // Test first byte
854        assert!(!Prunable::<4>::get_bit_from_chunk(&chunk, 0));
855        assert!(Prunable::<4>::get_bit_from_chunk(&chunk, 1));
856        assert!(!Prunable::<4>::get_bit_from_chunk(&chunk, 2));
857        assert!(Prunable::<4>::get_bit_from_chunk(&chunk, 3));
858
859        // Test second byte
860        assert!(!Prunable::<4>::get_bit_from_chunk(&chunk, 8));
861        assert!(!Prunable::<4>::get_bit_from_chunk(&chunk, 9));
862        assert!(Prunable::<4>::get_bit_from_chunk(&chunk, 10));
863        assert!(Prunable::<4>::get_bit_from_chunk(&chunk, 11));
864    }
865
866    #[test]
867    fn test_get_chunk() {
868        let mut prunable: Prunable<4> = Prunable::new();
869        let chunk1 = hex!("0x11223344");
870        let chunk2 = hex!("0x55667788");
871        let chunk3 = hex!("0x99AABBCC");
872
873        prunable.push_chunk(&chunk1);
874        prunable.push_chunk(&chunk2);
875        prunable.push_chunk(&chunk3);
876
877        // Before pruning
878        assert_eq!(prunable.get_chunk(0), &chunk1);
879        assert_eq!(prunable.get_chunk(1), &chunk2);
880        assert_eq!(prunable.get_chunk(2), &chunk3);
881
882        // After pruning
883        prunable.prune_to_bit(32);
884        assert_eq!(prunable.get_chunk(1), &chunk2);
885        assert_eq!(prunable.get_chunk(2), &chunk3);
886    }
887
888    #[test]
889    fn test_pop() {
890        let mut prunable: Prunable<4> = Prunable::new();
891
892        prunable.push(true);
893        prunable.push(false);
894        prunable.push(true);
895        assert_eq!(prunable.len(), 3);
896
897        assert!(prunable.pop());
898        assert_eq!(prunable.len(), 2);
899
900        assert!(!prunable.pop());
901        assert_eq!(prunable.len(), 1);
902
903        assert!(prunable.pop());
904        assert_eq!(prunable.len(), 0);
905        assert!(prunable.is_empty());
906
907        for i in 0..100 {
908            prunable.push(i % 3 == 0);
909        }
910        assert_eq!(prunable.len(), 100);
911
912        for i in (0..100).rev() {
913            let expected = i % 3 == 0;
914            assert_eq!(prunable.pop(), expected);
915            assert_eq!(prunable.len(), i);
916        }
917
918        assert!(prunable.is_empty());
919    }
920
921    #[test]
922    fn test_truncate() {
923        let mut prunable: Prunable<4> = Prunable::new();
924        let expected: Vec<bool> = (0..96).map(|i| i % 3 == 0).collect();
925        for &bit in &expected {
926            prunable.push(bit);
927        }
928
929        prunable.truncate(65);
930        assert_eq!(prunable.len(), 65);
931        for i in 0..65 {
932            assert_eq!(prunable.get_bit(i), expected[i as usize]);
933        }
934
935        prunable.prune_to_bit(32);
936        assert_eq!(prunable.pruned_bits(), 32);
937
938        prunable.truncate(64);
939        assert_eq!(prunable.len(), 64);
940        for i in 32..64 {
941            assert_eq!(prunable.get_bit(i), expected[i as usize]);
942        }
943    }
944
945    #[test]
946    #[should_panic(expected = "cannot truncate into pruned region")]
947    fn test_truncate_into_pruned_region_panics() {
948        let mut prunable: Prunable<4> = Prunable::new();
949        prunable.push_chunk(&[0xFF; 4]);
950        prunable.push_chunk(&[0xFF; 4]);
951        prunable.prune_to_bit(32);
952        prunable.truncate(31);
953    }
954
955    #[test]
956    fn test_pop_chunk() {
957        let mut prunable: Prunable<4> = Prunable::new();
958        const CHUNK_SIZE: u64 = Prunable::<4>::CHUNK_SIZE_BITS;
959
960        // Test 1: Pop a single chunk and verify it returns the correct data
961        let chunk1 = hex!("0xAABBCCDD");
962        prunable.push_chunk(&chunk1);
963        assert_eq!(prunable.len(), CHUNK_SIZE);
964        let popped = prunable.pop_chunk();
965        assert_eq!(popped, chunk1);
966        assert_eq!(prunable.len(), 0);
967        assert!(prunable.is_empty());
968
969        // Test 2: Pop multiple chunks in reverse order
970        let chunk2 = hex!("0x11223344");
971        let chunk3 = hex!("0x55667788");
972        let chunk4 = hex!("0x99AABBCC");
973
974        prunable.push_chunk(&chunk2);
975        prunable.push_chunk(&chunk3);
976        prunable.push_chunk(&chunk4);
977        assert_eq!(prunable.len(), CHUNK_SIZE * 3);
978
979        assert_eq!(prunable.pop_chunk(), chunk4);
980        assert_eq!(prunable.len(), CHUNK_SIZE * 2);
981
982        assert_eq!(prunable.pop_chunk(), chunk3);
983        assert_eq!(prunable.len(), CHUNK_SIZE);
984
985        assert_eq!(prunable.pop_chunk(), chunk2);
986        assert_eq!(prunable.len(), 0);
987
988        // Test 3: Verify data integrity when popping chunks
989        prunable = Prunable::new();
990        let first_chunk = hex!("0xAABBCCDD");
991        let second_chunk = hex!("0x11223344");
992        prunable.push_chunk(&first_chunk);
993        prunable.push_chunk(&second_chunk);
994
995        // Pop the second chunk, verify it and that first chunk is intact
996        assert_eq!(prunable.pop_chunk(), second_chunk);
997        assert_eq!(prunable.len(), CHUNK_SIZE);
998
999        for i in 0..CHUNK_SIZE {
1000            let byte_idx = (i / 8) as usize;
1001            let bit_idx = i % 8;
1002            let expected = (first_chunk[byte_idx] >> bit_idx) & 1 == 1;
1003            assert_eq!(prunable.get_bit(i), expected);
1004        }
1005
1006        assert_eq!(prunable.pop_chunk(), first_chunk);
1007        assert_eq!(prunable.len(), 0);
1008    }
1009
1010    #[test]
1011    #[should_panic(expected = "cannot pop chunk when not chunk aligned")]
1012    fn test_pop_chunk_not_aligned() {
1013        let mut prunable: Prunable<4> = Prunable::new();
1014
1015        // Push a full chunk plus one bit
1016        prunable.push_chunk(&[0xFF; 4]);
1017        prunable.push(true);
1018
1019        // Should panic because not chunk-aligned
1020        prunable.pop_chunk();
1021    }
1022
1023    #[test]
1024    #[should_panic(expected = "cannot pop chunk: bitmap has fewer than CHUNK_SIZE_BITS bits")]
1025    fn test_pop_chunk_insufficient_bits() {
1026        let mut prunable: Prunable<4> = Prunable::new();
1027
1028        // Push only a few bits (less than a full chunk)
1029        prunable.push(true);
1030        prunable.push(false);
1031
1032        // Should panic because we don't have a full chunk to pop
1033        prunable.pop_chunk();
1034    }
1035
1036    #[test]
1037    fn test_write_read_empty() {
1038        let original: Prunable<4> = Prunable::new();
1039        let mut encoded = original.encode();
1040
1041        let decoded = Prunable::<4>::read_cfg(&mut encoded, &(..).into()).unwrap();
1042        assert_eq!(decoded.len(), original.len());
1043        assert_eq!(decoded.pruned_chunks(), original.pruned_chunks());
1044        assert!(decoded.is_empty());
1045    }
1046
1047    #[test]
1048    fn test_write_read_non_empty() {
1049        let mut original: Prunable<4> = Prunable::new();
1050        original.push_chunk(&hex!("0xAABBCCDD"));
1051        original.push_chunk(&hex!("0x11223344"));
1052        original.push(true);
1053        original.push(false);
1054        original.push(true);
1055
1056        let mut encoded = original.encode();
1057        let decoded = Prunable::<4>::read_cfg(&mut encoded, &(..).into()).unwrap();
1058
1059        assert_eq!(decoded.len(), original.len());
1060        assert_eq!(decoded.pruned_chunks(), original.pruned_chunks());
1061        assert_eq!(decoded.len(), 67);
1062
1063        // Verify all bits match
1064        for i in 0..original.len() {
1065            assert_eq!(decoded.get_bit(i), original.get_bit(i));
1066        }
1067    }
1068
1069    #[test]
1070    fn test_write_read_with_pruning() {
1071        let mut original: Prunable<4> = Prunable::new();
1072        original.push_chunk(&hex!("0x01020304"));
1073        original.push_chunk(&hex!("0x05060708"));
1074        original.push_chunk(&hex!("0x090A0B0C"));
1075
1076        // Prune first chunk
1077        original.prune_to_bit(32);
1078        assert_eq!(original.pruned_chunks(), 1);
1079        assert_eq!(original.len(), 96);
1080
1081        let mut encoded = original.encode();
1082        let decoded = Prunable::<4>::read_cfg(&mut encoded, &(..).into()).unwrap();
1083
1084        assert_eq!(decoded.len(), original.len());
1085        assert_eq!(decoded.pruned_chunks(), original.pruned_chunks());
1086        assert_eq!(decoded.pruned_chunks(), 1);
1087        assert_eq!(decoded.len(), 96);
1088
1089        // Verify remaining chunks match
1090        assert_eq!(decoded.get_chunk_containing(32), &hex!("0x05060708"));
1091        assert_eq!(decoded.get_chunk_containing(64), &hex!("0x090A0B0C"));
1092    }
1093
1094    #[test]
1095    fn test_write_read_with_pruning_2() {
1096        let mut original: Prunable<4> = Prunable::new();
1097
1098        // Add several chunks
1099        for i in 0..5 {
1100            let chunk = [
1101                (i * 4) as u8,
1102                (i * 4 + 1) as u8,
1103                (i * 4 + 2) as u8,
1104                (i * 4 + 3) as u8,
1105            ];
1106            original.push_chunk(&chunk);
1107        }
1108
1109        // Keep only last two chunks
1110        original.prune_to_bit(96); // Prune first 3 chunks
1111        assert_eq!(original.pruned_chunks(), 3);
1112        assert_eq!(original.len(), 160);
1113
1114        let mut encoded = original.encode();
1115        let decoded = Prunable::<4>::read_cfg(&mut encoded, &(..).into()).unwrap();
1116
1117        assert_eq!(decoded.len(), original.len());
1118        assert_eq!(decoded.pruned_chunks(), 3);
1119
1120        // Verify remaining accessible bits match
1121        for i in 96..original.len() {
1122            assert_eq!(decoded.get_bit(i), original.get_bit(i));
1123        }
1124    }
1125
1126    #[test]
1127    fn test_encode_size_matches() {
1128        let mut prunable: Prunable<4> = Prunable::new();
1129        prunable.push_chunk(&[1, 2, 3, 4]);
1130        prunable.push_chunk(&[5, 6, 7, 8]);
1131        prunable.push(true);
1132
1133        let size = prunable.encode_size();
1134        let encoded = prunable.encode();
1135
1136        assert_eq!(size, encoded.len());
1137    }
1138
1139    #[test]
1140    fn test_encode_size_with_pruning() {
1141        let mut prunable: Prunable<4> = Prunable::new();
1142        prunable.push_chunk(&[1, 2, 3, 4]);
1143        prunable.push_chunk(&[5, 6, 7, 8]);
1144        prunable.push_chunk(&[9, 10, 11, 12]);
1145
1146        prunable.prune_to_bit(32);
1147
1148        let size = prunable.encode_size();
1149        let encoded = prunable.encode();
1150
1151        assert_eq!(size, encoded.len());
1152    }
1153
1154    #[test]
1155    fn test_read_max_len_validation() {
1156        let mut original: Prunable<4> = Prunable::new();
1157        for _ in 0..10 {
1158            original.push(true);
1159        }
1160
1161        let mut encoded = original.encode();
1162
1163        // Should succeed with sufficient max_len
1164        assert!(Prunable::<4>::read_cfg(&mut encoded.clone(), &(..=100).into()).is_ok());
1165
1166        // Should fail with insufficient max_len
1167        let result = Prunable::<4>::read_cfg(&mut encoded, &(..=5).into());
1168        assert!(result.is_err());
1169    }
1170
1171    #[test]
1172    fn test_read_range_bounds_retained_bits() {
1173        // 100 bits with the first two chunks (64 bits) pruned leaves 36 retained bits.
1174        let mut original: Prunable<4> = Prunable::new();
1175        for i in 0..100 {
1176            original.push(i % 3 == 0);
1177        }
1178        original.prune_to_bit(64);
1179        let mut encoded = original.encode();
1180
1181        let decoded = Prunable::<4>::read_cfg(&mut encoded.clone(), &RangeCfg::exact(36)).unwrap();
1182        assert_eq!(decoded.len(), 100);
1183        assert_eq!(decoded.pruned_bits(), 64);
1184        let result = Prunable::<4>::read_cfg(&mut encoded.clone(), &RangeCfg::exact(100));
1185        assert!(matches!(result, Err(CodecError::InvalidLength(36))));
1186        let result = Prunable::<4>::read_cfg(&mut encoded, &(37..).into());
1187        assert!(matches!(result, Err(CodecError::InvalidLength(36))));
1188
1189        // Pruning every chunk leaves zero retained bits.
1190        let mut original: Prunable<4> = Prunable::new();
1191        for _ in 0..96 {
1192            original.push(true);
1193        }
1194        original.prune_to_bit(96);
1195        let mut encoded = original.encode();
1196        let decoded = Prunable::<4>::read_cfg(&mut encoded, &RangeCfg::exact(0)).unwrap();
1197        assert_eq!(decoded.len(), 96);
1198        assert_eq!(decoded.pruned_bits(), 96);
1199    }
1200
1201    #[test]
1202    fn test_codec_roundtrip_different_chunk_sizes() {
1203        // Test with different chunk sizes
1204        let mut p8: Prunable<8> = Prunable::new();
1205        let mut p16: Prunable<16> = Prunable::new();
1206        let mut p32: Prunable<32> = Prunable::new();
1207
1208        for i in 0..100 {
1209            let bit = i % 3 == 0;
1210            p8.push(bit);
1211            p16.push(bit);
1212            p32.push(bit);
1213        }
1214
1215        // Roundtrip each
1216        let mut encoded8 = p8.encode();
1217        let decoded8 = Prunable::<8>::read_cfg(&mut encoded8, &(..).into()).unwrap();
1218        assert_eq!(decoded8.len(), p8.len());
1219
1220        let mut encoded16 = p16.encode();
1221        let decoded16 = Prunable::<16>::read_cfg(&mut encoded16, &(..).into()).unwrap();
1222        assert_eq!(decoded16.len(), p16.len());
1223
1224        let mut encoded32 = p32.encode();
1225        let decoded32 = Prunable::<32>::read_cfg(&mut encoded32, &(..).into()).unwrap();
1226        assert_eq!(decoded32.len(), p32.len());
1227    }
1228
1229    #[test]
1230    fn test_read_pruned_chunks_overflow() {
1231        let mut buf = BytesMut::new();
1232
1233        // Write a pruned_chunks value that would overflow when multiplied by CHUNK_SIZE_BITS
1234        let overflowing_pruned_chunks = (u64::MAX / Prunable::<4>::CHUNK_SIZE_BITS) + 1;
1235        overflowing_pruned_chunks.write(&mut buf);
1236
1237        // Write a valid bitmap (empty)
1238        0u64.write(&mut buf); // len = 0
1239
1240        // Try to read - should fail with overflow error
1241        let result = Prunable::<4>::read_cfg(&mut buf, &(..).into());
1242        match result {
1243            Err(CodecError::Invalid(type_name, msg)) => {
1244                assert_eq!(type_name, "Prunable");
1245                assert_eq!(
1246                    msg,
1247                    "pruned_chunks would overflow when computing pruned_bits"
1248                );
1249            }
1250            Ok(_) => panic!("Expected error but got Ok"),
1251            Err(e) => panic!("Expected Invalid error for pruned_bits overflow, got: {e:?}"),
1252        }
1253    }
1254
1255    #[test]
1256    fn test_read_total_length_overflow() {
1257        let mut buf = BytesMut::new();
1258
1259        // Make pruned_bits as large as possible without overflowing
1260        let max_safe_pruned_chunks = u64::MAX / Prunable::<4>::CHUNK_SIZE_BITS;
1261        let pruned_bits = max_safe_pruned_chunks * Prunable::<4>::CHUNK_SIZE_BITS;
1262
1263        // Make bitmap_len large enough that adding it overflows
1264        let remaining_space = u64::MAX - pruned_bits;
1265        let bitmap_len = remaining_space + 1; // Go over by 1 to trigger overflow
1266
1267        // Write the serialized data
1268        max_safe_pruned_chunks.write(&mut buf);
1269        bitmap_len.write(&mut buf);
1270
1271        // Write bitmap chunk data
1272        let num_chunks = bitmap_len.div_ceil(Prunable::<4>::CHUNK_SIZE_BITS);
1273        for _ in 0..(num_chunks * 4) {
1274            0u8.write(&mut buf);
1275        }
1276
1277        // Try to read - should fail because pruned_bits + bitmap_len overflows u64
1278        let result = Prunable::<4>::read_cfg(&mut buf, &(..).into());
1279        match result {
1280            Err(CodecError::Invalid(type_name, msg)) => {
1281                assert_eq!(type_name, "Prunable");
1282                assert_eq!(
1283                    msg,
1284                    "total bitmap length (pruned + unpruned) would overflow u64"
1285                );
1286            }
1287            Ok(_) => panic!("Expected error but got Ok"),
1288            Err(e) => panic!("Expected Invalid error for total length overflow, got: {e:?}"),
1289        }
1290    }
1291
1292    #[test]
1293    fn test_is_chunk_aligned() {
1294        // Empty bitmap is chunk aligned
1295        let prunable: Prunable<4> = Prunable::new();
1296        assert!(prunable.is_chunk_aligned());
1297
1298        // Add bits one at a time and check alignment
1299        let mut prunable: Prunable<4> = Prunable::new();
1300        for i in 1..=32 {
1301            prunable.push(i % 2 == 0);
1302            if i == 32 {
1303                assert!(prunable.is_chunk_aligned()); // Exactly one chunk
1304            } else {
1305                assert!(!prunable.is_chunk_aligned()); // Partial chunk
1306            }
1307        }
1308
1309        // Add another full chunk
1310        for i in 33..=64 {
1311            prunable.push(i % 2 == 0);
1312            if i == 64 {
1313                assert!(prunable.is_chunk_aligned()); // Exactly two chunks
1314            } else {
1315                assert!(!prunable.is_chunk_aligned()); // Partial chunk
1316            }
1317        }
1318
1319        // Test with push_chunk
1320        let mut prunable: Prunable<4> = Prunable::new();
1321        assert!(prunable.is_chunk_aligned());
1322        prunable.push_chunk(&[1, 2, 3, 4]);
1323        assert!(prunable.is_chunk_aligned()); // 32 bits = 1 chunk
1324        prunable.push_chunk(&[5, 6, 7, 8]);
1325        assert!(prunable.is_chunk_aligned()); // 64 bits = 2 chunks
1326        prunable.push(true);
1327        assert!(!prunable.is_chunk_aligned()); // 65 bits = partial chunk
1328
1329        // Test alignment with pruning
1330        let mut prunable: Prunable<4> = Prunable::new();
1331        prunable.push_chunk(&[1, 2, 3, 4]);
1332        prunable.push_chunk(&[5, 6, 7, 8]);
1333        prunable.push_chunk(&[9, 10, 11, 12]);
1334        assert!(prunable.is_chunk_aligned()); // 96 bits = 3 chunks
1335
1336        // Prune first chunk - still aligned (64 bits remaining)
1337        prunable.prune_to_bit(32);
1338        assert!(prunable.is_chunk_aligned());
1339        assert_eq!(prunable.len(), 96);
1340
1341        // Add a partial chunk
1342        prunable.push(true);
1343        prunable.push(false);
1344        assert!(!prunable.is_chunk_aligned()); // 98 bits total
1345
1346        // Prune to align again
1347        prunable.prune_to_bit(64);
1348        assert!(!prunable.is_chunk_aligned()); // 98 bits total (34 bits remaining)
1349
1350        // Test with new_with_pruned_chunks
1351        let prunable: Prunable<4> = Prunable::new_with_pruned_chunks(2).unwrap();
1352        assert!(prunable.is_chunk_aligned()); // 64 bits pruned, 0 bits in bitmap
1353
1354        let mut prunable: Prunable<4> = Prunable::new_with_pruned_chunks(1).unwrap();
1355        assert!(prunable.is_chunk_aligned()); // 32 bits pruned, 0 bits in bitmap
1356        prunable.push(true);
1357        assert!(!prunable.is_chunk_aligned()); // 33 bits total
1358
1359        // Test with push_byte
1360        let mut prunable: Prunable<4> = Prunable::new();
1361        for _ in 0..4 {
1362            prunable.push_byte(0xFF);
1363        }
1364        assert!(prunable.is_chunk_aligned()); // 32 bits = 1 chunk
1365
1366        // Test after pop
1367        prunable.pop();
1368        assert!(!prunable.is_chunk_aligned()); // 31 bits
1369
1370        // Pop back to alignment
1371        for _ in 0..31 {
1372            prunable.pop();
1373        }
1374        assert!(prunable.is_chunk_aligned()); // 0 bits
1375    }
1376
1377    /// Compare the first bit `ones_iter_range` yields with a bit-by-bit scan for ranges over a
1378    /// bitmap of `N`-byte chunks, with and without a pruned chunk.
1379    fn check_ones_iter_ranges<const N: usize>() {
1380        let len = 3 * Prunable::<N>::CHUNK_SIZE_BITS + 3;
1381        for stride in [1, 7, 63, 65, len + 1] {
1382            let mut bitmap = Prunable::<N>::new();
1383            bitmap.extend_to(len);
1384            for bit in (0..len).filter(|bit| bit % stride == 0) {
1385                bitmap.set_bit(bit, true);
1386            }
1387            for pruned in [false, true] {
1388                if pruned {
1389                    bitmap.prune_to_bit(Prunable::<N>::CHUNK_SIZE_BITS);
1390                }
1391
1392                // Wide chunks sample range bounds beside byte boundaries to bound the runtime.
1393                for from in (0..=len + 1).filter(|bit| N <= 9 || bit % 8 <= 1 || bit % 8 == 7) {
1394                    for end in (from..=len + 1).filter(|bit| N <= 9 || bit % 8 <= 1 || bit % 8 == 7)
1395                    {
1396                        let expected = (from.max(bitmap.pruned_bits())..end.min(len))
1397                            .find(|&bit| bitmap.get_bit(bit));
1398                        assert_eq!(
1399                            bitmap.ones_iter_range(from..end).next(),
1400                            expected,
1401                            "N={N}, {from}..{end}"
1402                        );
1403                    }
1404                    assert_eq!(
1405                        bitmap.ones_iter_range(from..u64::MAX).next(),
1406                        (from.max(bitmap.pruned_bits())..len).find(|&bit| bitmap.get_bit(bit))
1407                    );
1408                }
1409            }
1410        }
1411    }
1412
1413    /// [`check_ones_iter_ranges`] for chunks narrower than, equal to, and wider than eight bytes.
1414    #[test]
1415    fn test_ones_iter_range_matches_bit_scan() {
1416        check_ones_iter_ranges::<1>();
1417        check_ones_iter_ranges::<7>();
1418        check_ones_iter_ranges::<8>();
1419        check_ones_iter_ranges::<9>();
1420        check_ones_iter_ranges::<32>();
1421        check_ones_iter_ranges::<64>();
1422    }
1423
1424    /// Ranges ending at or just below `u64::MAX` stop at their end without overflowing.
1425    #[test]
1426    fn test_ones_iter_range_near_max() {
1427        let mut bitmap = Prunable::<8>::new_with_pruned_chunks((u64::MAX / 64) as usize).unwrap();
1428        let from = bitmap.pruned_bits();
1429        bitmap.extend_to(u64::MAX);
1430        assert_eq!(bitmap.ones_iter_range(from..u64::MAX).next(), None);
1431        bitmap.set_bit(u64::MAX - 1, true);
1432        assert_eq!(bitmap.ones_iter_range(from..u64::MAX - 1).next(), None);
1433        assert_eq!(
1434            bitmap.ones_iter_range(from..u64::MAX).next(),
1435            Some(u64::MAX - 1)
1436        );
1437    }
1438
1439    #[test]
1440    fn test_ones_iter_with_pruning() {
1441        // 3 chunks of 32 bits each = 96 bits total.
1442        let mut p = Prunable::<4>::new();
1443        for _ in 0..96 {
1444            p.push(false);
1445        }
1446        // Set bits in chunks 1 and 2 (absolute indices 40, 55, 64, 90).
1447        p.set_bit(40, true);
1448        p.set_bit(55, true);
1449        p.set_bit(64, true);
1450        p.set_bit(90, true);
1451
1452        // Prune first chunk (32 bits).
1453        p.prune_to_bit(32);
1454        assert_eq!(p.pruned_chunks(), 1);
1455        assert_eq!(p.pruned_bits(), 32);
1456
1457        let ones: Vec<u64> = Readable::ones_iter_from(&p, p.pruned_bits()).collect();
1458        assert_eq!(ones, vec![40, 55, 64, 90]);
1459    }
1460
1461    #[test]
1462    fn test_ones_iter_pruned_bits_near_max() {
1463        // The largest pruned offset accepted for 8-byte chunks places pruned_bits at
1464        // u64::MAX - 63, so word advances near the top of the range must not wrap.
1465        const PRUNED_CHUNKS: usize = (u64::MAX / 64) as usize;
1466
1467        // Fully pruned and empty: iteration is exhausted at construction.
1468        let p = Prunable::<8>::new_with_pruned_chunks(PRUNED_CHUNKS).unwrap();
1469        assert_eq!(p.len(), u64::MAX - 63);
1470        assert!(Readable::ones_iter_from(&p, 0).next().is_none());
1471
1472        // Bits in the last representable chunk: iteration must terminate after them.
1473        let mut p = Prunable::<8>::new_with_pruned_chunks(PRUNED_CHUNKS).unwrap();
1474        let start = p.pruned_bits();
1475        for i in 0..32 {
1476            p.push(i == 0 || i == 31);
1477        }
1478        let ones: Vec<u64> = Readable::ones_iter_from(&p, 0).collect();
1479        assert_eq!(ones, vec![start, start + 31]);
1480    }
1481
1482    #[test]
1483    fn test_ones_iter_from_midway_with_pruning() {
1484        let mut p = Prunable::<4>::new();
1485        for _ in 0..96 {
1486            p.push(false);
1487        }
1488        p.set_bit(40, true);
1489        p.set_bit(55, true);
1490        p.set_bit(64, true);
1491        p.set_bit(90, true);
1492
1493        p.prune_to_bit(32);
1494
1495        // Start past the first set bit in the unpruned region.
1496        let ones: Vec<u64> = Readable::ones_iter_from(&p, 50).collect();
1497        assert_eq!(ones, vec![55, 64, 90]);
1498
1499        // Start past all set bits.
1500        let ones: Vec<u64> = Readable::ones_iter_from(&p, 91).collect();
1501        assert!(ones.is_empty());
1502    }
1503
1504    #[test]
1505    fn test_ones_iter_all_ones_with_pruning() {
1506        let mut p = Prunable::<4>::new();
1507        for _ in 0..96 {
1508            p.push(true);
1509        }
1510
1511        // Prune 2 chunks (64 bits).
1512        p.prune_to_bit(64);
1513        assert_eq!(p.pruned_chunks(), 2);
1514
1515        let ones: Vec<u64> = Readable::ones_iter_from(&p, p.pruned_bits()).collect();
1516        let expected: Vec<u64> = (64..96).collect();
1517        assert_eq!(ones, expected);
1518    }
1519
1520    #[test]
1521    fn test_ones_iter_empty_after_pruning() {
1522        let mut p = Prunable::<4>::new();
1523        for _ in 0..64 {
1524            p.push(false);
1525        }
1526
1527        // Prune first chunk, leaving 32 zero bits.
1528        p.prune_to_bit(32);
1529        assert_eq!(p.pruned_chunks(), 1);
1530
1531        let ones: Vec<u64> = Readable::ones_iter_from(&p, p.pruned_bits()).collect();
1532        assert!(ones.is_empty());
1533    }
1534
1535    #[test]
1536    fn test_ones_iter_from_pruned_region_fast_forwards() {
1537        let mut p = Prunable::<4>::new();
1538        for _ in 0..64 {
1539            p.push(true);
1540        }
1541        p.prune_to_bit(32);
1542
1543        // Starting in the pruned region fast-forwards past it.
1544        let ones: Vec<u64> = Readable::ones_iter_from(&p, 0).collect();
1545        let expected: Vec<u64> = (32..64).collect();
1546        assert_eq!(ones, expected);
1547    }
1548
1549    #[test]
1550    fn test_extend_to() {
1551        let mut p = Prunable::<4>::new();
1552        for i in 0..30u64 {
1553            p.push(i % 2 == 0);
1554        }
1555
1556        // No-op when target is smaller.
1557        p.extend_to(5);
1558        assert_eq!(p.len(), 30);
1559
1560        // Extends across a chunk boundary, new bits are zero.
1561        p.extend_to(65);
1562        assert_eq!(p.len(), 65);
1563        for i in 0..30 {
1564            assert_eq!(p.get_bit(i), i % 2 == 0, "original bit {i}");
1565        }
1566        for i in 30..65 {
1567            assert!(!p.get_bit(i), "extended bit {i} should be false");
1568        }
1569
1570        // Works correctly with a pruned prefix.
1571        p.prune_to_bit(32);
1572        p.extend_to(100);
1573        assert_eq!(p.len(), 100);
1574        assert_eq!(p.pruned_bits(), 32);
1575        for i in 65..100 {
1576            assert!(!p.get_bit(i), "extended bit {i} should be false");
1577        }
1578    }
1579
1580    // set_chunk_by_index must reject non-zero bits beyond len() in the last partial chunk.
1581
1582    #[test]
1583    fn test_set_chunk_by_index_accepts_valid_partial_chunk() {
1584        let mut p = Prunable::<4>::new();
1585        p.extend_to(35); // 1 full chunk + 3 bits in chunk 1
1586        // Only the low 3 bits of byte 0 are valid; rest must be zero.
1587        p.set_chunk_by_index(1, &[0b0000_0111, 0, 0, 0]);
1588        assert_eq!(p.get_chunk(1), &[0b0000_0111, 0, 0, 0]);
1589    }
1590
1591    #[test]
1592    fn test_set_chunk_by_index_accepts_full_chunk() {
1593        let mut p = Prunable::<4>::new();
1594        p.extend_to(64); // 2 full chunks
1595        // Full chunk: any byte pattern is valid.
1596        p.set_chunk_by_index(1, &[0xFF; 4]);
1597        assert_eq!(p.get_chunk(1), &[0xFF; 4]);
1598    }
1599
1600    #[test]
1601    #[should_panic(expected = "non-zero bits beyond bitmap length")]
1602    fn test_set_chunk_by_index_rejects_high_bits_in_last_byte() {
1603        let mut p = Prunable::<4>::new();
1604        p.extend_to(35); // 3 valid bits in chunk 1
1605        // Bit 3 (0b0000_1000) is beyond len.
1606        p.set_chunk_by_index(1, &[0b0000_1000, 0, 0, 0]);
1607    }
1608
1609    #[test]
1610    #[should_panic(expected = "non-zero bits beyond bitmap length")]
1611    fn test_set_chunk_by_index_rejects_nonzero_trailing_bytes() {
1612        let mut p = Prunable::<4>::new();
1613        p.extend_to(35); // 3 valid bits in chunk 1
1614        // Byte 1 is entirely beyond len.
1615        p.set_chunk_by_index(1, &[0, 0xFF, 0, 0]);
1616    }
1617
1618    #[test]
1619    #[should_panic(expected = "non-zero bits beyond bitmap length")]
1620    fn test_set_chunk_by_index_rejects_nonzero_final_byte() {
1621        let mut p = Prunable::<4>::new();
1622        p.extend_to(35); // 3 valid bits in chunk 1
1623        p.set_chunk_by_index(1, &[0, 0, 0, 1]);
1624    }
1625
1626    #[test]
1627    fn test_set_chunk_by_index_roundtrips_codec() {
1628        let mut p = Prunable::<4>::new();
1629        p.extend_to(35);
1630        p.set_chunk_by_index(1, &[0b0000_0101, 0, 0, 0]);
1631
1632        let mut encoded = p.encode();
1633        let decoded = Prunable::<4>::read_cfg(&mut encoded, &(..).into())
1634            .expect("valid chunk should round-trip");
1635        assert_eq!(decoded.len(), 35);
1636        assert_eq!(decoded.get_chunk(1), &[0b0000_0101, 0, 0, 0]);
1637    }
1638
1639    #[cfg(feature = "arbitrary")]
1640    mod conformance {
1641        use super::*;
1642        use commonware_codec::conformance::CodecConformance;
1643
1644        commonware_conformance::conformance_tests! {
1645            CodecConformance<Prunable<16>>,
1646        }
1647    }
1648}