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 check_against_dataset(
275 &self,
276 dataspace: &crate::format::messages::dataspace::DataspaceMessage,
277 datatype: &crate::format::messages::datatype::DatatypeMessage,
278 ctx: &FormatContext,
279 ) -> FormatResult<()> {
280 match self {
281 Self::ChunkedV3 { chunk_dims, .. } | Self::ChunkedV4 { chunk_dims, .. } => {
282 let rank = dataspace.dims.len();
283 if chunk_dims.len() != rank + 1 {
284 return Err(FormatError::InvalidData(format!(
285 "chunk dimensionality {} over a rank-{rank} dataspace; the chunk rank \
286 plus the element-size dimension must be {}",
287 chunk_dims.len(),
288 rank + 1
289 )));
290 }
291 Ok(())
292 }
293 Self::Compact { data } => {
294 let dt_size = datatype.element_size_ctx(ctx) as u64;
295 let nelmts = dataspace.element_count();
296 let Some(expected) = nelmts.and_then(|n| n.checked_mul(dt_size)) else {
297 return Err(FormatError::InvalidData(
298 "the size of the dataset's compact storage overflows".into(),
299 ));
300 };
301 if data.len() as u64 != expected {
302 return Err(FormatError::InvalidData(format!(
303 "compact storage holds {} bytes but the dataset's {} elements of \
304 {dt_size} bytes need {expected}",
305 data.len(),
306 nelmts.unwrap_or(0)
307 )));
308 }
309 Ok(())
310 }
311 Self::Contiguous { .. } | Self::Virtual { .. } => Ok(()),
312 }
313 }
314
315 pub fn describe(&self) -> &'static str {
318 match self {
319 Self::Contiguous { .. } => "contiguous",
320 Self::Compact { .. } => "compact (version 3)",
321 Self::ChunkedV3 { .. } => "chunked, version-1 B-tree index (layout version 3)",
322 Self::ChunkedV4 { .. } => "chunked (layout version 4 or 5)",
323 Self::Virtual { .. } => "virtual",
324 }
325 }
326
327 pub fn contiguous_unallocated(size: u64) -> Self {
329 Self::Contiguous {
330 address: UNDEF_ADDR,
331 size,
332 }
333 }
334
335 pub fn contiguous(address: u64, size: u64) -> Self {
337 Self::Contiguous { address, size }
338 }
339
340 pub fn compact(data: Vec<u8>) -> Self {
342 Self::Compact { data }
343 }
344
345 pub fn chunked_v3_btree_v1(chunk_dims: Vec<u64>, b_tree_address: u64) -> Self {
349 Self::ChunkedV3 {
350 chunk_dims,
351 b_tree_address,
352 }
353 }
354
355 pub fn chunked_v4_earray(
361 version: u8,
362 chunk_dims: Vec<u64>,
363 earray_params: EarrayParams,
364 index_address: u64,
365 ) -> Self {
366 Self::ChunkedV4 {
367 version,
368 flags: 0,
369 chunk_dims,
370 index_type: ChunkIndexType::ExtensibleArray,
371 earray_params: Some(earray_params),
372 farray_params: None,
373 bt2_params: None,
374 single_chunk_filter: None,
375 index_address,
376 }
377 }
378
379 pub fn chunked_v4_farray(
383 version: u8,
384 chunk_dims: Vec<u64>,
385 farray_params: FixedArrayParams,
386 index_address: u64,
387 ) -> Self {
388 Self::ChunkedV4 {
389 version,
390 flags: 0,
391 chunk_dims,
392 index_type: ChunkIndexType::FixedArray,
393 earray_params: None,
394 farray_params: Some(farray_params),
395 bt2_params: None,
396 single_chunk_filter: None,
397 index_address,
398 }
399 }
400
401 pub fn chunked_v4_btree_v2(
405 version: u8,
406 chunk_dims: Vec<u64>,
407 bt2_params: Bt2Params,
408 index_address: u64,
409 ) -> Self {
410 Self::ChunkedV4 {
411 version,
412 flags: 0,
413 chunk_dims,
414 index_type: ChunkIndexType::BTreeV2,
415 earray_params: None,
416 farray_params: None,
417 bt2_params: Some(bt2_params),
418 single_chunk_filter: None,
419 index_address,
420 }
421 }
422
423 pub fn chunked_v4_implicit(version: u8, chunk_dims: Vec<u64>, index_address: u64) -> Self {
430 Self::ChunkedV4 {
431 version,
432 flags: 0,
433 chunk_dims,
434 index_type: ChunkIndexType::Implicit,
435 earray_params: None,
436 farray_params: None,
437 bt2_params: None,
438 single_chunk_filter: None,
439 index_address,
440 }
441 }
442
443 pub fn virtual_layout(version: u8, heap_address: u64, heap_index: u32) -> Self {
445 Self::Virtual {
446 version,
447 heap_address,
448 heap_index,
449 }
450 }
451
452 pub fn chunked_v4_single(chunk_dims: Vec<u64>, index_address: u64) -> Self {
456 Self::ChunkedV4 {
457 version: VERSION_4,
458 flags: 0,
459 chunk_dims,
460 index_type: ChunkIndexType::SingleChunk,
461 earray_params: None,
462 farray_params: None,
463 bt2_params: None,
464 single_chunk_filter: None,
465 index_address,
466 }
467 }
468
469 pub fn chunked_v4_single_filtered(
476 chunk_dims: Vec<u64>,
477 index_address: u64,
478 nbytes: u64,
479 filter_mask: u32,
480 ) -> Self {
481 Self::ChunkedV4 {
482 version: VERSION_4,
483 flags: 0x02,
484 chunk_dims,
485 index_type: ChunkIndexType::SingleChunk,
486 earray_params: None,
487 farray_params: None,
488 bt2_params: None,
489 single_chunk_filter: Some(SingleChunkFilter {
490 nbytes,
491 filter_mask,
492 }),
493 index_address,
494 }
495 }
496
497 pub fn encode(&self, ctx: &FormatContext) -> Vec<u8> {
500 match self {
501 Self::Contiguous { address, size } => {
502 let sa = ctx.sizeof_addr as usize;
503 let ss = ctx.sizeof_size as usize;
504 let mut buf = Vec::with_capacity(2 + sa + ss);
505 buf.push(VERSION_3);
506 buf.push(CLASS_CONTIGUOUS);
507 buf.extend_from_slice(&address.to_le_bytes()[..sa]);
508 buf.extend_from_slice(&size.to_le_bytes()[..ss]);
509 buf
510 }
511 Self::Compact { data } => {
512 let mut buf = Vec::with_capacity(2 + 2 + data.len());
513 buf.push(VERSION_3);
514 buf.push(CLASS_COMPACT);
515 buf.extend_from_slice(&(data.len() as u16).to_le_bytes());
516 buf.extend_from_slice(data);
517 buf
518 }
519 Self::ChunkedV3 {
520 chunk_dims,
521 b_tree_address,
522 } => {
523 let sa = ctx.sizeof_addr as usize;
524 let ndims = chunk_dims.len() as u8;
525 let mut buf = Vec::with_capacity(3 + sa + chunk_dims.len() * 4);
526 buf.push(VERSION_3);
527 buf.push(CLASS_CHUNKED);
528 buf.push(ndims);
529 buf.extend_from_slice(&b_tree_address.to_le_bytes()[..sa]);
530 for &d in chunk_dims {
532 buf.extend_from_slice(&(d as u32).to_le_bytes());
533 }
534 buf
535 }
536 Self::ChunkedV4 {
537 version,
538 flags,
539 chunk_dims,
540 index_type,
541 earray_params,
542 farray_params,
543 bt2_params,
544 single_chunk_filter,
545 index_address,
546 } => {
547 let sa = ctx.sizeof_addr as usize;
548 let ndims = chunk_dims.len() as u8;
549
550 let max_dim = chunk_dims.iter().copied().max().unwrap_or(1);
553 let enc_bytes = enc_bytes_for_value(max_dim);
554
555 debug_assert!(matches!(*version, VERSION_4 | VERSION_5));
556 let mut buf = Vec::with_capacity(64);
557 buf.push(*version);
558 buf.push(CLASS_CHUNKED);
559 buf.push(*flags);
560 buf.push(ndims);
561 buf.push(enc_bytes);
562
563 for &d in chunk_dims {
565 buf.extend_from_slice(&d.to_le_bytes()[..enc_bytes as usize]);
566 }
567
568 buf.push(*index_type as u8);
570
571 match *index_type {
573 ChunkIndexType::ExtensibleArray => {
574 if let Some(ref params) = earray_params {
575 buf.push(params.max_nelmts_bits);
576 buf.push(params.idx_blk_elmts);
577 buf.push(params.sup_blk_min_data_ptrs);
578 buf.push(params.data_blk_min_elmts);
579 buf.push(params.max_dblk_page_nelmts_bits);
580 }
581 }
582 ChunkIndexType::FixedArray => {
583 if let Some(ref params) = farray_params {
584 buf.push(params.max_dblk_page_nelmts_bits);
585 }
586 }
587 ChunkIndexType::BTreeV2 => {
588 if let Some(ref params) = bt2_params {
594 buf.extend_from_slice(¶ms.node_size.to_le_bytes());
595 buf.push(params.split_percent);
596 buf.push(params.merge_percent);
597 }
598 }
599 ChunkIndexType::SingleChunk if *flags & 0x02 != 0 => {
605 if let Some(scf) = single_chunk_filter {
606 let ss = ctx.sizeof_size as usize;
607 buf.extend_from_slice(&scf.nbytes.to_le_bytes()[..ss]);
608 buf.extend_from_slice(&scf.filter_mask.to_le_bytes());
609 }
610 }
611 _ => {}
613 }
614
615 buf.extend_from_slice(&index_address.to_le_bytes()[..sa]);
617
618 buf
619 }
620 Self::Virtual {
621 version,
622 heap_address,
623 heap_index,
624 } => {
625 let sa = ctx.sizeof_addr as usize;
626 debug_assert!(matches!(*version, VERSION_4 | VERSION_5));
627 let mut buf = Vec::with_capacity(2 + sa + 4);
628 buf.push(*version);
629 buf.push(CLASS_VIRTUAL);
630 buf.extend_from_slice(&heap_address.to_le_bytes()[..sa]);
631 buf.extend_from_slice(&heap_index.to_le_bytes());
632 buf
633 }
634 }
635 }
636
637 pub fn decode(buf: &[u8], ctx: &FormatContext) -> FormatResult<(Self, usize)> {
640 if buf.len() < 2 {
641 return Err(FormatError::BufferTooShort {
642 needed: 2,
643 available: buf.len(),
644 });
645 }
646
647 let version = buf[0];
648 let class = buf[1];
649
650 match version {
658 VERSION_1 | VERSION_2 => {
659 return Err(FormatError::UnsupportedFeature(format!(
660 "data layout message version {version}"
661 )))
662 }
663 VERSION_3 | VERSION_4 | VERSION_5 => {}
664 v => return Err(FormatError::InvalidVersion(v)),
665 }
666
667 match class {
668 CLASS_CONTIGUOUS => {
669 let sa = ctx.sizeof_addr as usize;
670 let ss = ctx.sizeof_size as usize;
671 let mut pos = 2;
672 let needed = pos + sa + ss;
673 if buf.len() < needed {
674 return Err(FormatError::BufferTooShort {
675 needed,
676 available: buf.len(),
677 });
678 }
679 let address = read_addr(&buf[pos..], sa);
680 pos += sa;
681 let size = read_size(&buf[pos..], ss);
682 pos += ss;
683 Ok((Self::Contiguous { address, size }, pos))
684 }
685 CLASS_COMPACT => {
686 let mut pos = 2;
687 if buf.len() < pos + 2 {
688 return Err(FormatError::BufferTooShort {
689 needed: pos + 2,
690 available: buf.len(),
691 });
692 }
693 let compact_size = u16::from_le_bytes([buf[pos], buf[pos + 1]]) as usize;
694 pos += 2;
695 if buf.len() < pos + compact_size {
696 return Err(FormatError::BufferTooShort {
697 needed: pos + compact_size,
698 available: buf.len(),
699 });
700 }
701 let data = buf[pos..pos + compact_size].to_vec();
702 pos += compact_size;
703 Ok((Self::Compact { data }, pos))
704 }
705 CLASS_CHUNKED if version == VERSION_3 => {
706 let sa = ctx.sizeof_addr as usize;
709 let mut pos = 2;
710 if buf.len() < pos + 1 {
711 return Err(FormatError::BufferTooShort {
712 needed: pos + 1,
713 available: buf.len(),
714 });
715 }
716 let ndims = buf[pos] as usize;
717 pos += 1;
718
719 if ndims < 2 {
723 return Err(FormatError::InvalidData(format!(
724 "chunked v3 layout dimensionality {ndims} is too small"
725 )));
726 }
727
728 if buf.len() < pos + sa {
729 return Err(FormatError::BufferTooShort {
730 needed: pos + sa,
731 available: buf.len(),
732 });
733 }
734 let b_tree_address = read_addr(&buf[pos..], sa);
735 pos += sa;
736
737 let dim_data_len = ndims * 4;
738 if buf.len() < pos + dim_data_len {
739 return Err(FormatError::BufferTooShort {
740 needed: pos + dim_data_len,
741 available: buf.len(),
742 });
743 }
744 let mut chunk_dims = Vec::with_capacity(ndims);
745 for _ in 0..ndims {
746 let d = u32::from_le_bytes([buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]])
747 as u64;
748 if d == 0 {
749 return Err(FormatError::InvalidData(
750 "chunked v3 layout has a zero chunk dimension".into(),
751 ));
752 }
753 chunk_dims.push(d);
754 pos += 4;
755 }
756
757 Ok((
758 Self::ChunkedV3 {
759 chunk_dims,
760 b_tree_address,
761 },
762 pos,
763 ))
764 }
765 CLASS_CHUNKED => {
766 let sa = ctx.sizeof_addr as usize;
767 let mut pos = 2;
768
769 if buf.len() < pos + 3 {
771 return Err(FormatError::BufferTooShort {
772 needed: pos + 3,
773 available: buf.len(),
774 });
775 }
776 let flags = buf[pos];
777 pos += 1;
778 let ndims = buf[pos] as usize;
779 pos += 1;
780 let enc_bytes = buf[pos] as usize;
781 pos += 1;
782
783 if !(1..=8).contains(&enc_bytes) {
786 return Err(FormatError::InvalidData(format!(
787 "chunked layout encoded dimension size {enc_bytes} is out of range"
788 )));
789 }
790 if ndims < 2 {
793 return Err(FormatError::InvalidData(format!(
794 "chunked v4 layout dimensionality {ndims} is too small"
795 )));
796 }
797
798 let dim_data_len = ndims * enc_bytes;
800 if buf.len() < pos + dim_data_len {
801 return Err(FormatError::BufferTooShort {
802 needed: pos + dim_data_len,
803 available: buf.len(),
804 });
805 }
806 let mut chunk_dims = Vec::with_capacity(ndims);
807 for _ in 0..ndims {
808 let d = read_size(&buf[pos..], enc_bytes);
809 if d == 0 {
810 return Err(FormatError::InvalidData(
811 "chunked v4 layout has a zero chunk dimension".into(),
812 ));
813 }
814 chunk_dims.push(d);
815 pos += enc_bytes;
816 }
817
818 if buf.len() < pos + 1 {
820 return Err(FormatError::BufferTooShort {
821 needed: pos + 1,
822 available: buf.len(),
823 });
824 }
825 let idx_type_raw = buf[pos];
826 pos += 1;
827 let index_type = ChunkIndexType::from_u8(idx_type_raw).ok_or_else(|| {
828 FormatError::UnsupportedFeature(format!("chunk index type {}", idx_type_raw))
829 })?;
830
831 let mut earray_params = None;
833 let mut farray_params = None;
834 let mut bt2_params = None;
835 let mut single_chunk_filter = None;
836
837 match index_type {
838 ChunkIndexType::ExtensibleArray => {
839 if buf.len() < pos + 5 {
840 return Err(FormatError::BufferTooShort {
841 needed: pos + 5,
842 available: buf.len(),
843 });
844 }
845 let ep = EarrayParams {
846 max_nelmts_bits: buf[pos],
847 idx_blk_elmts: buf[pos + 1],
848 sup_blk_min_data_ptrs: buf[pos + 2],
849 data_blk_min_elmts: buf[pos + 3],
850 max_dblk_page_nelmts_bits: buf[pos + 4],
851 };
852 if ep.max_nelmts_bits == 0
854 || ep.idx_blk_elmts == 0
855 || ep.sup_blk_min_data_ptrs == 0
856 || ep.data_blk_min_elmts == 0
857 || ep.max_dblk_page_nelmts_bits == 0
858 {
859 return Err(FormatError::InvalidData(
860 "extensible-array layout parameter is zero".into(),
861 ));
862 }
863 earray_params = Some(ep);
864 pos += 5;
865 }
866 ChunkIndexType::FixedArray => {
867 if buf.len() < pos + 1 {
868 return Err(FormatError::BufferTooShort {
869 needed: pos + 1,
870 available: buf.len(),
871 });
872 }
873 farray_params = Some(FixedArrayParams {
879 max_dblk_page_nelmts_bits: buf[pos],
880 });
881 pos += 1;
882 }
883 ChunkIndexType::BTreeV2 => {
884 if buf.len() < pos + 6 {
889 return Err(FormatError::BufferTooShort {
890 needed: pos + 6,
891 available: buf.len(),
892 });
893 }
894 bt2_params = Some(Bt2Params {
895 node_size: u32::from_le_bytes([
896 buf[pos],
897 buf[pos + 1],
898 buf[pos + 2],
899 buf[pos + 3],
900 ]),
901 split_percent: buf[pos + 4],
902 merge_percent: buf[pos + 5],
903 });
904 pos += 6;
905 }
906 ChunkIndexType::SingleChunk if flags & 0x02 != 0 => {
913 let ss = ctx.sizeof_size as usize;
914 let extra = ss + 4;
915 if buf.len() < pos + extra {
916 return Err(FormatError::BufferTooShort {
917 needed: pos + extra,
918 available: buf.len(),
919 });
920 }
921 let nbytes = read_size(&buf[pos..], ss);
922 pos += ss;
923 let filter_mask = u32::from_le_bytes([
924 buf[pos],
925 buf[pos + 1],
926 buf[pos + 2],
927 buf[pos + 3],
928 ]);
929 pos += 4;
930 single_chunk_filter = Some(SingleChunkFilter {
931 nbytes,
932 filter_mask,
933 });
934 }
935 _ => {}
938 }
939
940 if buf.len() < pos + sa {
942 return Err(FormatError::BufferTooShort {
943 needed: pos + sa,
944 available: buf.len(),
945 });
946 }
947 let index_address = read_addr(&buf[pos..], sa);
948 pos += sa;
949
950 Ok((
951 Self::ChunkedV4 {
952 version: buf[0],
953 flags,
954 chunk_dims,
955 index_type,
956 earray_params,
957 farray_params,
958 bt2_params,
959 single_chunk_filter,
960 index_address,
961 },
962 pos,
963 ))
964 }
965 CLASS_VIRTUAL => {
966 if version == VERSION_3 {
971 return Err(FormatError::InvalidVersion(VERSION_3));
972 }
973 let sa = ctx.sizeof_addr as usize;
974 let mut pos = 2;
975 if buf.len() < pos + sa {
976 return Err(FormatError::BufferTooShort {
977 needed: pos + sa,
978 available: buf.len(),
979 });
980 }
981 let heap_address = read_addr(&buf[pos..], sa);
982 pos += sa;
983
984 if buf.len() < pos + 4 {
985 return Err(FormatError::BufferTooShort {
986 needed: pos + 4,
987 available: buf.len(),
988 });
989 }
990 let heap_index =
991 u32::from_le_bytes([buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]]);
992 pos += 4;
993
994 Ok((
995 Self::Virtual {
996 version: buf[0],
997 heap_address,
998 heap_index,
999 },
1000 pos,
1001 ))
1002 }
1003 other => Err(FormatError::UnsupportedFeature(format!(
1004 "data layout class {}",
1005 other
1006 ))),
1007 }
1008 }
1009}
1010
1011fn enc_bytes_for_value(v: u64) -> u8 {
1015 if v == 0 {
1016 return 1;
1017 }
1018 let bits_needed = 64 - v.leading_zeros(); bits_needed.div_ceil(8) as u8
1020}
1021
1022#[cfg(test)]
1025mod tests {
1026 use super::*;
1027
1028 fn ctx8() -> FormatContext {
1029 FormatContext {
1030 sizeof_addr: 8,
1031 sizeof_size: 8,
1032 }
1033 }
1034
1035 fn ctx4() -> FormatContext {
1036 FormatContext {
1037 sizeof_addr: 4,
1038 sizeof_size: 4,
1039 }
1040 }
1041
1042 #[test]
1043 fn roundtrip_contiguous() {
1044 let msg = DataLayoutMessage::contiguous(0x1000, 4096);
1045 let encoded = msg.encode(&ctx8());
1046 assert_eq!(encoded.len(), 18);
1048 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
1049 assert_eq!(consumed, 18);
1050 assert_eq!(decoded, msg);
1051 }
1052
1053 #[test]
1054 fn roundtrip_contiguous_ctx4() {
1055 let msg = DataLayoutMessage::contiguous(0x800, 256);
1056 let encoded = msg.encode(&ctx4());
1057 assert_eq!(encoded.len(), 10);
1059 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx4()).unwrap();
1060 assert_eq!(consumed, 10);
1061 assert_eq!(decoded, msg);
1062 }
1063
1064 #[test]
1065 fn roundtrip_contiguous_unallocated() {
1066 let msg = DataLayoutMessage::contiguous_unallocated(1024);
1067 let encoded = msg.encode(&ctx8());
1068 let (decoded, _) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
1069 assert_eq!(decoded, msg);
1070 match decoded {
1071 DataLayoutMessage::Contiguous { address, size } => {
1072 assert_eq!(address, UNDEF_ADDR);
1073 assert_eq!(size, 1024);
1074 }
1075 _ => panic!("expected Contiguous"),
1076 }
1077 }
1078
1079 #[test]
1080 fn roundtrip_contiguous_undef_ctx4() {
1081 let msg = DataLayoutMessage::contiguous_unallocated(512);
1082 let encoded = msg.encode(&ctx4());
1083 let (decoded, _) = DataLayoutMessage::decode(&encoded, &ctx4()).unwrap();
1084 match decoded {
1085 DataLayoutMessage::Contiguous { address, .. } => {
1086 assert_eq!(address, UNDEF_ADDR);
1087 }
1088 _ => panic!("expected Contiguous"),
1089 }
1090 }
1091
1092 #[test]
1093 fn roundtrip_compact() {
1094 let data = vec![1, 2, 3, 4, 5, 6, 7, 8];
1095 let msg = DataLayoutMessage::compact(data.clone());
1096 let encoded = msg.encode(&ctx8());
1097 assert_eq!(encoded.len(), 12);
1099 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
1100 assert_eq!(consumed, 12);
1101 assert_eq!(decoded, msg);
1102 }
1103
1104 #[test]
1105 fn roundtrip_compact_empty() {
1106 let msg = DataLayoutMessage::compact(vec![]);
1107 let encoded = msg.encode(&ctx8());
1108 assert_eq!(encoded.len(), 4); let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
1110 assert_eq!(consumed, 4);
1111 assert_eq!(decoded, msg);
1112 }
1113
1114 #[test]
1118 fn decode_legacy_version_is_unsupported_not_invalid() {
1119 for version in [1u8, 2] {
1120 let mut buf = vec![version, 1];
1121 buf.extend_from_slice(&[0u8; 16]);
1122 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
1123 match err {
1124 FormatError::UnsupportedFeature(ref s) => {
1125 assert!(s.contains(&version.to_string()), "{s}")
1126 }
1127 other => panic!("unexpected error for version {version}: {other:?}"),
1128 }
1129 }
1130 }
1131
1132 #[test]
1133 fn decode_bad_version() {
1134 for version in [0u8, 6, 255] {
1135 let mut buf = vec![version, 1];
1136 buf.extend_from_slice(&[0u8; 16]);
1137 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
1138 match err {
1139 FormatError::InvalidVersion(v) if v == version => {}
1140 other => panic!("unexpected error for version {version}: {other:?}"),
1141 }
1142 }
1143 }
1144
1145 #[test]
1150 fn decode_contiguous_and_compact_at_every_modern_version() {
1151 for version in [3u8, 4, 5] {
1152 let mut contig = vec![version, CLASS_CONTIGUOUS];
1153 contig.extend_from_slice(&0x800u64.to_le_bytes());
1154 contig.extend_from_slice(&64u64.to_le_bytes());
1155 let (decoded, consumed) = DataLayoutMessage::decode(&contig, &ctx8()).unwrap();
1156 assert_eq!(consumed, contig.len());
1157 assert_eq!(
1158 decoded,
1159 DataLayoutMessage::Contiguous {
1160 address: 0x800,
1161 size: 64
1162 }
1163 );
1164
1165 let payload = [1u8, 2, 3, 4];
1166 let mut compact = vec![version, CLASS_COMPACT];
1167 compact.extend_from_slice(&(payload.len() as u16).to_le_bytes());
1168 compact.extend_from_slice(&payload);
1169 let (decoded, consumed) = DataLayoutMessage::decode(&compact, &ctx8()).unwrap();
1170 assert_eq!(consumed, compact.len());
1171 assert_eq!(
1172 decoded,
1173 DataLayoutMessage::Compact {
1174 data: payload.to_vec()
1175 }
1176 );
1177 }
1178 }
1179
1180 #[test]
1181 fn decode_unsupported_class() {
1182 let buf = [3u8, 4]; let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
1184 match err {
1185 FormatError::UnsupportedFeature(_) => {}
1186 other => panic!("unexpected error: {:?}", other),
1187 }
1188 }
1189
1190 #[test]
1191 fn decode_buffer_too_short() {
1192 let buf = [3u8];
1193 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
1194 match err {
1195 FormatError::BufferTooShort { .. } => {}
1196 other => panic!("unexpected error: {:?}", other),
1197 }
1198 }
1199
1200 #[test]
1201 fn decode_contiguous_truncated() {
1202 let buf = [3u8, 1, 0, 0];
1204 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
1205 match err {
1206 FormatError::BufferTooShort { .. } => {}
1207 other => panic!("unexpected error: {:?}", other),
1208 }
1209 }
1210
1211 #[test]
1212 fn version_and_class_bytes() {
1213 let encoded = DataLayoutMessage::contiguous(0, 0).encode(&ctx8());
1214 assert_eq!(encoded[0], 3);
1215 assert_eq!(encoded[1], 1);
1216
1217 let encoded = DataLayoutMessage::compact(vec![]).encode(&ctx8());
1218 assert_eq!(encoded[0], 3);
1219 assert_eq!(encoded[1], 0);
1220 }
1221
1222 #[test]
1223 fn roundtrip_chunked_v4_earray() {
1224 let params = EarrayParams::default_params();
1225 let msg = DataLayoutMessage::chunked_v4_earray(4, vec![1, 256, 256], params, 0x2000);
1226 let encoded = msg.encode(&ctx8());
1227 assert_eq!(encoded[0], 4); assert_eq!(encoded[1], 2); let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
1230 assert_eq!(consumed, encoded.len());
1231 assert_eq!(decoded, msg);
1232 }
1233
1234 #[test]
1235 fn roundtrip_chunked_v4_earray_ctx4() {
1236 let params = EarrayParams::default_params();
1237 let msg = DataLayoutMessage::chunked_v4_earray(4, vec![1, 128], params, 0x1000);
1238 let encoded = msg.encode(&ctx4());
1239 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx4()).unwrap();
1240 assert_eq!(consumed, encoded.len());
1241 assert_eq!(decoded, msg);
1242 }
1243
1244 #[test]
1249 fn roundtrip_chunked_v5_earray() {
1250 let params = EarrayParams::default_params();
1251 let v5 = DataLayoutMessage::chunked_v4_earray(5, vec![1, 256, 256], params.clone(), 0x2000);
1252 let encoded = v5.encode(&ctx8());
1253 assert_eq!(encoded[0], 5); assert_eq!(encoded[1], 2); let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
1256 assert_eq!(consumed, encoded.len());
1257 assert_eq!(decoded, v5);
1258
1259 let v4 = DataLayoutMessage::chunked_v4_earray(4, vec![1, 256, 256], params, 0x2000);
1261 let encoded_v4 = v4.encode(&ctx8());
1262 assert_eq!(encoded[1..], encoded_v4[1..]);
1263 }
1264
1265 #[test]
1266 fn roundtrip_chunked_v4_single() {
1267 let msg = DataLayoutMessage::chunked_v4_single(vec![100, 200], 0x3000);
1268 let encoded = msg.encode(&ctx8());
1269 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
1270 assert_eq!(consumed, encoded.len());
1271 assert_eq!(decoded, msg);
1272 }
1273
1274 #[test]
1279 fn roundtrip_chunked_v4_btree_v2_params() {
1280 for ctx in [ctx8(), ctx4()] {
1281 let msg = DataLayoutMessage::chunked_v4_btree_v2(
1282 4,
1283 vec![2, 2, 8],
1284 Bt2Params {
1285 node_size: 512,
1286 split_percent: 90,
1287 merge_percent: 30,
1288 },
1289 0x2000,
1290 );
1291 let encoded = msg.encode(&ctx);
1292 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx).unwrap();
1293 assert_eq!(consumed, encoded.len());
1294 assert_eq!(decoded, msg);
1295 }
1296 }
1297
1298 #[test]
1303 fn roundtrip_chunked_v4_single_filtered() {
1304 for ctx in [ctx8(), ctx4()] {
1305 let msg = DataLayoutMessage::ChunkedV4 {
1306 version: 4,
1307 flags: 0x02,
1308 chunk_dims: vec![100, 200, 4],
1309 index_type: ChunkIndexType::SingleChunk,
1310 earray_params: None,
1311 farray_params: None,
1312 bt2_params: None,
1313 single_chunk_filter: Some(SingleChunkFilter {
1314 nbytes: 12345,
1315 filter_mask: 0b101,
1316 }),
1317 index_address: 0x3000,
1318 };
1319 let encoded = msg.encode(&ctx);
1320 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx).unwrap();
1321 assert_eq!(consumed, encoded.len());
1322 assert_eq!(decoded, msg);
1323 match decoded {
1325 DataLayoutMessage::ChunkedV4 {
1326 single_chunk_filter: Some(scf),
1327 ..
1328 } => {
1329 assert_eq!(scf.nbytes, 12345);
1330 assert_eq!(scf.filter_mask, 0b101);
1331 }
1332 other => panic!("expected filtered single-chunk layout, got {other:?}"),
1333 }
1334 }
1335 }
1336
1337 #[test]
1338 fn chunked_v4_enc_bytes() {
1339 let params = EarrayParams::default_params();
1341 let msg = DataLayoutMessage::chunked_v4_earray(4, vec![1, 256, 256], params, 0x2000);
1342 let encoded = msg.encode(&ctx8());
1343 assert_eq!(encoded.len(), 25);
1346 assert_eq!(encoded[4], 2); }
1348
1349 #[test]
1350 fn roundtrip_chunked_v3_btree_v1() {
1351 let msg = DataLayoutMessage::chunked_v3_btree_v1(vec![8, 4], 0x1234);
1353 let encoded = msg.encode(&ctx8());
1354 assert_eq!(encoded.len(), 19);
1356 assert_eq!(encoded[0], 3);
1357 assert_eq!(encoded[1], 2);
1358 assert_eq!(encoded[2], 2); let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
1360 assert_eq!(consumed, encoded.len());
1361 assert_eq!(decoded, msg);
1362 }
1363
1364 #[test]
1365 fn roundtrip_chunked_v3_btree_v1_2d_ctx4() {
1366 let msg = DataLayoutMessage::chunked_v3_btree_v1(vec![2, 3, 8], 0x800);
1368 let encoded = msg.encode(&ctx4());
1369 assert_eq!(encoded.len(), 19);
1371 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx4()).unwrap();
1372 assert_eq!(consumed, encoded.len());
1373 assert_eq!(decoded, msg);
1374 }
1375
1376 #[test]
1377 fn chunked_v3_undef_btree_addr() {
1378 let msg = DataLayoutMessage::chunked_v3_btree_v1(vec![16, 4], UNDEF_ADDR);
1379 let encoded = msg.encode(&ctx8());
1380 let (decoded, _) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
1381 match decoded {
1382 DataLayoutMessage::ChunkedV3 { b_tree_address, .. } => {
1383 assert_eq!(b_tree_address, UNDEF_ADDR);
1384 }
1385 _ => panic!("expected ChunkedV3"),
1386 }
1387 }
1388
1389 #[test]
1390 fn chunked_v3_rejects_ndims_too_small() {
1391 let buf = [3u8, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
1393 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
1394 assert!(matches!(err, FormatError::InvalidData(_)));
1395 }
1396
1397 #[test]
1398 fn chunked_v3_rejects_zero_dim() {
1399 let mut buf = vec![3u8, 2, 2];
1401 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();
1405 assert!(matches!(err, FormatError::InvalidData(_)));
1406 }
1407
1408 #[test]
1409 fn chunked_v3_truncated() {
1410 let buf = [3u8, 2, 2];
1412 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
1413 assert!(matches!(err, FormatError::BufferTooShort { .. }));
1414 }
1415
1416 #[test]
1417 fn roundtrip_virtual_layout() {
1418 for ctx in [ctx8(), ctx4()] {
1419 for version in [4u8, 5u8] {
1420 let msg = DataLayoutMessage::virtual_layout(version, 0x5000, 3);
1421 let encoded = msg.encode(&ctx);
1422 assert_eq!(encoded[0], version);
1423 assert_eq!(encoded[1], CLASS_VIRTUAL);
1424 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx).unwrap();
1425 assert_eq!(consumed, encoded.len());
1426 assert_eq!(decoded, msg);
1427 }
1428 }
1429 }
1430
1431 #[test]
1432 fn virtual_layout_undefined_heap_address() {
1433 let msg = DataLayoutMessage::virtual_layout(4, UNDEF_ADDR, 0);
1436 let encoded = msg.encode(&ctx8());
1437 let (decoded, _) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
1438 match decoded {
1439 DataLayoutMessage::Virtual {
1440 heap_address,
1441 heap_index,
1442 ..
1443 } => {
1444 assert_eq!(heap_address, UNDEF_ADDR);
1445 assert_eq!(heap_index, 0);
1446 }
1447 other => panic!("expected Virtual, got {other:?}"),
1448 }
1449 }
1450
1451 #[test]
1455 fn virtual_layout_rejects_version_3() {
1456 let buf = [VERSION_3, CLASS_VIRTUAL];
1457 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
1458 assert!(matches!(err, FormatError::InvalidVersion(VERSION_3)));
1459 }
1460
1461 #[test]
1462 fn chunked_v4_large_dims() {
1463 let params = EarrayParams::default_params();
1465 let msg = DataLayoutMessage::chunked_v4_earray(4, vec![1, 65536], params, 0x4000);
1466 let encoded = msg.encode(&ctx8());
1467 assert_eq!(encoded[4], 3); }
1469
1470 #[test]
1474 fn check_against_dataset_chunk_rank_boundaries() {
1475 use crate::format::messages::dataspace::DataspaceMessage;
1476 use crate::format::messages::datatype::DatatypeMessage;
1477 let ctx = FormatContext::default_v3();
1478 let i32_t = DatatypeMessage::i32_type();
1479 let rank2 = DataspaceMessage::simple(&[3, 4]);
1480 let v3 = DataLayoutMessage::chunked_v3_btree_v1(vec![2, 2, 4], 0x1000);
1482 assert!(v3.check_against_dataset(&rank2, &i32_t, &ctx).is_ok());
1483 let v4 = DataLayoutMessage::chunked_v4_single(vec![2, 2, 4], 0x1000);
1484 assert!(v4.check_against_dataset(&rank2, &i32_t, &ctx).is_ok());
1485
1486 let rank3 = DataspaceMessage::simple(&[3, 4, 5]);
1489 let err = v3.check_against_dataset(&rank3, &i32_t, &ctx).unwrap_err();
1490 assert!(
1491 matches!(err, FormatError::InvalidData(ref s) if s.contains("must be 4")),
1492 "{err}"
1493 );
1494 assert!(v4.check_against_dataset(&rank3, &i32_t, &ctx).is_err());
1495
1496 let rank1 = DataspaceMessage::simple(&[8]);
1498 assert!(v3.check_against_dataset(&rank1, &i32_t, &ctx).is_err());
1499 assert!(v3
1500 .check_against_dataset(&DataspaceMessage::scalar(), &i32_t, &ctx)
1501 .is_err());
1502
1503 let contiguous = DataLayoutMessage::contiguous_unallocated(96);
1505 assert!(contiguous
1506 .check_against_dataset(&rank3, &i32_t, &ctx)
1507 .is_ok());
1508 assert!(contiguous
1509 .check_against_dataset(&DataspaceMessage::scalar(), &i32_t, &ctx)
1510 .is_ok());
1511 }
1512
1513 #[test]
1518 fn check_against_dataset_compact_size_boundaries() {
1519 use crate::format::messages::dataspace::DataspaceMessage;
1520 use crate::format::messages::datatype::DatatypeMessage;
1521 let ctx = FormatContext::default_v3();
1522 let i32_t = DatatypeMessage::i32_type();
1523 let d = DataspaceMessage::simple(&[3, 4]);
1524 let ok = DataLayoutMessage::compact(vec![0u8; 48]);
1525 assert!(ok.check_against_dataset(&d, &i32_t, &ctx).is_ok());
1526 let short = DataLayoutMessage::compact(vec![0u8; 44]);
1527 let err = short.check_against_dataset(&d, &i32_t, &ctx).unwrap_err();
1528 assert!(
1529 matches!(err, FormatError::InvalidData(ref s) if s.contains("44 bytes") && s.contains("need 48")),
1530 "{err}"
1531 );
1532 let long = DataLayoutMessage::compact(vec![0u8; 52]);
1533 assert!(long.check_against_dataset(&d, &i32_t, &ctx).is_err());
1534
1535 let one = DataLayoutMessage::compact(vec![0u8; 4]);
1536 assert!(one
1537 .check_against_dataset(&DataspaceMessage::scalar(), &i32_t, &ctx)
1538 .is_ok());
1539 assert!(one
1540 .check_against_dataset(&DataspaceMessage::null(), &i32_t, &ctx)
1541 .is_err());
1542 let none = DataLayoutMessage::compact(vec![]);
1543 assert!(none
1544 .check_against_dataset(&DataspaceMessage::null(), &i32_t, &ctx)
1545 .is_ok());
1546 assert!(none
1547 .check_against_dataset(&DataspaceMessage::simple(&[0, 5]), &i32_t, &ctx)
1548 .is_ok());
1549
1550 let vlen = DatatypeMessage::vlen_string_ascii();
1552 let small_ctx = FormatContext {
1553 sizeof_addr: 4,
1554 sizeof_size: 4,
1555 };
1556 let refs = DataLayoutMessage::compact(vec![0u8; 2 * 12]);
1557 assert!(refs
1558 .check_against_dataset(&DataspaceMessage::simple(&[2]), &vlen, &small_ctx)
1559 .is_ok());
1560 assert!(refs
1561 .check_against_dataset(&DataspaceMessage::simple(&[2]), &vlen, &ctx)
1562 .is_err());
1563
1564 let huge = DataspaceMessage::simple(&[u64::MAX, 2]);
1565 assert!(ok.check_against_dataset(&huge, &i32_t, &ctx).is_err());
1566 let huge_bytes = DataspaceMessage::simple(&[u64::MAX / 2]);
1567 assert!(ok.check_against_dataset(&huge_bytes, &i32_t, &ctx).is_err());
1568 }
1569}