1use super::BitMap;
4use bytes::BufMut;
5use commonware_codec::{Buf, EncodeSize, Error as CodecError, RangeCfg, Read, ReadExt, Write};
6use thiserror::Error;
7
8#[derive(Debug, Error, Clone, PartialEq, Eq)]
10pub enum Error {
11 #[error("pruned_chunks * CHUNK_SIZE_BITS overflows u64")]
13 PrunedChunksOverflow,
14}
15
16#[derive(Clone, Debug)]
23pub struct Prunable<const N: usize> {
24 bitmap: BitMap<N>,
26
27 pruned_chunks: usize,
33}
34
35impl<const N: usize> Prunable<N> {
36 pub const CHUNK_SIZE_BITS: u64 = BitMap::<N>::CHUNK_SIZE_BITS;
38
39 pub const fn new() -> Self {
43 Self {
44 bitmap: BitMap::new(),
45 pruned_chunks: 0,
46 }
47 }
48
49 pub fn new_with_pruned_chunks(pruned_chunks: usize) -> Result<Self, Error> {
56 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 #[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 #[inline]
84 pub const fn is_empty(&self) -> bool {
85 self.len() == 0
86 }
87
88 #[inline]
90 pub const fn is_chunk_aligned(&self) -> bool {
91 self.len().is_multiple_of(Self::CHUNK_SIZE_BITS)
92 }
93
94 #[inline]
96 pub fn chunks_len(&self) -> usize {
97 self.pruned_chunks + self.bitmap.chunks_len()
98 }
99
100 #[inline]
102 pub const fn pruned_chunks(&self) -> usize {
103 self.pruned_chunks
104 }
105
106 #[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 #[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 #[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 self.bitmap.get(bit - self.pruned_bits())
139 }
140
141 #[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 self.bitmap.get_chunk_containing(bit - self.pruned_bits())
153 }
154
155 #[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 #[inline]
164 pub fn last_chunk(&self) -> (&[u8; N], u64) {
165 self.bitmap.last_chunk()
166 }
167
168 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 self.bitmap.set(bit - self.pruned_bits(), value);
181 }
182
183 pub fn push(&mut self, bit: bool) {
185 self.bitmap.push(bit);
186 }
187
188 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 pub fn pop(&mut self) -> bool {
203 self.bitmap.pop()
204 }
205
206 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 pub fn push_byte(&mut self, byte: u8) {
225 self.bitmap.push_byte(byte);
226 }
227
228 pub fn push_chunk(&mut self, chunk: &[u8; N]) {
234 self.bitmap.push_chunk(chunk);
235 }
236
237 pub fn extend_from_bitmap<const M: usize>(&mut self, other: &BitMap<M>) {
239 self.bitmap.extend_from_bitmap(other);
240 }
241
242 pub fn pop_chunk(&mut self) -> [u8; N] {
248 self.bitmap.pop_chunk()
249 }
250
251 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 #[inline]
286 pub const fn chunk_byte_bitmask(bit: u64) -> u8 {
287 BitMap::<N>::chunk_byte_bitmask(bit)
288 }
289
290 #[inline]
292 pub const fn chunk_byte_offset(bit: u64) -> usize {
293 BitMap::<N>::chunk_byte_offset(bit)
294 }
295
296 #[inline]
302 pub fn to_chunk_index(bit: u64) -> usize {
303 BitMap::<N>::to_chunk_index(bit)
304 }
305
306 #[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 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 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 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 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 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 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); }
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 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 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 prunable.push_byte(0xFF);
529 assert_eq!(prunable.len(), 8);
530
531 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 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); 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 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 prunable.push(false);
592 prunable.push(false);
593 prunable.push(false);
594
595 assert!(!prunable.get_bit(1));
596
597 prunable.set_bit(1, true);
599 assert!(prunable.get_bit(1));
600
601 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 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); assert_eq!(prunable.pruned_chunks(), 0);
621
622 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); assert_eq!(prunable.get_chunk_containing(32), &chunk2);
630 assert_eq!(prunable.get_chunk_containing(64), &chunk3);
631
632 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 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 prunable.push_chunk(&[1, 2, 3, 4]);
649 prunable.push_chunk(&[5, 6, 7, 8]);
650
651 prunable.prune_to_bit(32);
653
654 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 prunable.push_chunk(&[1, 2, 3, 4]);
665 prunable.push_chunk(&[5, 6, 7, 8]);
666
667 prunable.prune_to_bit(32);
669
670 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 prunable.push_chunk(&[1, 2, 3, 4]);
681 prunable.push_chunk(&[5, 6, 7, 8]);
682
683 prunable.prune_to_bit(32);
685
686 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 prunable.push_byte(1);
697 prunable.push_byte(2);
698 prunable.push_byte(3);
699
700 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 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); prunable.prune_to_bit(32);
719 assert_eq!(prunable.pruned_chunks(), 1);
720 assert_eq!(prunable.len(), 67);
721
722 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 prunable.push_chunk(&[1, 2, 3, 4]);
734 prunable.push_chunk(&[5, 6, 7, 8]);
735
736 prunable.prune_to_bit(32);
738 assert_eq!(prunable.pruned_chunks(), 1);
739
740 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 prunable.push_chunk(&[1, 2, 3, 4]);
754 prunable.push_chunk(&[5, 6, 7, 8]);
755
756 prunable.prune_to_bit(32);
758 assert_eq!(prunable.len(), 64);
759 assert_eq!(prunable.pruned_chunks(), 1);
760
761 prunable.push_chunk(&[9, 10, 11, 12]);
763 assert_eq!(prunable.len(), 96); assert_eq!(prunable.get_chunk_containing(64), &[9, 10, 11, 12]);
767 }
768
769 #[test]
770 fn test_chunk_calculations() {
771 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 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); 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); }
791
792 #[test]
793 fn test_last_chunk_with_pruning() {
794 let mut prunable: Prunable<4> = Prunable::new();
795
796 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 let chunk_data = *prunable.last_chunk().0;
807
808 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 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 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 assert_eq!(p8.len(), 10);
831 assert_eq!(p16.len(), 10);
832 assert_eq!(p32.len(), 10);
833
834 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 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 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 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 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 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 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 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 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 prunable.push_chunk(&[0xFF; 4]);
1017 prunable.push(true);
1018
1019 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 prunable.push(true);
1030 prunable.push(false);
1031
1032 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 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 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 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 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 original.prune_to_bit(96); 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 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 assert!(Prunable::<4>::read_cfg(&mut encoded.clone(), &(..=100).into()).is_ok());
1165
1166 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 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 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 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 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 let overflowing_pruned_chunks = (u64::MAX / Prunable::<4>::CHUNK_SIZE_BITS) + 1;
1235 overflowing_pruned_chunks.write(&mut buf);
1236
1237 0u64.write(&mut buf); 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 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 let remaining_space = u64::MAX - pruned_bits;
1265 let bitmap_len = remaining_space + 1; max_safe_pruned_chunks.write(&mut buf);
1269 bitmap_len.write(&mut buf);
1270
1271 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 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 let prunable: Prunable<4> = Prunable::new();
1296 assert!(prunable.is_chunk_aligned());
1297
1298 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()); } else {
1305 assert!(!prunable.is_chunk_aligned()); }
1307 }
1308
1309 for i in 33..=64 {
1311 prunable.push(i % 2 == 0);
1312 if i == 64 {
1313 assert!(prunable.is_chunk_aligned()); } else {
1315 assert!(!prunable.is_chunk_aligned()); }
1317 }
1318
1319 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()); prunable.push_chunk(&[5, 6, 7, 8]);
1325 assert!(prunable.is_chunk_aligned()); prunable.push(true);
1327 assert!(!prunable.is_chunk_aligned()); 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()); prunable.prune_to_bit(32);
1338 assert!(prunable.is_chunk_aligned());
1339 assert_eq!(prunable.len(), 96);
1340
1341 prunable.push(true);
1343 prunable.push(false);
1344 assert!(!prunable.is_chunk_aligned()); prunable.prune_to_bit(64);
1348 assert!(!prunable.is_chunk_aligned()); let prunable: Prunable<4> = Prunable::new_with_pruned_chunks(2).unwrap();
1352 assert!(prunable.is_chunk_aligned()); let mut prunable: Prunable<4> = Prunable::new_with_pruned_chunks(1).unwrap();
1355 assert!(prunable.is_chunk_aligned()); prunable.push(true);
1357 assert!(!prunable.is_chunk_aligned()); let mut prunable: Prunable<4> = Prunable::new();
1361 for _ in 0..4 {
1362 prunable.push_byte(0xFF);
1363 }
1364 assert!(prunable.is_chunk_aligned()); prunable.pop();
1368 assert!(!prunable.is_chunk_aligned()); for _ in 0..31 {
1372 prunable.pop();
1373 }
1374 assert!(prunable.is_chunk_aligned()); }
1376
1377 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 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 #[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 #[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 let mut p = Prunable::<4>::new();
1443 for _ in 0..96 {
1444 p.push(false);
1445 }
1446 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 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 const PRUNED_CHUNKS: usize = (u64::MAX / 64) as usize;
1466
1467 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 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 let ones: Vec<u64> = Readable::ones_iter_from(&p, 50).collect();
1497 assert_eq!(ones, vec![55, 64, 90]);
1498
1499 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 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 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 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 p.extend_to(5);
1558 assert_eq!(p.len(), 30);
1559
1560 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 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 #[test]
1583 fn test_set_chunk_by_index_accepts_valid_partial_chunk() {
1584 let mut p = Prunable::<4>::new();
1585 p.extend_to(35); 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); 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); 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); 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); 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}