1use crate::format::bytes::{read_le_addr as read_addr, read_le_uint as read_size};
50use crate::format::{FormatContext, FormatError, FormatResult, UNDEF_ADDR};
51
52const VERSION_1: u8 = 1;
57const VERSION_2: u8 = 2;
58const VERSION_3: u8 = 3;
59const VERSION_4: u8 = 4;
60const VERSION_5: u8 = 5;
65
66pub const LAYOUT_VERSION_DEFAULT: u8 = VERSION_3;
72
73const CLASS_COMPACT: u8 = 0;
74const CLASS_CONTIGUOUS: u8 = 1;
75const CLASS_CHUNKED: u8 = 2;
76const CLASS_VIRTUAL: u8 = 3;
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80#[repr(u8)]
81pub enum ChunkIndexType {
82 SingleChunk = 1,
83 Implicit = 2,
84 FixedArray = 3,
85 ExtensibleArray = 4,
86 BTreeV2 = 5,
87}
88
89impl ChunkIndexType {
90 pub fn from_u8(v: u8) -> Option<Self> {
91 match v {
92 1 => Some(Self::SingleChunk),
93 2 => Some(Self::Implicit),
94 3 => Some(Self::FixedArray),
95 4 => Some(Self::ExtensibleArray),
96 5 => Some(Self::BTreeV2),
97 _ => None,
98 }
99 }
100}
101
102#[derive(Debug, Clone, PartialEq, Eq)]
104pub struct EarrayParams {
105 pub max_nelmts_bits: u8,
106 pub idx_blk_elmts: u8,
107 pub sup_blk_min_data_ptrs: u8,
108 pub data_blk_min_elmts: u8,
109 pub max_dblk_page_nelmts_bits: u8,
110}
111
112impl EarrayParams {
113 pub fn default_params() -> Self {
115 Self {
116 max_nelmts_bits: 32,
117 idx_blk_elmts: 4,
118 sup_blk_min_data_ptrs: 4,
119 data_blk_min_elmts: 16,
120 max_dblk_page_nelmts_bits: 10,
121 }
122 }
123}
124
125#[derive(Debug, Clone, PartialEq, Eq)]
127pub struct FixedArrayParams {
128 pub max_dblk_page_nelmts_bits: u8,
129}
130
131impl FixedArrayParams {
132 pub fn default_params() -> Self {
133 Self {
134 max_dblk_page_nelmts_bits: 10,
138 }
139 }
140}
141
142#[derive(Debug, Clone, PartialEq, Eq)]
148pub struct Bt2Params {
149 pub node_size: u32,
150 pub split_percent: u8,
151 pub merge_percent: u8,
152}
153
154impl Bt2Params {
155 pub fn default_params() -> Self {
159 use crate::format::chunk_index::btree_v2::{
160 BT2_MERGE_PERCENT, BT2_NODE_SIZE, BT2_SPLIT_PERCENT,
161 };
162 Self {
163 node_size: BT2_NODE_SIZE,
164 split_percent: BT2_SPLIT_PERCENT,
165 merge_percent: BT2_MERGE_PERCENT,
166 }
167 }
168}
169
170#[derive(Debug, Clone, Copy, PartialEq, Eq)]
179pub struct SingleChunkFilter {
180 pub nbytes: u64,
182 pub filter_mask: u32,
185}
186
187#[derive(Debug, Clone, PartialEq)]
189pub enum DataLayoutMessage {
190 Contiguous {
192 address: u64,
194 size: u64,
196 },
197 Compact {
199 data: Vec<u8>,
201 },
202 ChunkedV3 {
207 chunk_dims: Vec<u64>,
210 b_tree_address: u64,
212 },
213 ChunkedV4 {
216 version: u8,
223 flags: u8,
224 chunk_dims: Vec<u64>,
226 index_type: ChunkIndexType,
228 earray_params: Option<EarrayParams>,
230 farray_params: Option<FixedArrayParams>,
232 bt2_params: Option<Bt2Params>,
234 single_chunk_filter: Option<SingleChunkFilter>,
237 index_address: u64,
239 },
240 Virtual {
245 version: u8,
248 heap_address: u64,
250 heap_index: u32,
254 },
255}
256
257impl DataLayoutMessage {
258 pub fn describe(&self) -> &'static str {
261 match self {
262 Self::Contiguous { .. } => "contiguous",
263 Self::Compact { .. } => "compact (version 3)",
264 Self::ChunkedV3 { .. } => "chunked, version-1 B-tree index (layout version 3)",
265 Self::ChunkedV4 { .. } => "chunked (layout version 4 or 5)",
266 Self::Virtual { .. } => "virtual",
267 }
268 }
269
270 pub fn contiguous_unallocated(size: u64) -> Self {
272 Self::Contiguous {
273 address: UNDEF_ADDR,
274 size,
275 }
276 }
277
278 pub fn contiguous(address: u64, size: u64) -> Self {
280 Self::Contiguous { address, size }
281 }
282
283 pub fn compact(data: Vec<u8>) -> Self {
285 Self::Compact { data }
286 }
287
288 pub fn chunked_v3_btree_v1(chunk_dims: Vec<u64>, b_tree_address: u64) -> Self {
292 Self::ChunkedV3 {
293 chunk_dims,
294 b_tree_address,
295 }
296 }
297
298 pub fn chunked_v4_earray(
304 version: u8,
305 chunk_dims: Vec<u64>,
306 earray_params: EarrayParams,
307 index_address: u64,
308 ) -> Self {
309 Self::ChunkedV4 {
310 version,
311 flags: 0,
312 chunk_dims,
313 index_type: ChunkIndexType::ExtensibleArray,
314 earray_params: Some(earray_params),
315 farray_params: None,
316 bt2_params: None,
317 single_chunk_filter: None,
318 index_address,
319 }
320 }
321
322 pub fn chunked_v4_farray(
326 version: u8,
327 chunk_dims: Vec<u64>,
328 farray_params: FixedArrayParams,
329 index_address: u64,
330 ) -> Self {
331 Self::ChunkedV4 {
332 version,
333 flags: 0,
334 chunk_dims,
335 index_type: ChunkIndexType::FixedArray,
336 earray_params: None,
337 farray_params: Some(farray_params),
338 bt2_params: None,
339 single_chunk_filter: None,
340 index_address,
341 }
342 }
343
344 pub fn chunked_v4_btree_v2(
348 version: u8,
349 chunk_dims: Vec<u64>,
350 bt2_params: Bt2Params,
351 index_address: u64,
352 ) -> Self {
353 Self::ChunkedV4 {
354 version,
355 flags: 0,
356 chunk_dims,
357 index_type: ChunkIndexType::BTreeV2,
358 earray_params: None,
359 farray_params: None,
360 bt2_params: Some(bt2_params),
361 single_chunk_filter: None,
362 index_address,
363 }
364 }
365
366 pub fn chunked_v4_implicit(version: u8, chunk_dims: Vec<u64>, index_address: u64) -> Self {
373 Self::ChunkedV4 {
374 version,
375 flags: 0,
376 chunk_dims,
377 index_type: ChunkIndexType::Implicit,
378 earray_params: None,
379 farray_params: None,
380 bt2_params: None,
381 single_chunk_filter: None,
382 index_address,
383 }
384 }
385
386 pub fn virtual_layout(version: u8, heap_address: u64, heap_index: u32) -> Self {
388 Self::Virtual {
389 version,
390 heap_address,
391 heap_index,
392 }
393 }
394
395 pub fn chunked_v4_single(chunk_dims: Vec<u64>, index_address: u64) -> Self {
399 Self::ChunkedV4 {
400 version: VERSION_4,
401 flags: 0,
402 chunk_dims,
403 index_type: ChunkIndexType::SingleChunk,
404 earray_params: None,
405 farray_params: None,
406 bt2_params: None,
407 single_chunk_filter: None,
408 index_address,
409 }
410 }
411
412 pub fn chunked_v4_single_filtered(
419 chunk_dims: Vec<u64>,
420 index_address: u64,
421 nbytes: u64,
422 filter_mask: u32,
423 ) -> Self {
424 Self::ChunkedV4 {
425 version: VERSION_4,
426 flags: 0x02,
427 chunk_dims,
428 index_type: ChunkIndexType::SingleChunk,
429 earray_params: None,
430 farray_params: None,
431 bt2_params: None,
432 single_chunk_filter: Some(SingleChunkFilter {
433 nbytes,
434 filter_mask,
435 }),
436 index_address,
437 }
438 }
439
440 pub fn encode(&self, ctx: &FormatContext) -> Vec<u8> {
443 match self {
444 Self::Contiguous { address, size } => {
445 let sa = ctx.sizeof_addr as usize;
446 let ss = ctx.sizeof_size as usize;
447 let mut buf = Vec::with_capacity(2 + sa + ss);
448 buf.push(VERSION_3);
449 buf.push(CLASS_CONTIGUOUS);
450 buf.extend_from_slice(&address.to_le_bytes()[..sa]);
451 buf.extend_from_slice(&size.to_le_bytes()[..ss]);
452 buf
453 }
454 Self::Compact { data } => {
455 let mut buf = Vec::with_capacity(2 + 2 + data.len());
456 buf.push(VERSION_3);
457 buf.push(CLASS_COMPACT);
458 buf.extend_from_slice(&(data.len() as u16).to_le_bytes());
459 buf.extend_from_slice(data);
460 buf
461 }
462 Self::ChunkedV3 {
463 chunk_dims,
464 b_tree_address,
465 } => {
466 let sa = ctx.sizeof_addr as usize;
467 let ndims = chunk_dims.len() as u8;
468 let mut buf = Vec::with_capacity(3 + sa + chunk_dims.len() * 4);
469 buf.push(VERSION_3);
470 buf.push(CLASS_CHUNKED);
471 buf.push(ndims);
472 buf.extend_from_slice(&b_tree_address.to_le_bytes()[..sa]);
473 for &d in chunk_dims {
475 buf.extend_from_slice(&(d as u32).to_le_bytes());
476 }
477 buf
478 }
479 Self::ChunkedV4 {
480 version,
481 flags,
482 chunk_dims,
483 index_type,
484 earray_params,
485 farray_params,
486 bt2_params,
487 single_chunk_filter,
488 index_address,
489 } => {
490 let sa = ctx.sizeof_addr as usize;
491 let ndims = chunk_dims.len() as u8;
492
493 let max_dim = chunk_dims.iter().copied().max().unwrap_or(1);
496 let enc_bytes = enc_bytes_for_value(max_dim);
497
498 debug_assert!(matches!(*version, VERSION_4 | VERSION_5));
499 let mut buf = Vec::with_capacity(64);
500 buf.push(*version);
501 buf.push(CLASS_CHUNKED);
502 buf.push(*flags);
503 buf.push(ndims);
504 buf.push(enc_bytes);
505
506 for &d in chunk_dims {
508 buf.extend_from_slice(&d.to_le_bytes()[..enc_bytes as usize]);
509 }
510
511 buf.push(*index_type as u8);
513
514 match *index_type {
516 ChunkIndexType::ExtensibleArray => {
517 if let Some(ref params) = earray_params {
518 buf.push(params.max_nelmts_bits);
519 buf.push(params.idx_blk_elmts);
520 buf.push(params.sup_blk_min_data_ptrs);
521 buf.push(params.data_blk_min_elmts);
522 buf.push(params.max_dblk_page_nelmts_bits);
523 }
524 }
525 ChunkIndexType::FixedArray => {
526 if let Some(ref params) = farray_params {
527 buf.push(params.max_dblk_page_nelmts_bits);
528 }
529 }
530 ChunkIndexType::BTreeV2 => {
531 if let Some(ref params) = bt2_params {
537 buf.extend_from_slice(¶ms.node_size.to_le_bytes());
538 buf.push(params.split_percent);
539 buf.push(params.merge_percent);
540 }
541 }
542 ChunkIndexType::SingleChunk if *flags & 0x02 != 0 => {
548 if let Some(scf) = single_chunk_filter {
549 let ss = ctx.sizeof_size as usize;
550 buf.extend_from_slice(&scf.nbytes.to_le_bytes()[..ss]);
551 buf.extend_from_slice(&scf.filter_mask.to_le_bytes());
552 }
553 }
554 _ => {}
556 }
557
558 buf.extend_from_slice(&index_address.to_le_bytes()[..sa]);
560
561 buf
562 }
563 Self::Virtual {
564 version,
565 heap_address,
566 heap_index,
567 } => {
568 let sa = ctx.sizeof_addr as usize;
569 debug_assert!(matches!(*version, VERSION_4 | VERSION_5));
570 let mut buf = Vec::with_capacity(2 + sa + 4);
571 buf.push(*version);
572 buf.push(CLASS_VIRTUAL);
573 buf.extend_from_slice(&heap_address.to_le_bytes()[..sa]);
574 buf.extend_from_slice(&heap_index.to_le_bytes());
575 buf
576 }
577 }
578 }
579
580 pub fn decode(buf: &[u8], ctx: &FormatContext) -> FormatResult<(Self, usize)> {
583 if buf.len() < 2 {
584 return Err(FormatError::BufferTooShort {
585 needed: 2,
586 available: buf.len(),
587 });
588 }
589
590 let version = buf[0];
591 let class = buf[1];
592
593 match version {
601 VERSION_1 | VERSION_2 => {
602 return Err(FormatError::UnsupportedFeature(format!(
603 "data layout message version {version}"
604 )))
605 }
606 VERSION_3 | VERSION_4 | VERSION_5 => {}
607 v => return Err(FormatError::InvalidVersion(v)),
608 }
609
610 match class {
611 CLASS_CONTIGUOUS => {
612 let sa = ctx.sizeof_addr as usize;
613 let ss = ctx.sizeof_size as usize;
614 let mut pos = 2;
615 let needed = pos + sa + ss;
616 if buf.len() < needed {
617 return Err(FormatError::BufferTooShort {
618 needed,
619 available: buf.len(),
620 });
621 }
622 let address = read_addr(&buf[pos..], sa);
623 pos += sa;
624 let size = read_size(&buf[pos..], ss);
625 pos += ss;
626 Ok((Self::Contiguous { address, size }, pos))
627 }
628 CLASS_COMPACT => {
629 let mut pos = 2;
630 if buf.len() < pos + 2 {
631 return Err(FormatError::BufferTooShort {
632 needed: pos + 2,
633 available: buf.len(),
634 });
635 }
636 let compact_size = u16::from_le_bytes([buf[pos], buf[pos + 1]]) as usize;
637 pos += 2;
638 if buf.len() < pos + compact_size {
639 return Err(FormatError::BufferTooShort {
640 needed: pos + compact_size,
641 available: buf.len(),
642 });
643 }
644 let data = buf[pos..pos + compact_size].to_vec();
645 pos += compact_size;
646 Ok((Self::Compact { data }, pos))
647 }
648 CLASS_CHUNKED if version == VERSION_3 => {
649 let sa = ctx.sizeof_addr as usize;
652 let mut pos = 2;
653 if buf.len() < pos + 1 {
654 return Err(FormatError::BufferTooShort {
655 needed: pos + 1,
656 available: buf.len(),
657 });
658 }
659 let ndims = buf[pos] as usize;
660 pos += 1;
661
662 if ndims < 2 {
666 return Err(FormatError::InvalidData(format!(
667 "chunked v3 layout dimensionality {ndims} is too small"
668 )));
669 }
670
671 if buf.len() < pos + sa {
672 return Err(FormatError::BufferTooShort {
673 needed: pos + sa,
674 available: buf.len(),
675 });
676 }
677 let b_tree_address = read_addr(&buf[pos..], sa);
678 pos += sa;
679
680 let dim_data_len = ndims * 4;
681 if buf.len() < pos + dim_data_len {
682 return Err(FormatError::BufferTooShort {
683 needed: pos + dim_data_len,
684 available: buf.len(),
685 });
686 }
687 let mut chunk_dims = Vec::with_capacity(ndims);
688 for _ in 0..ndims {
689 let d = u32::from_le_bytes([buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]])
690 as u64;
691 if d == 0 {
692 return Err(FormatError::InvalidData(
693 "chunked v3 layout has a zero chunk dimension".into(),
694 ));
695 }
696 chunk_dims.push(d);
697 pos += 4;
698 }
699
700 Ok((
701 Self::ChunkedV3 {
702 chunk_dims,
703 b_tree_address,
704 },
705 pos,
706 ))
707 }
708 CLASS_CHUNKED => {
709 let sa = ctx.sizeof_addr as usize;
710 let mut pos = 2;
711
712 if buf.len() < pos + 3 {
714 return Err(FormatError::BufferTooShort {
715 needed: pos + 3,
716 available: buf.len(),
717 });
718 }
719 let flags = buf[pos];
720 pos += 1;
721 let ndims = buf[pos] as usize;
722 pos += 1;
723 let enc_bytes = buf[pos] as usize;
724 pos += 1;
725
726 if !(1..=8).contains(&enc_bytes) {
729 return Err(FormatError::InvalidData(format!(
730 "chunked layout encoded dimension size {enc_bytes} is out of range"
731 )));
732 }
733 if ndims < 2 {
736 return Err(FormatError::InvalidData(format!(
737 "chunked v4 layout dimensionality {ndims} is too small"
738 )));
739 }
740
741 let dim_data_len = ndims * enc_bytes;
743 if buf.len() < pos + dim_data_len {
744 return Err(FormatError::BufferTooShort {
745 needed: pos + dim_data_len,
746 available: buf.len(),
747 });
748 }
749 let mut chunk_dims = Vec::with_capacity(ndims);
750 for _ in 0..ndims {
751 let d = read_size(&buf[pos..], enc_bytes);
752 if d == 0 {
753 return Err(FormatError::InvalidData(
754 "chunked v4 layout has a zero chunk dimension".into(),
755 ));
756 }
757 chunk_dims.push(d);
758 pos += enc_bytes;
759 }
760
761 if buf.len() < pos + 1 {
763 return Err(FormatError::BufferTooShort {
764 needed: pos + 1,
765 available: buf.len(),
766 });
767 }
768 let idx_type_raw = buf[pos];
769 pos += 1;
770 let index_type = ChunkIndexType::from_u8(idx_type_raw).ok_or_else(|| {
771 FormatError::UnsupportedFeature(format!("chunk index type {}", idx_type_raw))
772 })?;
773
774 let mut earray_params = None;
776 let mut farray_params = None;
777 let mut bt2_params = None;
778 let mut single_chunk_filter = None;
779
780 match index_type {
781 ChunkIndexType::ExtensibleArray => {
782 if buf.len() < pos + 5 {
783 return Err(FormatError::BufferTooShort {
784 needed: pos + 5,
785 available: buf.len(),
786 });
787 }
788 let ep = EarrayParams {
789 max_nelmts_bits: buf[pos],
790 idx_blk_elmts: buf[pos + 1],
791 sup_blk_min_data_ptrs: buf[pos + 2],
792 data_blk_min_elmts: buf[pos + 3],
793 max_dblk_page_nelmts_bits: buf[pos + 4],
794 };
795 if ep.max_nelmts_bits == 0
797 || ep.idx_blk_elmts == 0
798 || ep.sup_blk_min_data_ptrs == 0
799 || ep.data_blk_min_elmts == 0
800 || ep.max_dblk_page_nelmts_bits == 0
801 {
802 return Err(FormatError::InvalidData(
803 "extensible-array layout parameter is zero".into(),
804 ));
805 }
806 earray_params = Some(ep);
807 pos += 5;
808 }
809 ChunkIndexType::FixedArray => {
810 if buf.len() < pos + 1 {
811 return Err(FormatError::BufferTooShort {
812 needed: pos + 1,
813 available: buf.len(),
814 });
815 }
816 farray_params = Some(FixedArrayParams {
822 max_dblk_page_nelmts_bits: buf[pos],
823 });
824 pos += 1;
825 }
826 ChunkIndexType::BTreeV2 => {
827 if buf.len() < pos + 6 {
832 return Err(FormatError::BufferTooShort {
833 needed: pos + 6,
834 available: buf.len(),
835 });
836 }
837 bt2_params = Some(Bt2Params {
838 node_size: u32::from_le_bytes([
839 buf[pos],
840 buf[pos + 1],
841 buf[pos + 2],
842 buf[pos + 3],
843 ]),
844 split_percent: buf[pos + 4],
845 merge_percent: buf[pos + 5],
846 });
847 pos += 6;
848 }
849 ChunkIndexType::SingleChunk if flags & 0x02 != 0 => {
856 let ss = ctx.sizeof_size as usize;
857 let extra = ss + 4;
858 if buf.len() < pos + extra {
859 return Err(FormatError::BufferTooShort {
860 needed: pos + extra,
861 available: buf.len(),
862 });
863 }
864 let nbytes = read_size(&buf[pos..], ss);
865 pos += ss;
866 let filter_mask = u32::from_le_bytes([
867 buf[pos],
868 buf[pos + 1],
869 buf[pos + 2],
870 buf[pos + 3],
871 ]);
872 pos += 4;
873 single_chunk_filter = Some(SingleChunkFilter {
874 nbytes,
875 filter_mask,
876 });
877 }
878 _ => {}
881 }
882
883 if buf.len() < pos + sa {
885 return Err(FormatError::BufferTooShort {
886 needed: pos + sa,
887 available: buf.len(),
888 });
889 }
890 let index_address = read_addr(&buf[pos..], sa);
891 pos += sa;
892
893 Ok((
894 Self::ChunkedV4 {
895 version: buf[0],
896 flags,
897 chunk_dims,
898 index_type,
899 earray_params,
900 farray_params,
901 bt2_params,
902 single_chunk_filter,
903 index_address,
904 },
905 pos,
906 ))
907 }
908 CLASS_VIRTUAL => {
909 if version == VERSION_3 {
914 return Err(FormatError::InvalidVersion(VERSION_3));
915 }
916 let sa = ctx.sizeof_addr as usize;
917 let mut pos = 2;
918 if buf.len() < pos + sa {
919 return Err(FormatError::BufferTooShort {
920 needed: pos + sa,
921 available: buf.len(),
922 });
923 }
924 let heap_address = read_addr(&buf[pos..], sa);
925 pos += sa;
926
927 if buf.len() < pos + 4 {
928 return Err(FormatError::BufferTooShort {
929 needed: pos + 4,
930 available: buf.len(),
931 });
932 }
933 let heap_index =
934 u32::from_le_bytes([buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]]);
935 pos += 4;
936
937 Ok((
938 Self::Virtual {
939 version: buf[0],
940 heap_address,
941 heap_index,
942 },
943 pos,
944 ))
945 }
946 other => Err(FormatError::UnsupportedFeature(format!(
947 "data layout class {}",
948 other
949 ))),
950 }
951 }
952}
953
954fn enc_bytes_for_value(v: u64) -> u8 {
958 if v == 0 {
959 return 1;
960 }
961 let bits_needed = 64 - v.leading_zeros(); bits_needed.div_ceil(8) as u8
963}
964
965#[cfg(test)]
968mod tests {
969 use super::*;
970
971 fn ctx8() -> FormatContext {
972 FormatContext {
973 sizeof_addr: 8,
974 sizeof_size: 8,
975 }
976 }
977
978 fn ctx4() -> FormatContext {
979 FormatContext {
980 sizeof_addr: 4,
981 sizeof_size: 4,
982 }
983 }
984
985 #[test]
986 fn roundtrip_contiguous() {
987 let msg = DataLayoutMessage::contiguous(0x1000, 4096);
988 let encoded = msg.encode(&ctx8());
989 assert_eq!(encoded.len(), 18);
991 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
992 assert_eq!(consumed, 18);
993 assert_eq!(decoded, msg);
994 }
995
996 #[test]
997 fn roundtrip_contiguous_ctx4() {
998 let msg = DataLayoutMessage::contiguous(0x800, 256);
999 let encoded = msg.encode(&ctx4());
1000 assert_eq!(encoded.len(), 10);
1002 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx4()).unwrap();
1003 assert_eq!(consumed, 10);
1004 assert_eq!(decoded, msg);
1005 }
1006
1007 #[test]
1008 fn roundtrip_contiguous_unallocated() {
1009 let msg = DataLayoutMessage::contiguous_unallocated(1024);
1010 let encoded = msg.encode(&ctx8());
1011 let (decoded, _) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
1012 assert_eq!(decoded, msg);
1013 match decoded {
1014 DataLayoutMessage::Contiguous { address, size } => {
1015 assert_eq!(address, UNDEF_ADDR);
1016 assert_eq!(size, 1024);
1017 }
1018 _ => panic!("expected Contiguous"),
1019 }
1020 }
1021
1022 #[test]
1023 fn roundtrip_contiguous_undef_ctx4() {
1024 let msg = DataLayoutMessage::contiguous_unallocated(512);
1025 let encoded = msg.encode(&ctx4());
1026 let (decoded, _) = DataLayoutMessage::decode(&encoded, &ctx4()).unwrap();
1027 match decoded {
1028 DataLayoutMessage::Contiguous { address, .. } => {
1029 assert_eq!(address, UNDEF_ADDR);
1030 }
1031 _ => panic!("expected Contiguous"),
1032 }
1033 }
1034
1035 #[test]
1036 fn roundtrip_compact() {
1037 let data = vec![1, 2, 3, 4, 5, 6, 7, 8];
1038 let msg = DataLayoutMessage::compact(data.clone());
1039 let encoded = msg.encode(&ctx8());
1040 assert_eq!(encoded.len(), 12);
1042 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
1043 assert_eq!(consumed, 12);
1044 assert_eq!(decoded, msg);
1045 }
1046
1047 #[test]
1048 fn roundtrip_compact_empty() {
1049 let msg = DataLayoutMessage::compact(vec![]);
1050 let encoded = msg.encode(&ctx8());
1051 assert_eq!(encoded.len(), 4); let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
1053 assert_eq!(consumed, 4);
1054 assert_eq!(decoded, msg);
1055 }
1056
1057 #[test]
1061 fn decode_legacy_version_is_unsupported_not_invalid() {
1062 for version in [1u8, 2] {
1063 let mut buf = vec![version, 1];
1064 buf.extend_from_slice(&[0u8; 16]);
1065 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
1066 match err {
1067 FormatError::UnsupportedFeature(ref s) => {
1068 assert!(s.contains(&version.to_string()), "{s}")
1069 }
1070 other => panic!("unexpected error for version {version}: {other:?}"),
1071 }
1072 }
1073 }
1074
1075 #[test]
1076 fn decode_bad_version() {
1077 for version in [0u8, 6, 255] {
1078 let mut buf = vec![version, 1];
1079 buf.extend_from_slice(&[0u8; 16]);
1080 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
1081 match err {
1082 FormatError::InvalidVersion(v) if v == version => {}
1083 other => panic!("unexpected error for version {version}: {other:?}"),
1084 }
1085 }
1086 }
1087
1088 #[test]
1093 fn decode_contiguous_and_compact_at_every_modern_version() {
1094 for version in [3u8, 4, 5] {
1095 let mut contig = vec![version, CLASS_CONTIGUOUS];
1096 contig.extend_from_slice(&0x800u64.to_le_bytes());
1097 contig.extend_from_slice(&64u64.to_le_bytes());
1098 let (decoded, consumed) = DataLayoutMessage::decode(&contig, &ctx8()).unwrap();
1099 assert_eq!(consumed, contig.len());
1100 assert_eq!(
1101 decoded,
1102 DataLayoutMessage::Contiguous {
1103 address: 0x800,
1104 size: 64
1105 }
1106 );
1107
1108 let payload = [1u8, 2, 3, 4];
1109 let mut compact = vec![version, CLASS_COMPACT];
1110 compact.extend_from_slice(&(payload.len() as u16).to_le_bytes());
1111 compact.extend_from_slice(&payload);
1112 let (decoded, consumed) = DataLayoutMessage::decode(&compact, &ctx8()).unwrap();
1113 assert_eq!(consumed, compact.len());
1114 assert_eq!(
1115 decoded,
1116 DataLayoutMessage::Compact {
1117 data: payload.to_vec()
1118 }
1119 );
1120 }
1121 }
1122
1123 #[test]
1124 fn decode_unsupported_class() {
1125 let buf = [3u8, 4]; let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
1127 match err {
1128 FormatError::UnsupportedFeature(_) => {}
1129 other => panic!("unexpected error: {:?}", other),
1130 }
1131 }
1132
1133 #[test]
1134 fn decode_buffer_too_short() {
1135 let buf = [3u8];
1136 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
1137 match err {
1138 FormatError::BufferTooShort { .. } => {}
1139 other => panic!("unexpected error: {:?}", other),
1140 }
1141 }
1142
1143 #[test]
1144 fn decode_contiguous_truncated() {
1145 let buf = [3u8, 1, 0, 0];
1147 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
1148 match err {
1149 FormatError::BufferTooShort { .. } => {}
1150 other => panic!("unexpected error: {:?}", other),
1151 }
1152 }
1153
1154 #[test]
1155 fn version_and_class_bytes() {
1156 let encoded = DataLayoutMessage::contiguous(0, 0).encode(&ctx8());
1157 assert_eq!(encoded[0], 3);
1158 assert_eq!(encoded[1], 1);
1159
1160 let encoded = DataLayoutMessage::compact(vec![]).encode(&ctx8());
1161 assert_eq!(encoded[0], 3);
1162 assert_eq!(encoded[1], 0);
1163 }
1164
1165 #[test]
1166 fn roundtrip_chunked_v4_earray() {
1167 let params = EarrayParams::default_params();
1168 let msg = DataLayoutMessage::chunked_v4_earray(4, vec![1, 256, 256], params, 0x2000);
1169 let encoded = msg.encode(&ctx8());
1170 assert_eq!(encoded[0], 4); assert_eq!(encoded[1], 2); let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
1173 assert_eq!(consumed, encoded.len());
1174 assert_eq!(decoded, msg);
1175 }
1176
1177 #[test]
1178 fn roundtrip_chunked_v4_earray_ctx4() {
1179 let params = EarrayParams::default_params();
1180 let msg = DataLayoutMessage::chunked_v4_earray(4, vec![1, 128], params, 0x1000);
1181 let encoded = msg.encode(&ctx4());
1182 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx4()).unwrap();
1183 assert_eq!(consumed, encoded.len());
1184 assert_eq!(decoded, msg);
1185 }
1186
1187 #[test]
1192 fn roundtrip_chunked_v5_earray() {
1193 let params = EarrayParams::default_params();
1194 let v5 = DataLayoutMessage::chunked_v4_earray(5, vec![1, 256, 256], params.clone(), 0x2000);
1195 let encoded = v5.encode(&ctx8());
1196 assert_eq!(encoded[0], 5); assert_eq!(encoded[1], 2); let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
1199 assert_eq!(consumed, encoded.len());
1200 assert_eq!(decoded, v5);
1201
1202 let v4 = DataLayoutMessage::chunked_v4_earray(4, vec![1, 256, 256], params, 0x2000);
1204 let encoded_v4 = v4.encode(&ctx8());
1205 assert_eq!(encoded[1..], encoded_v4[1..]);
1206 }
1207
1208 #[test]
1209 fn roundtrip_chunked_v4_single() {
1210 let msg = DataLayoutMessage::chunked_v4_single(vec![100, 200], 0x3000);
1211 let encoded = msg.encode(&ctx8());
1212 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
1213 assert_eq!(consumed, encoded.len());
1214 assert_eq!(decoded, msg);
1215 }
1216
1217 #[test]
1222 fn roundtrip_chunked_v4_btree_v2_params() {
1223 for ctx in [ctx8(), ctx4()] {
1224 let msg = DataLayoutMessage::chunked_v4_btree_v2(
1225 4,
1226 vec![2, 2, 8],
1227 Bt2Params {
1228 node_size: 512,
1229 split_percent: 90,
1230 merge_percent: 30,
1231 },
1232 0x2000,
1233 );
1234 let encoded = msg.encode(&ctx);
1235 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx).unwrap();
1236 assert_eq!(consumed, encoded.len());
1237 assert_eq!(decoded, msg);
1238 }
1239 }
1240
1241 #[test]
1246 fn roundtrip_chunked_v4_single_filtered() {
1247 for ctx in [ctx8(), ctx4()] {
1248 let msg = DataLayoutMessage::ChunkedV4 {
1249 version: 4,
1250 flags: 0x02,
1251 chunk_dims: vec![100, 200, 4],
1252 index_type: ChunkIndexType::SingleChunk,
1253 earray_params: None,
1254 farray_params: None,
1255 bt2_params: None,
1256 single_chunk_filter: Some(SingleChunkFilter {
1257 nbytes: 12345,
1258 filter_mask: 0b101,
1259 }),
1260 index_address: 0x3000,
1261 };
1262 let encoded = msg.encode(&ctx);
1263 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx).unwrap();
1264 assert_eq!(consumed, encoded.len());
1265 assert_eq!(decoded, msg);
1266 match decoded {
1268 DataLayoutMessage::ChunkedV4 {
1269 single_chunk_filter: Some(scf),
1270 ..
1271 } => {
1272 assert_eq!(scf.nbytes, 12345);
1273 assert_eq!(scf.filter_mask, 0b101);
1274 }
1275 other => panic!("expected filtered single-chunk layout, got {other:?}"),
1276 }
1277 }
1278 }
1279
1280 #[test]
1281 fn chunked_v4_enc_bytes() {
1282 let params = EarrayParams::default_params();
1284 let msg = DataLayoutMessage::chunked_v4_earray(4, vec![1, 256, 256], params, 0x2000);
1285 let encoded = msg.encode(&ctx8());
1286 assert_eq!(encoded.len(), 25);
1289 assert_eq!(encoded[4], 2); }
1291
1292 #[test]
1293 fn roundtrip_chunked_v3_btree_v1() {
1294 let msg = DataLayoutMessage::chunked_v3_btree_v1(vec![8, 4], 0x1234);
1296 let encoded = msg.encode(&ctx8());
1297 assert_eq!(encoded.len(), 19);
1299 assert_eq!(encoded[0], 3);
1300 assert_eq!(encoded[1], 2);
1301 assert_eq!(encoded[2], 2); let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
1303 assert_eq!(consumed, encoded.len());
1304 assert_eq!(decoded, msg);
1305 }
1306
1307 #[test]
1308 fn roundtrip_chunked_v3_btree_v1_2d_ctx4() {
1309 let msg = DataLayoutMessage::chunked_v3_btree_v1(vec![2, 3, 8], 0x800);
1311 let encoded = msg.encode(&ctx4());
1312 assert_eq!(encoded.len(), 19);
1314 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx4()).unwrap();
1315 assert_eq!(consumed, encoded.len());
1316 assert_eq!(decoded, msg);
1317 }
1318
1319 #[test]
1320 fn chunked_v3_undef_btree_addr() {
1321 let msg = DataLayoutMessage::chunked_v3_btree_v1(vec![16, 4], UNDEF_ADDR);
1322 let encoded = msg.encode(&ctx8());
1323 let (decoded, _) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
1324 match decoded {
1325 DataLayoutMessage::ChunkedV3 { b_tree_address, .. } => {
1326 assert_eq!(b_tree_address, UNDEF_ADDR);
1327 }
1328 _ => panic!("expected ChunkedV3"),
1329 }
1330 }
1331
1332 #[test]
1333 fn chunked_v3_rejects_ndims_too_small() {
1334 let buf = [3u8, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
1336 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
1337 assert!(matches!(err, FormatError::InvalidData(_)));
1338 }
1339
1340 #[test]
1341 fn chunked_v3_rejects_zero_dim() {
1342 let mut buf = vec![3u8, 2, 2];
1344 buf.extend_from_slice(&0u64.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&4u32.to_le_bytes()); let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
1348 assert!(matches!(err, FormatError::InvalidData(_)));
1349 }
1350
1351 #[test]
1352 fn chunked_v3_truncated() {
1353 let buf = [3u8, 2, 2];
1355 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
1356 assert!(matches!(err, FormatError::BufferTooShort { .. }));
1357 }
1358
1359 #[test]
1360 fn roundtrip_virtual_layout() {
1361 for ctx in [ctx8(), ctx4()] {
1362 for version in [4u8, 5u8] {
1363 let msg = DataLayoutMessage::virtual_layout(version, 0x5000, 3);
1364 let encoded = msg.encode(&ctx);
1365 assert_eq!(encoded[0], version);
1366 assert_eq!(encoded[1], CLASS_VIRTUAL);
1367 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx).unwrap();
1368 assert_eq!(consumed, encoded.len());
1369 assert_eq!(decoded, msg);
1370 }
1371 }
1372 }
1373
1374 #[test]
1375 fn virtual_layout_undefined_heap_address() {
1376 let msg = DataLayoutMessage::virtual_layout(4, UNDEF_ADDR, 0);
1379 let encoded = msg.encode(&ctx8());
1380 let (decoded, _) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
1381 match decoded {
1382 DataLayoutMessage::Virtual {
1383 heap_address,
1384 heap_index,
1385 ..
1386 } => {
1387 assert_eq!(heap_address, UNDEF_ADDR);
1388 assert_eq!(heap_index, 0);
1389 }
1390 other => panic!("expected Virtual, got {other:?}"),
1391 }
1392 }
1393
1394 #[test]
1398 fn virtual_layout_rejects_version_3() {
1399 let buf = [VERSION_3, CLASS_VIRTUAL];
1400 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
1401 assert!(matches!(err, FormatError::InvalidVersion(VERSION_3)));
1402 }
1403
1404 #[test]
1405 fn chunked_v4_large_dims() {
1406 let params = EarrayParams::default_params();
1408 let msg = DataLayoutMessage::chunked_v4_earray(4, vec![1, 65536], params, 0x4000);
1409 let encoded = msg.encode(&ctx8());
1410 assert_eq!(encoded[4], 3); }
1412}