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: u64,
224 pub filter_mask: u32,
225}
226
227#[derive(Debug, Clone, PartialEq)]
238pub struct FixedArrayDataBlock {
239 pub client_id: u8,
240 pub header_addr: u64,
241 pub elements: Vec<u64>,
243 pub filtered_elements: Vec<FixedArrayFilteredChunkElement>,
245}
246
247impl FixedArrayDataBlock {
248 pub fn new_unfiltered(header_addr: u64, num_elmts: usize) -> Self {
250 Self {
251 client_id: FA_CLIENT_CHUNK,
252 header_addr,
253 elements: vec![UNDEF_ADDR; num_elmts],
254 filtered_elements: Vec::new(),
255 }
256 }
257
258 pub fn new_filtered(header_addr: u64, num_elmts: usize) -> Self {
260 let default_entry = FixedArrayFilteredChunkElement {
261 address: UNDEF_ADDR,
262 chunk_size: 0,
263 filter_mask: 0,
264 };
265 Self {
266 client_id: FA_CLIENT_FILT_CHUNK,
267 header_addr,
268 elements: Vec::new(),
269 filtered_elements: vec![default_entry; num_elmts],
270 }
271 }
272
273 pub fn encoded_size_unfiltered(&self, ctx: &FormatContext) -> usize {
275 let sa = ctx.sizeof_addr as usize;
276 4 + 1 + 1 + sa + self.elements.len() * sa + 4
281 }
282
283 pub fn encoded_size_filtered(&self, ctx: &FormatContext, chunk_size_len: usize) -> usize {
285 let sa = ctx.sizeof_addr as usize;
286 let elem_size = sa + chunk_size_len + 4; 4 + 1 + 1 + sa + self.filtered_elements.len() * elem_size + 4
292 }
293
294 pub fn encode_unfiltered(&self, ctx: &FormatContext) -> Vec<u8> {
296 let sa = ctx.sizeof_addr as usize;
297 let size = self.encoded_size_unfiltered(ctx);
298 let mut buf = Vec::with_capacity(size);
299
300 buf.extend_from_slice(&FADB_SIGNATURE);
301 buf.push(FA_VERSION);
302 buf.push(self.client_id);
303 buf.extend_from_slice(&self.header_addr.to_le_bytes()[..sa]);
304
305 for &addr in &self.elements {
306 buf.extend_from_slice(&addr.to_le_bytes()[..sa]);
307 }
308
309 let cksum = checksum_metadata(&buf);
310 buf.extend_from_slice(&cksum.to_le_bytes());
311
312 debug_assert_eq!(buf.len(), size);
313 buf
314 }
315
316 pub fn encode_filtered(&self, ctx: &FormatContext, chunk_size_len: usize) -> Vec<u8> {
318 let sa = ctx.sizeof_addr as usize;
319 let size = self.encoded_size_filtered(ctx, chunk_size_len);
320 let mut buf = Vec::with_capacity(size);
321
322 buf.extend_from_slice(&FADB_SIGNATURE);
323 buf.push(FA_VERSION);
324 buf.push(self.client_id);
325 buf.extend_from_slice(&self.header_addr.to_le_bytes()[..sa]);
326
327 for elem in &self.filtered_elements {
328 buf.extend_from_slice(&elem.address.to_le_bytes()[..sa]);
329 buf.extend_from_slice(&elem.chunk_size.to_le_bytes()[..chunk_size_len]);
330 buf.extend_from_slice(&elem.filter_mask.to_le_bytes());
331 }
332
333 let cksum = checksum_metadata(&buf);
334 buf.extend_from_slice(&cksum.to_le_bytes());
335
336 debug_assert_eq!(buf.len(), size);
337 buf
338 }
339
340 pub fn decode_unfiltered(
342 buf: &[u8],
343 ctx: &FormatContext,
344 num_elmts: usize,
345 ) -> FormatResult<Self> {
346 let sa = ctx.sizeof_addr as usize;
347 let min_size = num_elmts.saturating_mul(sa).saturating_add(10 + sa);
350
351 if buf.len() < min_size {
352 return Err(FormatError::BufferTooShort {
353 needed: min_size,
354 available: buf.len(),
355 });
356 }
357
358 if buf[0..4] != FADB_SIGNATURE {
359 return Err(FormatError::InvalidSignature);
360 }
361
362 let version = buf[4];
363 if version != FA_VERSION {
364 return Err(FormatError::InvalidVersion(version));
365 }
366
367 let data_end = min_size - 4;
369 let stored_cksum = u32::from_le_bytes([
370 buf[data_end],
371 buf[data_end + 1],
372 buf[data_end + 2],
373 buf[data_end + 3],
374 ]);
375 let computed_cksum = checksum_metadata(&buf[..data_end]);
376 if stored_cksum != computed_cksum {
377 return Err(FormatError::ChecksumMismatch {
378 expected: stored_cksum,
379 computed: computed_cksum,
380 });
381 }
382
383 let client_id = buf[5];
384 let mut pos = 6;
385 let header_addr = read_addr(&buf[pos..], sa);
386 pos += sa;
387
388 let mut elements = Vec::with_capacity(num_elmts);
389 for _ in 0..num_elmts {
390 elements.push(read_addr(&buf[pos..], sa));
391 pos += sa;
392 }
393
394 Ok(Self {
395 client_id,
396 header_addr,
397 elements,
398 filtered_elements: Vec::new(),
399 })
400 }
401
402 pub fn decode_filtered(
404 buf: &[u8],
405 ctx: &FormatContext,
406 num_elmts: usize,
407 chunk_size_len: usize,
408 ) -> FormatResult<Self> {
409 let sa = ctx.sizeof_addr as usize;
410 let elem_size = sa + chunk_size_len + 4;
411 let min_size = num_elmts.saturating_mul(elem_size).saturating_add(10 + sa);
412
413 if buf.len() < min_size {
414 return Err(FormatError::BufferTooShort {
415 needed: min_size,
416 available: buf.len(),
417 });
418 }
419
420 if buf[0..4] != FADB_SIGNATURE {
421 return Err(FormatError::InvalidSignature);
422 }
423
424 let version = buf[4];
425 if version != FA_VERSION {
426 return Err(FormatError::InvalidVersion(version));
427 }
428
429 let data_end = min_size - 4;
431 let stored_cksum = u32::from_le_bytes([
432 buf[data_end],
433 buf[data_end + 1],
434 buf[data_end + 2],
435 buf[data_end + 3],
436 ]);
437 let computed_cksum = checksum_metadata(&buf[..data_end]);
438 if stored_cksum != computed_cksum {
439 return Err(FormatError::ChecksumMismatch {
440 expected: stored_cksum,
441 computed: computed_cksum,
442 });
443 }
444
445 let client_id = buf[5];
446 let mut pos = 6;
447 let header_addr = read_addr(&buf[pos..], sa);
448 pos += sa;
449
450 let mut filtered_elements = Vec::with_capacity(num_elmts);
451 for _ in 0..num_elmts {
452 let address = read_addr(&buf[pos..], sa);
453 pos += sa;
454 let chunk_size = read_size(&buf[pos..], chunk_size_len);
455 pos += chunk_size_len;
456 let filter_mask =
457 u32::from_le_bytes([buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]]);
458 pos += 4;
459 filtered_elements.push(FixedArrayFilteredChunkElement {
460 address,
461 chunk_size,
462 filter_mask,
463 });
464 }
465
466 Ok(Self {
467 client_id,
468 header_addr,
469 elements: Vec::new(),
470 filtered_elements,
471 })
472 }
473}
474
475#[derive(Debug, Clone, PartialEq)]
487pub struct FixedArrayPagedPrefix {
488 pub client_id: u8,
489 pub header_addr: u64,
490 pub page_init_bitmap: Vec<u8>,
493 pub prefix_size: usize,
496}
497
498impl FixedArrayPagedPrefix {
499 pub fn decode(buf: &[u8], ctx: &FormatContext, npages: u64) -> FormatResult<Self> {
503 let sa = ctx.sizeof_addr as usize;
504 let bitmap_size = (npages as usize).div_ceil(8);
505 let prefix_size = 4 + 1 + 1 + sa + bitmap_size + 4;
508
509 if buf.len() < prefix_size {
510 return Err(FormatError::BufferTooShort {
511 needed: prefix_size,
512 available: buf.len(),
513 });
514 }
515
516 if buf[0..4] != FADB_SIGNATURE {
517 return Err(FormatError::InvalidSignature);
518 }
519
520 let version = buf[4];
521 if version != FA_VERSION {
522 return Err(FormatError::InvalidVersion(version));
523 }
524
525 let data_end = prefix_size - 4;
527 let stored_cksum = u32::from_le_bytes([
528 buf[data_end],
529 buf[data_end + 1],
530 buf[data_end + 2],
531 buf[data_end + 3],
532 ]);
533 let computed_cksum = checksum_metadata(&buf[..data_end]);
534 if stored_cksum != computed_cksum {
535 return Err(FormatError::ChecksumMismatch {
536 expected: stored_cksum,
537 computed: computed_cksum,
538 });
539 }
540
541 let client_id = buf[5];
542 let mut pos = 6;
543 let header_addr = read_addr(&buf[pos..], sa);
544 pos += sa;
545 let page_init_bitmap = buf[pos..pos + bitmap_size].to_vec();
546
547 Ok(Self {
548 client_id,
549 header_addr,
550 page_init_bitmap,
551 prefix_size,
552 })
553 }
554
555 pub fn page_initialized(&self, p: usize) -> bool {
559 let byte = p / 8;
560 if byte >= self.page_init_bitmap.len() {
561 return false;
562 }
563 (self.page_init_bitmap[byte] & (0x80u8 >> (p % 8))) != 0
564 }
565
566 pub fn encode(&self, ctx: &FormatContext) -> Vec<u8> {
572 let sa = ctx.sizeof_addr as usize;
573 let mut buf = Vec::with_capacity(4 + 1 + 1 + sa + self.page_init_bitmap.len() + 4);
574 buf.extend_from_slice(&FADB_SIGNATURE);
575 buf.push(FA_VERSION);
576 buf.push(self.client_id);
577 buf.extend_from_slice(&self.header_addr.to_le_bytes()[..sa]);
578 buf.extend_from_slice(&self.page_init_bitmap);
579 let cksum = checksum_metadata(&buf);
580 buf.extend_from_slice(&cksum.to_le_bytes());
581 buf
582 }
583}
584
585pub fn encode_unfiltered_page(addrs: &[u64], ctx: &FormatContext) -> Vec<u8> {
591 let sa = ctx.sizeof_addr as usize;
592 let mut buf = Vec::with_capacity(addrs.len() * sa + 4);
593 for &addr in addrs {
594 buf.extend_from_slice(&addr.to_le_bytes()[..sa]);
595 }
596 let cksum = checksum_metadata(&buf);
597 buf.extend_from_slice(&cksum.to_le_bytes());
598 buf
599}
600
601pub fn encode_filtered_page(
606 elems: &[FixedArrayFilteredChunkElement],
607 ctx: &FormatContext,
608 chunk_size_len: usize,
609) -> Vec<u8> {
610 let sa = ctx.sizeof_addr as usize;
611 let elem_size = sa + chunk_size_len + 4;
612 let mut buf = Vec::with_capacity(elems.len() * elem_size + 4);
613 for e in elems {
614 buf.extend_from_slice(&e.address.to_le_bytes()[..sa]);
615 buf.extend_from_slice(&e.chunk_size.to_le_bytes()[..chunk_size_len]);
616 buf.extend_from_slice(&e.filter_mask.to_le_bytes());
617 }
618 let cksum = checksum_metadata(&buf);
619 buf.extend_from_slice(&cksum.to_le_bytes());
620 buf
621}
622
623pub fn decode_unfiltered_page(
629 page_buf: &[u8],
630 ctx: &FormatContext,
631 nelmts: usize,
632) -> FormatResult<Vec<u64>> {
633 let sa = ctx.sizeof_addr as usize;
634 let page_size = nelmts.saturating_mul(sa).saturating_add(4);
637 if page_buf.len() < page_size {
638 return Err(FormatError::BufferTooShort {
639 needed: page_size,
640 available: page_buf.len(),
641 });
642 }
643
644 let data_end = page_size - 4;
645 let stored_cksum = u32::from_le_bytes([
646 page_buf[data_end],
647 page_buf[data_end + 1],
648 page_buf[data_end + 2],
649 page_buf[data_end + 3],
650 ]);
651 let computed_cksum = checksum_metadata(&page_buf[..data_end]);
652 if stored_cksum != computed_cksum {
653 return Err(FormatError::ChecksumMismatch {
654 expected: stored_cksum,
655 computed: computed_cksum,
656 });
657 }
658
659 let mut elements = Vec::with_capacity(nelmts);
660 let mut pos = 0;
661 for _ in 0..nelmts {
662 elements.push(read_addr(&page_buf[pos..], sa));
663 pos += sa;
664 }
665 Ok(elements)
666}
667
668pub fn decode_filtered_page(
674 page_buf: &[u8],
675 ctx: &FormatContext,
676 nelmts: usize,
677 chunk_size_len: usize,
678) -> FormatResult<Vec<FixedArrayFilteredChunkElement>> {
679 let sa = ctx.sizeof_addr as usize;
680 let elem_size = sa + chunk_size_len + 4;
681 let page_size = nelmts.saturating_mul(elem_size).saturating_add(4);
684 if page_buf.len() < page_size {
685 return Err(FormatError::BufferTooShort {
686 needed: page_size,
687 available: page_buf.len(),
688 });
689 }
690
691 let data_end = page_size - 4;
692 let stored_cksum = u32::from_le_bytes([
693 page_buf[data_end],
694 page_buf[data_end + 1],
695 page_buf[data_end + 2],
696 page_buf[data_end + 3],
697 ]);
698 let computed_cksum = checksum_metadata(&page_buf[..data_end]);
699 if stored_cksum != computed_cksum {
700 return Err(FormatError::ChecksumMismatch {
701 expected: stored_cksum,
702 computed: computed_cksum,
703 });
704 }
705
706 let mut elements = Vec::with_capacity(nelmts);
707 let mut pos = 0;
708 for _ in 0..nelmts {
709 let address = read_addr(&page_buf[pos..], sa);
710 pos += sa;
711 let chunk_size = read_size(&page_buf[pos..], chunk_size_len);
712 pos += chunk_size_len;
713 let filter_mask = u32::from_le_bytes([
714 page_buf[pos],
715 page_buf[pos + 1],
716 page_buf[pos + 2],
717 page_buf[pos + 3],
718 ]);
719 pos += 4;
720 elements.push(FixedArrayFilteredChunkElement {
721 address,
722 chunk_size,
723 filter_mask,
724 });
725 }
726 Ok(elements)
727}
728
729#[cfg(test)]
734mod tests {
735 use super::*;
736
737 fn ctx8() -> FormatContext {
738 FormatContext {
739 sizeof_addr: 8,
740 sizeof_size: 8,
741 }
742 }
743
744 fn ctx4() -> FormatContext {
745 FormatContext {
746 sizeof_addr: 4,
747 sizeof_size: 4,
748 }
749 }
750
751 #[test]
752 fn header_roundtrip() {
753 let mut hdr = FixedArrayHeader::new_for_chunks(&ctx8(), 10);
754 hdr.data_blk_addr = 0x2000;
755
756 let encoded = hdr.encode(&ctx8());
757 assert_eq!(encoded.len(), hdr.encoded_size(&ctx8()));
758 assert_eq!(&encoded[..4], b"FAHD");
759
760 let decoded = FixedArrayHeader::decode(&encoded, &ctx8()).unwrap();
761 assert_eq!(decoded, hdr);
762 }
763
764 #[test]
765 fn header_roundtrip_ctx4() {
766 let mut hdr = FixedArrayHeader::new_for_chunks(&ctx4(), 5);
767 hdr.data_blk_addr = 0x800;
768
769 let encoded = hdr.encode(&ctx4());
770 let decoded = FixedArrayHeader::decode(&encoded, &ctx4()).unwrap();
771 assert_eq!(decoded, hdr);
772 }
773
774 #[test]
775 fn header_bad_signature() {
776 let hdr = FixedArrayHeader::new_for_chunks(&ctx8(), 10);
777 let mut encoded = hdr.encode(&ctx8());
778 encoded[0] = b'X';
779 let err = FixedArrayHeader::decode(&encoded, &ctx8()).unwrap_err();
780 assert!(matches!(err, FormatError::InvalidSignature));
781 }
782
783 #[test]
784 fn header_checksum_mismatch() {
785 let hdr = FixedArrayHeader::new_for_chunks(&ctx8(), 10);
786 let mut encoded = hdr.encode(&ctx8());
787 encoded[6] ^= 0xFF;
788 let err = FixedArrayHeader::decode(&encoded, &ctx8()).unwrap_err();
789 assert!(matches!(err, FormatError::ChecksumMismatch { .. }));
790 }
791
792 #[test]
793 fn data_block_unfiltered_roundtrip() {
794 let mut dblk = FixedArrayDataBlock::new_unfiltered(0x1000, 4);
795 dblk.elements[0] = 0x3000;
796 dblk.elements[1] = 0x4000;
797 dblk.elements[2] = UNDEF_ADDR;
798 dblk.elements[3] = 0x5000;
799
800 let encoded = dblk.encode_unfiltered(&ctx8());
801 assert_eq!(encoded.len(), dblk.encoded_size_unfiltered(&ctx8()));
802 assert_eq!(&encoded[..4], b"FADB");
803
804 let decoded = FixedArrayDataBlock::decode_unfiltered(&encoded, &ctx8(), 4).unwrap();
805 assert_eq!(decoded.elements, dblk.elements);
806 assert_eq!(decoded.header_addr, 0x1000);
807 }
808
809 #[test]
810 fn data_block_unfiltered_roundtrip_ctx4() {
811 let mut dblk = FixedArrayDataBlock::new_unfiltered(0x500, 3);
812 dblk.elements[0] = 0x100;
813 dblk.elements[1] = 0x200;
814 dblk.elements[2] = 0x300;
815
816 let encoded = dblk.encode_unfiltered(&ctx4());
817 let decoded = FixedArrayDataBlock::decode_unfiltered(&encoded, &ctx4(), 3).unwrap();
818 assert_eq!(decoded.elements, dblk.elements);
819 }
820
821 #[test]
822 fn data_block_unfiltered_bad_checksum() {
823 let dblk = FixedArrayDataBlock::new_unfiltered(0x1000, 2);
824 let mut encoded = dblk.encode_unfiltered(&ctx8());
825 encoded[8] ^= 0xFF;
826 let err = FixedArrayDataBlock::decode_unfiltered(&encoded, &ctx8(), 2).unwrap_err();
827 assert!(matches!(err, FormatError::ChecksumMismatch { .. }));
828 }
829
830 #[test]
831 fn data_block_filtered_roundtrip() {
832 let mut dblk = FixedArrayDataBlock::new_filtered(0x1000, 2);
833 dblk.filtered_elements[0] = FixedArrayFilteredChunkElement {
834 address: 0x2000,
835 chunk_size: 512,
836 filter_mask: 0,
837 };
838 dblk.filtered_elements[1] = FixedArrayFilteredChunkElement {
839 address: 0x3000,
840 chunk_size: 400,
841 filter_mask: 1,
842 };
843
844 let chunk_size_len = 4; let encoded = dblk.encode_filtered(&ctx8(), chunk_size_len);
846 assert_eq!(
847 encoded.len(),
848 dblk.encoded_size_filtered(&ctx8(), chunk_size_len)
849 );
850
851 let decoded =
852 FixedArrayDataBlock::decode_filtered(&encoded, &ctx8(), 2, chunk_size_len).unwrap();
853 assert_eq!(decoded.filtered_elements, dblk.filtered_elements);
854 }
855
856 #[test]
857 fn header_filtered_roundtrip() {
858 let hdr = FixedArrayHeader::new_for_filtered_chunks(&ctx8(), 6, 4);
859 assert_eq!(hdr.element_size, 8 + 4 + 4); assert_eq!(hdr.client_id, FA_CLIENT_FILT_CHUNK);
861
862 let encoded = hdr.encode(&ctx8());
863 let decoded = FixedArrayHeader::decode(&encoded, &ctx8()).unwrap();
864 assert_eq!(decoded, hdr);
865 }
866
867 #[test]
868 fn empty_data_block() {
869 let dblk = FixedArrayDataBlock::new_unfiltered(0x500, 0);
870 let encoded = dblk.encode_unfiltered(&ctx8());
871 let decoded = FixedArrayDataBlock::decode_unfiltered(&encoded, &ctx8(), 0).unwrap();
872 assert!(decoded.elements.is_empty());
873 }
874
875 #[test]
878 fn header_paging_geometry() {
879 let mut hdr = FixedArrayHeader::new_for_chunks(&ctx8(), 10);
881 hdr.max_dblk_page_nelmts_bits = 2;
882 assert_eq!(hdr.dblk_page_nelmts(), 4);
883 assert!(hdr.is_paged()); assert_eq!(hdr.npages(), 3); hdr.num_elmts = 4;
888 assert!(!hdr.is_paged());
889 assert_eq!(hdr.npages(), 0);
890
891 hdr.num_elmts = 5;
893 assert!(hdr.is_paged());
894 assert_eq!(hdr.npages(), 2);
895 }
896
897 fn build_paged_prefix(
899 ctx: &FormatContext,
900 client_id: u8,
901 header_addr: u64,
902 npages: usize,
903 init_bits: &[bool],
904 ) -> Vec<u8> {
905 let sa = ctx.sizeof_addr as usize;
906 let bitmap_size = npages.div_ceil(8);
907 let mut bitmap = vec![0u8; bitmap_size];
908 for (p, &on) in init_bits.iter().enumerate() {
909 if on {
910 bitmap[p / 8] |= 0x80u8 >> (p % 8);
911 }
912 }
913 let mut buf = Vec::new();
914 buf.extend_from_slice(&FADB_SIGNATURE);
915 buf.push(FA_VERSION);
916 buf.push(client_id);
917 buf.extend_from_slice(&header_addr.to_le_bytes()[..sa]);
918 buf.extend_from_slice(&bitmap);
919 let cksum = checksum_metadata(&buf);
920 buf.extend_from_slice(&cksum.to_le_bytes());
921 buf
922 }
923
924 fn build_unfiltered_page(ctx: &FormatContext, addrs: &[u64]) -> Vec<u8> {
926 let sa = ctx.sizeof_addr as usize;
927 let mut buf = Vec::new();
928 for &a in addrs {
929 buf.extend_from_slice(&a.to_le_bytes()[..sa]);
930 }
931 let cksum = checksum_metadata(&buf);
932 buf.extend_from_slice(&cksum.to_le_bytes());
933 buf
934 }
935
936 fn build_filtered_page(
938 ctx: &FormatContext,
939 chunk_size_len: usize,
940 elems: &[FixedArrayFilteredChunkElement],
941 ) -> Vec<u8> {
942 let sa = ctx.sizeof_addr as usize;
943 let mut buf = Vec::new();
944 for e in elems {
945 buf.extend_from_slice(&e.address.to_le_bytes()[..sa]);
946 buf.extend_from_slice(&e.chunk_size.to_le_bytes()[..chunk_size_len]);
947 buf.extend_from_slice(&e.filter_mask.to_le_bytes());
948 }
949 let cksum = checksum_metadata(&buf);
950 buf.extend_from_slice(&cksum.to_le_bytes());
951 buf
952 }
953
954 #[test]
955 fn paged_prefix_roundtrip_and_bitmap() {
956 let ctx = ctx8();
957 let mut init = vec![false; 11];
959 for &p in &[0usize, 7, 8, 10] {
960 init[p] = true;
961 }
962 let buf = build_paged_prefix(&ctx, FA_CLIENT_CHUNK, 0xABCD, 11, &init);
963
964 let prefix = FixedArrayPagedPrefix::decode(&buf, &ctx, 11).unwrap();
965 assert_eq!(prefix.client_id, FA_CLIENT_CHUNK);
966 assert_eq!(prefix.header_addr, 0xABCD);
967 assert_eq!(prefix.prefix_size, buf.len());
968 assert_eq!(prefix.prefix_size, 20);
970 for (p, &expected) in init.iter().enumerate() {
971 assert_eq!(prefix.page_initialized(p), expected, "page {p}");
972 }
973 }
974
975 #[test]
976 fn paged_prefix_bad_checksum() {
977 let ctx = ctx8();
978 let mut buf = build_paged_prefix(&ctx, FA_CLIENT_CHUNK, 0x1000, 3, &[true, true, true]);
979 buf[6] ^= 0xFF;
980 let err = FixedArrayPagedPrefix::decode(&buf, &ctx, 3).unwrap_err();
981 assert!(matches!(err, FormatError::ChecksumMismatch { .. }));
982 }
983
984 #[test]
985 fn unfiltered_page_roundtrip() {
986 let ctx = ctx8();
987 let addrs = [0x100u64, UNDEF_ADDR, 0x300, 0x400];
988 let page = build_unfiltered_page(&ctx, &addrs);
989 let decoded = decode_unfiltered_page(&page, &ctx, 4).unwrap();
990 assert_eq!(decoded, addrs);
991 }
992
993 #[test]
994 fn unfiltered_page_bad_checksum() {
995 let ctx = ctx8();
996 let mut page = build_unfiltered_page(&ctx, &[0x100u64, 0x200]);
997 page[0] ^= 0xFF;
998 let err = decode_unfiltered_page(&page, &ctx, 2).unwrap_err();
999 assert!(matches!(err, FormatError::ChecksumMismatch { .. }));
1000 }
1001
1002 #[test]
1003 fn filtered_page_roundtrip() {
1004 let ctx = ctx8();
1005 let csl = 4;
1006 let elems = vec![
1007 FixedArrayFilteredChunkElement {
1008 address: 0x2000,
1009 chunk_size: 321,
1010 filter_mask: 0,
1011 },
1012 FixedArrayFilteredChunkElement {
1013 address: 0x3000,
1014 chunk_size: 654,
1015 filter_mask: 2,
1016 },
1017 ];
1018 let page = build_filtered_page(&ctx, csl, &elems);
1019 let decoded = decode_filtered_page(&page, &ctx, 2, csl).unwrap();
1020 assert_eq!(decoded, elems);
1021 }
1022
1023 #[test]
1024 fn page_too_short_errors() {
1025 let ctx = ctx8();
1026 let page = build_unfiltered_page(&ctx, &[0x100u64, 0x200]);
1027 let err = decode_unfiltered_page(&page, &ctx, 4).unwrap_err();
1029 assert!(matches!(err, FormatError::BufferTooShort { .. }));
1030 }
1031
1032 #[test]
1033 fn paged_prefix_encode_decode_roundtrip() {
1034 let ctx = ctx8();
1035 let npages = 17usize;
1037 let mut bitmap = vec![0u8; npages.div_ceil(8)];
1038 for &p in &[0usize, 8, 15, 16] {
1039 bitmap[p / 8] |= 0x80u8 >> (p % 8);
1040 }
1041 let prefix = FixedArrayPagedPrefix {
1042 client_id: FA_CLIENT_CHUNK,
1043 header_addr: 0xDEAD_BEEF,
1044 page_init_bitmap: bitmap.clone(),
1045 prefix_size: 4 + 1 + 1 + 8 + bitmap.len() + 4,
1046 };
1047 let encoded = prefix.encode(&ctx);
1048 assert_eq!(encoded.len(), prefix.prefix_size);
1049 assert_eq!(&encoded[..4], b"FADB");
1050
1051 let decoded = FixedArrayPagedPrefix::decode(&encoded, &ctx, npages as u64).unwrap();
1052 assert_eq!(decoded.client_id, FA_CLIENT_CHUNK);
1053 assert_eq!(decoded.header_addr, 0xDEAD_BEEF);
1054 assert_eq!(decoded.page_init_bitmap, bitmap);
1055 assert_eq!(decoded.prefix_size, prefix.prefix_size);
1056 for p in 0..npages {
1057 let expected = [0usize, 8, 15, 16].contains(&p);
1058 assert_eq!(decoded.page_initialized(p), expected, "page {p}");
1059 }
1060 }
1061
1062 #[test]
1063 fn unfiltered_page_encode_decode_roundtrip() {
1064 let ctx = ctx8();
1065 let addrs = [0x1000u64, 0x2000, UNDEF_ADDR, 0x4000];
1066 let encoded = encode_unfiltered_page(&addrs, &ctx);
1067 assert_eq!(encoded.len(), addrs.len() * 8 + 4);
1068 let decoded = decode_unfiltered_page(&encoded, &ctx, addrs.len()).unwrap();
1069 assert_eq!(decoded, addrs);
1070 }
1071
1072 #[test]
1073 fn filtered_page_encode_decode_roundtrip() {
1074 let ctx = ctx8();
1075 let csl = 4;
1076 let elems = vec![
1077 FixedArrayFilteredChunkElement {
1078 address: 0x5000,
1079 chunk_size: 123,
1080 filter_mask: 0,
1081 },
1082 FixedArrayFilteredChunkElement {
1083 address: 0x6000,
1084 chunk_size: 456,
1085 filter_mask: 1,
1086 },
1087 ];
1088 let encoded = encode_filtered_page(&elems, &ctx, csl);
1089 assert_eq!(encoded.len(), elems.len() * (8 + csl + 4) + 4);
1090 let decoded = decode_filtered_page(&encoded, &ctx, elems.len(), csl).unwrap();
1091 assert_eq!(decoded, elems);
1092 }
1093
1094 #[test]
1095 fn paged_prefix_encode_matches_test_builder() {
1096 let ctx = ctx8();
1097 let npages = 11usize;
1098 let mut init = vec![false; npages];
1099 for &p in &[0usize, 7, 8, 10] {
1100 init[p] = true;
1101 }
1102 let from_builder = build_paged_prefix(&ctx, FA_CLIENT_CHUNK, 0xABCD, npages, &init);
1103
1104 let mut bitmap = vec![0u8; npages.div_ceil(8)];
1105 for (p, &on) in init.iter().enumerate() {
1106 if on {
1107 bitmap[p / 8] |= 0x80u8 >> (p % 8);
1108 }
1109 }
1110 let prefix = FixedArrayPagedPrefix {
1111 client_id: FA_CLIENT_CHUNK,
1112 header_addr: 0xABCD,
1113 page_init_bitmap: bitmap.clone(),
1114 prefix_size: 4 + 1 + 1 + 8 + bitmap.len() + 4,
1115 };
1116 assert_eq!(prefix.encode(&ctx), from_builder);
1117 }
1118}