1use crate::format::bytes::{read_le_addr as read_addr, read_le_uint as read_size};
11use crate::format::checksum::checksum_metadata;
12use crate::format::{FormatContext, FormatError, FormatResult, UNDEF_ADDR};
13
14pub const FAHD_SIGNATURE: [u8; 4] = *b"FAHD";
16pub const FADB_SIGNATURE: [u8; 4] = *b"FADB";
18
19pub const FA_VERSION: u8 = 0;
21
22pub const FA_CLIENT_CHUNK: u8 = 0;
24pub const FA_CLIENT_FILT_CHUNK: u8 = 1;
26
27pub const FA_MAX_DBLK_PAGE_NELMTS_BITS: u8 = 10;
33
34#[derive(Debug, Clone, PartialEq)]
45pub struct FixedArrayHeader {
46 pub client_id: u8,
47 pub element_size: u8,
48 pub max_dblk_page_nelmts_bits: u8,
49 pub num_elmts: u64,
50 pub data_blk_addr: u64,
51}
52
53impl FixedArrayHeader {
54 pub fn new_for_chunks(ctx: &FormatContext, num_elmts: u64) -> Self {
56 Self {
57 client_id: FA_CLIENT_CHUNK,
58 element_size: ctx.sizeof_addr,
59 max_dblk_page_nelmts_bits: FA_MAX_DBLK_PAGE_NELMTS_BITS,
60 num_elmts,
61 data_blk_addr: UNDEF_ADDR,
62 }
63 }
64
65 pub fn new_for_filtered_chunks(
70 ctx: &FormatContext,
71 num_elmts: u64,
72 chunk_size_len: u8,
73 ) -> Self {
74 let element_size = ctx.sizeof_addr + chunk_size_len + 4;
76 Self {
77 client_id: FA_CLIENT_FILT_CHUNK,
78 element_size,
79 max_dblk_page_nelmts_bits: FA_MAX_DBLK_PAGE_NELMTS_BITS,
80 num_elmts,
81 data_blk_addr: UNDEF_ADDR,
82 }
83 }
84
85 pub fn dblk_page_nelmts(&self) -> u64 {
89 1u64 << (self.max_dblk_page_nelmts_bits as u64)
90 }
91
92 pub fn is_paged(&self) -> bool {
97 self.num_elmts > self.dblk_page_nelmts()
98 }
99
100 pub fn npages(&self) -> u64 {
106 if self.is_paged() {
107 self.num_elmts.div_ceil(self.dblk_page_nelmts())
108 } else {
109 0
110 }
111 }
112
113 pub fn encoded_size(&self, ctx: &FormatContext) -> usize {
115 let ss = ctx.sizeof_size as usize;
116 let sa = ctx.sizeof_addr as usize;
117 4 + 1 + 1 + 1 + 1 + ss + sa + 4
122 }
123
124 pub fn encode(&self, ctx: &FormatContext) -> Vec<u8> {
125 let ss = ctx.sizeof_size as usize;
126 let sa = ctx.sizeof_addr as usize;
127 let size = self.encoded_size(ctx);
128 let mut buf = Vec::with_capacity(size);
129
130 buf.extend_from_slice(&FAHD_SIGNATURE);
131 buf.push(FA_VERSION);
132 buf.push(self.client_id);
133 buf.push(self.element_size);
134 buf.push(self.max_dblk_page_nelmts_bits);
135
136 buf.extend_from_slice(&self.num_elmts.to_le_bytes()[..ss]);
137 buf.extend_from_slice(&self.data_blk_addr.to_le_bytes()[..sa]);
138
139 let cksum = checksum_metadata(&buf);
140 buf.extend_from_slice(&cksum.to_le_bytes());
141
142 debug_assert_eq!(buf.len(), size);
143 buf
144 }
145
146 pub fn decode(buf: &[u8], ctx: &FormatContext) -> FormatResult<Self> {
147 let ss = ctx.sizeof_size as usize;
148 let sa = ctx.sizeof_addr as usize;
149 let min_size = 4 + 1 + 1 + 1 + 1 + ss + sa + 4;
150
151 if buf.len() < min_size {
152 return Err(FormatError::BufferTooShort {
153 needed: min_size,
154 available: buf.len(),
155 });
156 }
157
158 if buf[0..4] != FAHD_SIGNATURE {
159 return Err(FormatError::InvalidSignature);
160 }
161
162 let version = buf[4];
163 if version != FA_VERSION {
164 return Err(FormatError::InvalidVersion(version));
165 }
166
167 let data_end = min_size - 4;
169 let stored_cksum = u32::from_le_bytes([
170 buf[data_end],
171 buf[data_end + 1],
172 buf[data_end + 2],
173 buf[data_end + 3],
174 ]);
175 let computed_cksum = checksum_metadata(&buf[..data_end]);
176 if stored_cksum != computed_cksum {
177 return Err(FormatError::ChecksumMismatch {
178 expected: stored_cksum,
179 computed: computed_cksum,
180 });
181 }
182
183 let client_id = buf[5];
184 let element_size = buf[6];
185 let max_dblk_page_nelmts_bits = buf[7];
186 if max_dblk_page_nelmts_bits >= 64 {
189 return Err(FormatError::InvalidData(format!(
190 "fixed-array max_dblk_page_nelmts_bits {max_dblk_page_nelmts_bits} is too large"
191 )));
192 }
193
194 let mut pos = 8;
195 let num_elmts = read_size(&buf[pos..], ss);
196 pos += ss;
197 let data_blk_addr = read_addr(&buf[pos..], sa);
198
199 Ok(Self {
200 client_id,
201 element_size,
202 max_dblk_page_nelmts_bits,
203 num_elmts,
204 data_blk_addr,
205 })
206 }
207}
208
209#[derive(Debug, Clone, PartialEq)]
212pub struct FixedArrayChunkElement {
213 pub address: u64,
214}
215
216#[derive(Debug, Clone, PartialEq)]
218pub struct FixedArrayFilteredChunkElement {
219 pub address: u64,
220 pub chunk_size: u32,
221 pub filter_mask: u32,
222}
223
224#[derive(Debug, Clone, PartialEq)]
235pub struct FixedArrayDataBlock {
236 pub client_id: u8,
237 pub header_addr: u64,
238 pub elements: Vec<u64>,
240 pub filtered_elements: Vec<FixedArrayFilteredChunkElement>,
242}
243
244impl FixedArrayDataBlock {
245 pub fn new_unfiltered(header_addr: u64, num_elmts: usize) -> Self {
247 Self {
248 client_id: FA_CLIENT_CHUNK,
249 header_addr,
250 elements: vec![UNDEF_ADDR; num_elmts],
251 filtered_elements: Vec::new(),
252 }
253 }
254
255 pub fn new_filtered(header_addr: u64, num_elmts: usize) -> Self {
257 let default_entry = FixedArrayFilteredChunkElement {
258 address: UNDEF_ADDR,
259 chunk_size: 0,
260 filter_mask: 0,
261 };
262 Self {
263 client_id: FA_CLIENT_FILT_CHUNK,
264 header_addr,
265 elements: Vec::new(),
266 filtered_elements: vec![default_entry; num_elmts],
267 }
268 }
269
270 pub fn encoded_size_unfiltered(&self, ctx: &FormatContext) -> usize {
272 let sa = ctx.sizeof_addr as usize;
273 4 + 1 + 1 + sa + self.elements.len() * sa + 4
278 }
279
280 pub fn encoded_size_filtered(&self, ctx: &FormatContext, chunk_size_len: usize) -> usize {
282 let sa = ctx.sizeof_addr as usize;
283 let elem_size = sa + chunk_size_len + 4; 4 + 1 + 1 + sa + self.filtered_elements.len() * elem_size + 4
289 }
290
291 pub fn encode_unfiltered(&self, ctx: &FormatContext) -> Vec<u8> {
293 let sa = ctx.sizeof_addr as usize;
294 let size = self.encoded_size_unfiltered(ctx);
295 let mut buf = Vec::with_capacity(size);
296
297 buf.extend_from_slice(&FADB_SIGNATURE);
298 buf.push(FA_VERSION);
299 buf.push(self.client_id);
300 buf.extend_from_slice(&self.header_addr.to_le_bytes()[..sa]);
301
302 for &addr in &self.elements {
303 buf.extend_from_slice(&addr.to_le_bytes()[..sa]);
304 }
305
306 let cksum = checksum_metadata(&buf);
307 buf.extend_from_slice(&cksum.to_le_bytes());
308
309 debug_assert_eq!(buf.len(), size);
310 buf
311 }
312
313 pub fn encode_filtered(&self, ctx: &FormatContext, chunk_size_len: usize) -> Vec<u8> {
315 let sa = ctx.sizeof_addr as usize;
316 let size = self.encoded_size_filtered(ctx, chunk_size_len);
317 let mut buf = Vec::with_capacity(size);
318
319 buf.extend_from_slice(&FADB_SIGNATURE);
320 buf.push(FA_VERSION);
321 buf.push(self.client_id);
322 buf.extend_from_slice(&self.header_addr.to_le_bytes()[..sa]);
323
324 for elem in &self.filtered_elements {
325 buf.extend_from_slice(&elem.address.to_le_bytes()[..sa]);
326 buf.extend_from_slice(&elem.chunk_size.to_le_bytes()[..chunk_size_len]);
327 buf.extend_from_slice(&elem.filter_mask.to_le_bytes());
328 }
329
330 let cksum = checksum_metadata(&buf);
331 buf.extend_from_slice(&cksum.to_le_bytes());
332
333 debug_assert_eq!(buf.len(), size);
334 buf
335 }
336
337 pub fn decode_unfiltered(
339 buf: &[u8],
340 ctx: &FormatContext,
341 num_elmts: usize,
342 ) -> FormatResult<Self> {
343 let sa = ctx.sizeof_addr as usize;
344 let min_size = num_elmts.saturating_mul(sa).saturating_add(10 + sa);
347
348 if buf.len() < min_size {
349 return Err(FormatError::BufferTooShort {
350 needed: min_size,
351 available: buf.len(),
352 });
353 }
354
355 if buf[0..4] != FADB_SIGNATURE {
356 return Err(FormatError::InvalidSignature);
357 }
358
359 let version = buf[4];
360 if version != FA_VERSION {
361 return Err(FormatError::InvalidVersion(version));
362 }
363
364 let data_end = min_size - 4;
366 let stored_cksum = u32::from_le_bytes([
367 buf[data_end],
368 buf[data_end + 1],
369 buf[data_end + 2],
370 buf[data_end + 3],
371 ]);
372 let computed_cksum = checksum_metadata(&buf[..data_end]);
373 if stored_cksum != computed_cksum {
374 return Err(FormatError::ChecksumMismatch {
375 expected: stored_cksum,
376 computed: computed_cksum,
377 });
378 }
379
380 let client_id = buf[5];
381 let mut pos = 6;
382 let header_addr = read_addr(&buf[pos..], sa);
383 pos += sa;
384
385 let mut elements = Vec::with_capacity(num_elmts);
386 for _ in 0..num_elmts {
387 elements.push(read_addr(&buf[pos..], sa));
388 pos += sa;
389 }
390
391 Ok(Self {
392 client_id,
393 header_addr,
394 elements,
395 filtered_elements: Vec::new(),
396 })
397 }
398
399 pub fn decode_filtered(
401 buf: &[u8],
402 ctx: &FormatContext,
403 num_elmts: usize,
404 chunk_size_len: usize,
405 ) -> FormatResult<Self> {
406 let sa = ctx.sizeof_addr as usize;
407 let elem_size = sa + chunk_size_len + 4;
408 let min_size = num_elmts.saturating_mul(elem_size).saturating_add(10 + sa);
409
410 if buf.len() < min_size {
411 return Err(FormatError::BufferTooShort {
412 needed: min_size,
413 available: buf.len(),
414 });
415 }
416
417 if buf[0..4] != FADB_SIGNATURE {
418 return Err(FormatError::InvalidSignature);
419 }
420
421 let version = buf[4];
422 if version != FA_VERSION {
423 return Err(FormatError::InvalidVersion(version));
424 }
425
426 let data_end = min_size - 4;
428 let stored_cksum = u32::from_le_bytes([
429 buf[data_end],
430 buf[data_end + 1],
431 buf[data_end + 2],
432 buf[data_end + 3],
433 ]);
434 let computed_cksum = checksum_metadata(&buf[..data_end]);
435 if stored_cksum != computed_cksum {
436 return Err(FormatError::ChecksumMismatch {
437 expected: stored_cksum,
438 computed: computed_cksum,
439 });
440 }
441
442 let client_id = buf[5];
443 let mut pos = 6;
444 let header_addr = read_addr(&buf[pos..], sa);
445 pos += sa;
446
447 let mut filtered_elements = Vec::with_capacity(num_elmts);
448 for _ in 0..num_elmts {
449 let address = read_addr(&buf[pos..], sa);
450 pos += sa;
451 let chunk_size = read_size(&buf[pos..], chunk_size_len) as u32;
452 pos += chunk_size_len;
453 let filter_mask =
454 u32::from_le_bytes([buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]]);
455 pos += 4;
456 filtered_elements.push(FixedArrayFilteredChunkElement {
457 address,
458 chunk_size,
459 filter_mask,
460 });
461 }
462
463 Ok(Self {
464 client_id,
465 header_addr,
466 elements: Vec::new(),
467 filtered_elements,
468 })
469 }
470}
471
472#[derive(Debug, Clone, PartialEq)]
484pub struct FixedArrayPagedPrefix {
485 pub client_id: u8,
486 pub header_addr: u64,
487 pub page_init_bitmap: Vec<u8>,
490 pub prefix_size: usize,
493}
494
495impl FixedArrayPagedPrefix {
496 pub fn decode(buf: &[u8], ctx: &FormatContext, npages: u64) -> FormatResult<Self> {
500 let sa = ctx.sizeof_addr as usize;
501 let bitmap_size = (npages as usize).div_ceil(8);
502 let prefix_size = 4 + 1 + 1 + sa + bitmap_size + 4;
505
506 if buf.len() < prefix_size {
507 return Err(FormatError::BufferTooShort {
508 needed: prefix_size,
509 available: buf.len(),
510 });
511 }
512
513 if buf[0..4] != FADB_SIGNATURE {
514 return Err(FormatError::InvalidSignature);
515 }
516
517 let version = buf[4];
518 if version != FA_VERSION {
519 return Err(FormatError::InvalidVersion(version));
520 }
521
522 let data_end = prefix_size - 4;
524 let stored_cksum = u32::from_le_bytes([
525 buf[data_end],
526 buf[data_end + 1],
527 buf[data_end + 2],
528 buf[data_end + 3],
529 ]);
530 let computed_cksum = checksum_metadata(&buf[..data_end]);
531 if stored_cksum != computed_cksum {
532 return Err(FormatError::ChecksumMismatch {
533 expected: stored_cksum,
534 computed: computed_cksum,
535 });
536 }
537
538 let client_id = buf[5];
539 let mut pos = 6;
540 let header_addr = read_addr(&buf[pos..], sa);
541 pos += sa;
542 let page_init_bitmap = buf[pos..pos + bitmap_size].to_vec();
543
544 Ok(Self {
545 client_id,
546 header_addr,
547 page_init_bitmap,
548 prefix_size,
549 })
550 }
551
552 pub fn page_initialized(&self, p: usize) -> bool {
556 let byte = p / 8;
557 if byte >= self.page_init_bitmap.len() {
558 return false;
559 }
560 (self.page_init_bitmap[byte] & (0x80u8 >> (p % 8))) != 0
561 }
562
563 pub fn encode(&self, ctx: &FormatContext) -> Vec<u8> {
569 let sa = ctx.sizeof_addr as usize;
570 let mut buf = Vec::with_capacity(4 + 1 + 1 + sa + self.page_init_bitmap.len() + 4);
571 buf.extend_from_slice(&FADB_SIGNATURE);
572 buf.push(FA_VERSION);
573 buf.push(self.client_id);
574 buf.extend_from_slice(&self.header_addr.to_le_bytes()[..sa]);
575 buf.extend_from_slice(&self.page_init_bitmap);
576 let cksum = checksum_metadata(&buf);
577 buf.extend_from_slice(&cksum.to_le_bytes());
578 buf
579 }
580}
581
582pub fn encode_unfiltered_page(addrs: &[u64], ctx: &FormatContext) -> Vec<u8> {
588 let sa = ctx.sizeof_addr as usize;
589 let mut buf = Vec::with_capacity(addrs.len() * sa + 4);
590 for &addr in addrs {
591 buf.extend_from_slice(&addr.to_le_bytes()[..sa]);
592 }
593 let cksum = checksum_metadata(&buf);
594 buf.extend_from_slice(&cksum.to_le_bytes());
595 buf
596}
597
598pub fn encode_filtered_page(
603 elems: &[FixedArrayFilteredChunkElement],
604 ctx: &FormatContext,
605 chunk_size_len: usize,
606) -> Vec<u8> {
607 let sa = ctx.sizeof_addr as usize;
608 let elem_size = sa + chunk_size_len + 4;
609 let mut buf = Vec::with_capacity(elems.len() * elem_size + 4);
610 for e in elems {
611 buf.extend_from_slice(&e.address.to_le_bytes()[..sa]);
612 buf.extend_from_slice(&(e.chunk_size as u64).to_le_bytes()[..chunk_size_len]);
613 buf.extend_from_slice(&e.filter_mask.to_le_bytes());
614 }
615 let cksum = checksum_metadata(&buf);
616 buf.extend_from_slice(&cksum.to_le_bytes());
617 buf
618}
619
620pub fn decode_unfiltered_page(
626 page_buf: &[u8],
627 ctx: &FormatContext,
628 nelmts: usize,
629) -> FormatResult<Vec<u64>> {
630 let sa = ctx.sizeof_addr as usize;
631 let page_size = nelmts.saturating_mul(sa).saturating_add(4);
634 if page_buf.len() < page_size {
635 return Err(FormatError::BufferTooShort {
636 needed: page_size,
637 available: page_buf.len(),
638 });
639 }
640
641 let data_end = page_size - 4;
642 let stored_cksum = u32::from_le_bytes([
643 page_buf[data_end],
644 page_buf[data_end + 1],
645 page_buf[data_end + 2],
646 page_buf[data_end + 3],
647 ]);
648 let computed_cksum = checksum_metadata(&page_buf[..data_end]);
649 if stored_cksum != computed_cksum {
650 return Err(FormatError::ChecksumMismatch {
651 expected: stored_cksum,
652 computed: computed_cksum,
653 });
654 }
655
656 let mut elements = Vec::with_capacity(nelmts);
657 let mut pos = 0;
658 for _ in 0..nelmts {
659 elements.push(read_addr(&page_buf[pos..], sa));
660 pos += sa;
661 }
662 Ok(elements)
663}
664
665pub fn decode_filtered_page(
671 page_buf: &[u8],
672 ctx: &FormatContext,
673 nelmts: usize,
674 chunk_size_len: usize,
675) -> FormatResult<Vec<FixedArrayFilteredChunkElement>> {
676 let sa = ctx.sizeof_addr as usize;
677 let elem_size = sa + chunk_size_len + 4;
678 let page_size = nelmts.saturating_mul(elem_size).saturating_add(4);
681 if page_buf.len() < page_size {
682 return Err(FormatError::BufferTooShort {
683 needed: page_size,
684 available: page_buf.len(),
685 });
686 }
687
688 let data_end = page_size - 4;
689 let stored_cksum = u32::from_le_bytes([
690 page_buf[data_end],
691 page_buf[data_end + 1],
692 page_buf[data_end + 2],
693 page_buf[data_end + 3],
694 ]);
695 let computed_cksum = checksum_metadata(&page_buf[..data_end]);
696 if stored_cksum != computed_cksum {
697 return Err(FormatError::ChecksumMismatch {
698 expected: stored_cksum,
699 computed: computed_cksum,
700 });
701 }
702
703 let mut elements = Vec::with_capacity(nelmts);
704 let mut pos = 0;
705 for _ in 0..nelmts {
706 let address = read_addr(&page_buf[pos..], sa);
707 pos += sa;
708 let chunk_size = read_size(&page_buf[pos..], chunk_size_len) as u32;
709 pos += chunk_size_len;
710 let filter_mask = u32::from_le_bytes([
711 page_buf[pos],
712 page_buf[pos + 1],
713 page_buf[pos + 2],
714 page_buf[pos + 3],
715 ]);
716 pos += 4;
717 elements.push(FixedArrayFilteredChunkElement {
718 address,
719 chunk_size,
720 filter_mask,
721 });
722 }
723 Ok(elements)
724}
725
726#[cfg(test)]
731mod tests {
732 use super::*;
733
734 fn ctx8() -> FormatContext {
735 FormatContext {
736 sizeof_addr: 8,
737 sizeof_size: 8,
738 }
739 }
740
741 fn ctx4() -> FormatContext {
742 FormatContext {
743 sizeof_addr: 4,
744 sizeof_size: 4,
745 }
746 }
747
748 #[test]
749 fn header_roundtrip() {
750 let mut hdr = FixedArrayHeader::new_for_chunks(&ctx8(), 10);
751 hdr.data_blk_addr = 0x2000;
752
753 let encoded = hdr.encode(&ctx8());
754 assert_eq!(encoded.len(), hdr.encoded_size(&ctx8()));
755 assert_eq!(&encoded[..4], b"FAHD");
756
757 let decoded = FixedArrayHeader::decode(&encoded, &ctx8()).unwrap();
758 assert_eq!(decoded, hdr);
759 }
760
761 #[test]
762 fn header_roundtrip_ctx4() {
763 let mut hdr = FixedArrayHeader::new_for_chunks(&ctx4(), 5);
764 hdr.data_blk_addr = 0x800;
765
766 let encoded = hdr.encode(&ctx4());
767 let decoded = FixedArrayHeader::decode(&encoded, &ctx4()).unwrap();
768 assert_eq!(decoded, hdr);
769 }
770
771 #[test]
772 fn header_bad_signature() {
773 let hdr = FixedArrayHeader::new_for_chunks(&ctx8(), 10);
774 let mut encoded = hdr.encode(&ctx8());
775 encoded[0] = b'X';
776 let err = FixedArrayHeader::decode(&encoded, &ctx8()).unwrap_err();
777 assert!(matches!(err, FormatError::InvalidSignature));
778 }
779
780 #[test]
781 fn header_checksum_mismatch() {
782 let hdr = FixedArrayHeader::new_for_chunks(&ctx8(), 10);
783 let mut encoded = hdr.encode(&ctx8());
784 encoded[6] ^= 0xFF;
785 let err = FixedArrayHeader::decode(&encoded, &ctx8()).unwrap_err();
786 assert!(matches!(err, FormatError::ChecksumMismatch { .. }));
787 }
788
789 #[test]
790 fn data_block_unfiltered_roundtrip() {
791 let mut dblk = FixedArrayDataBlock::new_unfiltered(0x1000, 4);
792 dblk.elements[0] = 0x3000;
793 dblk.elements[1] = 0x4000;
794 dblk.elements[2] = UNDEF_ADDR;
795 dblk.elements[3] = 0x5000;
796
797 let encoded = dblk.encode_unfiltered(&ctx8());
798 assert_eq!(encoded.len(), dblk.encoded_size_unfiltered(&ctx8()));
799 assert_eq!(&encoded[..4], b"FADB");
800
801 let decoded = FixedArrayDataBlock::decode_unfiltered(&encoded, &ctx8(), 4).unwrap();
802 assert_eq!(decoded.elements, dblk.elements);
803 assert_eq!(decoded.header_addr, 0x1000);
804 }
805
806 #[test]
807 fn data_block_unfiltered_roundtrip_ctx4() {
808 let mut dblk = FixedArrayDataBlock::new_unfiltered(0x500, 3);
809 dblk.elements[0] = 0x100;
810 dblk.elements[1] = 0x200;
811 dblk.elements[2] = 0x300;
812
813 let encoded = dblk.encode_unfiltered(&ctx4());
814 let decoded = FixedArrayDataBlock::decode_unfiltered(&encoded, &ctx4(), 3).unwrap();
815 assert_eq!(decoded.elements, dblk.elements);
816 }
817
818 #[test]
819 fn data_block_unfiltered_bad_checksum() {
820 let dblk = FixedArrayDataBlock::new_unfiltered(0x1000, 2);
821 let mut encoded = dblk.encode_unfiltered(&ctx8());
822 encoded[8] ^= 0xFF;
823 let err = FixedArrayDataBlock::decode_unfiltered(&encoded, &ctx8(), 2).unwrap_err();
824 assert!(matches!(err, FormatError::ChecksumMismatch { .. }));
825 }
826
827 #[test]
828 fn data_block_filtered_roundtrip() {
829 let mut dblk = FixedArrayDataBlock::new_filtered(0x1000, 2);
830 dblk.filtered_elements[0] = FixedArrayFilteredChunkElement {
831 address: 0x2000,
832 chunk_size: 512,
833 filter_mask: 0,
834 };
835 dblk.filtered_elements[1] = FixedArrayFilteredChunkElement {
836 address: 0x3000,
837 chunk_size: 400,
838 filter_mask: 1,
839 };
840
841 let chunk_size_len = 4; let encoded = dblk.encode_filtered(&ctx8(), chunk_size_len);
843 assert_eq!(
844 encoded.len(),
845 dblk.encoded_size_filtered(&ctx8(), chunk_size_len)
846 );
847
848 let decoded =
849 FixedArrayDataBlock::decode_filtered(&encoded, &ctx8(), 2, chunk_size_len).unwrap();
850 assert_eq!(decoded.filtered_elements, dblk.filtered_elements);
851 }
852
853 #[test]
854 fn header_filtered_roundtrip() {
855 let hdr = FixedArrayHeader::new_for_filtered_chunks(&ctx8(), 6, 4);
856 assert_eq!(hdr.element_size, 8 + 4 + 4); assert_eq!(hdr.client_id, FA_CLIENT_FILT_CHUNK);
858
859 let encoded = hdr.encode(&ctx8());
860 let decoded = FixedArrayHeader::decode(&encoded, &ctx8()).unwrap();
861 assert_eq!(decoded, hdr);
862 }
863
864 #[test]
865 fn empty_data_block() {
866 let dblk = FixedArrayDataBlock::new_unfiltered(0x500, 0);
867 let encoded = dblk.encode_unfiltered(&ctx8());
868 let decoded = FixedArrayDataBlock::decode_unfiltered(&encoded, &ctx8(), 0).unwrap();
869 assert!(decoded.elements.is_empty());
870 }
871
872 #[test]
875 fn header_paging_geometry() {
876 let mut hdr = FixedArrayHeader::new_for_chunks(&ctx8(), 10);
878 hdr.max_dblk_page_nelmts_bits = 2;
879 assert_eq!(hdr.dblk_page_nelmts(), 4);
880 assert!(hdr.is_paged()); assert_eq!(hdr.npages(), 3); hdr.num_elmts = 4;
885 assert!(!hdr.is_paged());
886 assert_eq!(hdr.npages(), 0);
887
888 hdr.num_elmts = 5;
890 assert!(hdr.is_paged());
891 assert_eq!(hdr.npages(), 2);
892 }
893
894 fn build_paged_prefix(
896 ctx: &FormatContext,
897 client_id: u8,
898 header_addr: u64,
899 npages: usize,
900 init_bits: &[bool],
901 ) -> Vec<u8> {
902 let sa = ctx.sizeof_addr as usize;
903 let bitmap_size = npages.div_ceil(8);
904 let mut bitmap = vec![0u8; bitmap_size];
905 for (p, &on) in init_bits.iter().enumerate() {
906 if on {
907 bitmap[p / 8] |= 0x80u8 >> (p % 8);
908 }
909 }
910 let mut buf = Vec::new();
911 buf.extend_from_slice(&FADB_SIGNATURE);
912 buf.push(FA_VERSION);
913 buf.push(client_id);
914 buf.extend_from_slice(&header_addr.to_le_bytes()[..sa]);
915 buf.extend_from_slice(&bitmap);
916 let cksum = checksum_metadata(&buf);
917 buf.extend_from_slice(&cksum.to_le_bytes());
918 buf
919 }
920
921 fn build_unfiltered_page(ctx: &FormatContext, addrs: &[u64]) -> Vec<u8> {
923 let sa = ctx.sizeof_addr as usize;
924 let mut buf = Vec::new();
925 for &a in addrs {
926 buf.extend_from_slice(&a.to_le_bytes()[..sa]);
927 }
928 let cksum = checksum_metadata(&buf);
929 buf.extend_from_slice(&cksum.to_le_bytes());
930 buf
931 }
932
933 fn build_filtered_page(
935 ctx: &FormatContext,
936 chunk_size_len: usize,
937 elems: &[FixedArrayFilteredChunkElement],
938 ) -> Vec<u8> {
939 let sa = ctx.sizeof_addr as usize;
940 let mut buf = Vec::new();
941 for e in elems {
942 buf.extend_from_slice(&e.address.to_le_bytes()[..sa]);
943 buf.extend_from_slice(&(e.chunk_size as u64).to_le_bytes()[..chunk_size_len]);
944 buf.extend_from_slice(&e.filter_mask.to_le_bytes());
945 }
946 let cksum = checksum_metadata(&buf);
947 buf.extend_from_slice(&cksum.to_le_bytes());
948 buf
949 }
950
951 #[test]
952 fn paged_prefix_roundtrip_and_bitmap() {
953 let ctx = ctx8();
954 let mut init = vec![false; 11];
956 for &p in &[0usize, 7, 8, 10] {
957 init[p] = true;
958 }
959 let buf = build_paged_prefix(&ctx, FA_CLIENT_CHUNK, 0xABCD, 11, &init);
960
961 let prefix = FixedArrayPagedPrefix::decode(&buf, &ctx, 11).unwrap();
962 assert_eq!(prefix.client_id, FA_CLIENT_CHUNK);
963 assert_eq!(prefix.header_addr, 0xABCD);
964 assert_eq!(prefix.prefix_size, buf.len());
965 assert_eq!(prefix.prefix_size, 20);
967 for (p, &expected) in init.iter().enumerate() {
968 assert_eq!(prefix.page_initialized(p), expected, "page {p}");
969 }
970 }
971
972 #[test]
973 fn paged_prefix_bad_checksum() {
974 let ctx = ctx8();
975 let mut buf = build_paged_prefix(&ctx, FA_CLIENT_CHUNK, 0x1000, 3, &[true, true, true]);
976 buf[6] ^= 0xFF;
977 let err = FixedArrayPagedPrefix::decode(&buf, &ctx, 3).unwrap_err();
978 assert!(matches!(err, FormatError::ChecksumMismatch { .. }));
979 }
980
981 #[test]
982 fn unfiltered_page_roundtrip() {
983 let ctx = ctx8();
984 let addrs = [0x100u64, UNDEF_ADDR, 0x300, 0x400];
985 let page = build_unfiltered_page(&ctx, &addrs);
986 let decoded = decode_unfiltered_page(&page, &ctx, 4).unwrap();
987 assert_eq!(decoded, addrs);
988 }
989
990 #[test]
991 fn unfiltered_page_bad_checksum() {
992 let ctx = ctx8();
993 let mut page = build_unfiltered_page(&ctx, &[0x100u64, 0x200]);
994 page[0] ^= 0xFF;
995 let err = decode_unfiltered_page(&page, &ctx, 2).unwrap_err();
996 assert!(matches!(err, FormatError::ChecksumMismatch { .. }));
997 }
998
999 #[test]
1000 fn filtered_page_roundtrip() {
1001 let ctx = ctx8();
1002 let csl = 4;
1003 let elems = vec![
1004 FixedArrayFilteredChunkElement {
1005 address: 0x2000,
1006 chunk_size: 321,
1007 filter_mask: 0,
1008 },
1009 FixedArrayFilteredChunkElement {
1010 address: 0x3000,
1011 chunk_size: 654,
1012 filter_mask: 2,
1013 },
1014 ];
1015 let page = build_filtered_page(&ctx, csl, &elems);
1016 let decoded = decode_filtered_page(&page, &ctx, 2, csl).unwrap();
1017 assert_eq!(decoded, elems);
1018 }
1019
1020 #[test]
1021 fn page_too_short_errors() {
1022 let ctx = ctx8();
1023 let page = build_unfiltered_page(&ctx, &[0x100u64, 0x200]);
1024 let err = decode_unfiltered_page(&page, &ctx, 4).unwrap_err();
1026 assert!(matches!(err, FormatError::BufferTooShort { .. }));
1027 }
1028
1029 #[test]
1030 fn paged_prefix_encode_decode_roundtrip() {
1031 let ctx = ctx8();
1032 let npages = 17usize;
1034 let mut bitmap = vec![0u8; npages.div_ceil(8)];
1035 for &p in &[0usize, 8, 15, 16] {
1036 bitmap[p / 8] |= 0x80u8 >> (p % 8);
1037 }
1038 let prefix = FixedArrayPagedPrefix {
1039 client_id: FA_CLIENT_CHUNK,
1040 header_addr: 0xDEAD_BEEF,
1041 page_init_bitmap: bitmap.clone(),
1042 prefix_size: 4 + 1 + 1 + 8 + bitmap.len() + 4,
1043 };
1044 let encoded = prefix.encode(&ctx);
1045 assert_eq!(encoded.len(), prefix.prefix_size);
1046 assert_eq!(&encoded[..4], b"FADB");
1047
1048 let decoded = FixedArrayPagedPrefix::decode(&encoded, &ctx, npages as u64).unwrap();
1049 assert_eq!(decoded.client_id, FA_CLIENT_CHUNK);
1050 assert_eq!(decoded.header_addr, 0xDEAD_BEEF);
1051 assert_eq!(decoded.page_init_bitmap, bitmap);
1052 assert_eq!(decoded.prefix_size, prefix.prefix_size);
1053 for p in 0..npages {
1054 let expected = [0usize, 8, 15, 16].contains(&p);
1055 assert_eq!(decoded.page_initialized(p), expected, "page {p}");
1056 }
1057 }
1058
1059 #[test]
1060 fn unfiltered_page_encode_decode_roundtrip() {
1061 let ctx = ctx8();
1062 let addrs = [0x1000u64, 0x2000, UNDEF_ADDR, 0x4000];
1063 let encoded = encode_unfiltered_page(&addrs, &ctx);
1064 assert_eq!(encoded.len(), addrs.len() * 8 + 4);
1065 let decoded = decode_unfiltered_page(&encoded, &ctx, addrs.len()).unwrap();
1066 assert_eq!(decoded, addrs);
1067 }
1068
1069 #[test]
1070 fn filtered_page_encode_decode_roundtrip() {
1071 let ctx = ctx8();
1072 let csl = 4;
1073 let elems = vec![
1074 FixedArrayFilteredChunkElement {
1075 address: 0x5000,
1076 chunk_size: 123,
1077 filter_mask: 0,
1078 },
1079 FixedArrayFilteredChunkElement {
1080 address: 0x6000,
1081 chunk_size: 456,
1082 filter_mask: 1,
1083 },
1084 ];
1085 let encoded = encode_filtered_page(&elems, &ctx, csl);
1086 assert_eq!(encoded.len(), elems.len() * (8 + csl + 4) + 4);
1087 let decoded = decode_filtered_page(&encoded, &ctx, elems.len(), csl).unwrap();
1088 assert_eq!(decoded, elems);
1089 }
1090
1091 #[test]
1092 fn paged_prefix_encode_matches_test_builder() {
1093 let ctx = ctx8();
1094 let npages = 11usize;
1095 let mut init = vec![false; npages];
1096 for &p in &[0usize, 7, 8, 10] {
1097 init[p] = true;
1098 }
1099 let from_builder = build_paged_prefix(&ctx, FA_CLIENT_CHUNK, 0xABCD, npages, &init);
1100
1101 let mut bitmap = vec![0u8; npages.div_ceil(8)];
1102 for (p, &on) in init.iter().enumerate() {
1103 if on {
1104 bitmap[p / 8] |= 0x80u8 >> (p % 8);
1105 }
1106 }
1107 let prefix = FixedArrayPagedPrefix {
1108 client_id: FA_CLIENT_CHUNK,
1109 header_addr: 0xABCD,
1110 page_init_bitmap: bitmap.clone(),
1111 prefix_size: 4 + 1 + 1 + 8 + bitmap.len() + 4,
1112 };
1113 assert_eq!(prefix.encode(&ctx), from_builder);
1114 }
1115}