1use crate::format::bytes::{read_le_addr as read_addr, read_le_uint as read_size};
35use crate::format::{FormatContext, FormatError, FormatResult, UNDEF_ADDR};
36
37const VERSION_3: u8 = 3;
38const VERSION_4: u8 = 4;
39const VERSION_5: u8 = 5;
44const CLASS_COMPACT: u8 = 0;
45const CLASS_CONTIGUOUS: u8 = 1;
46const CLASS_CHUNKED: u8 = 2;
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50#[repr(u8)]
51pub enum ChunkIndexType {
52 SingleChunk = 1,
53 Implicit = 2,
54 FixedArray = 3,
55 ExtensibleArray = 4,
56 BTreeV2 = 5,
57}
58
59impl ChunkIndexType {
60 pub fn from_u8(v: u8) -> Option<Self> {
61 match v {
62 1 => Some(Self::SingleChunk),
63 2 => Some(Self::Implicit),
64 3 => Some(Self::FixedArray),
65 4 => Some(Self::ExtensibleArray),
66 5 => Some(Self::BTreeV2),
67 _ => None,
68 }
69 }
70}
71
72#[derive(Debug, Clone, PartialEq, Eq)]
74pub struct EarrayParams {
75 pub max_nelmts_bits: u8,
76 pub idx_blk_elmts: u8,
77 pub sup_blk_min_data_ptrs: u8,
78 pub data_blk_min_elmts: u8,
79 pub max_dblk_page_nelmts_bits: u8,
80}
81
82impl EarrayParams {
83 pub fn default_params() -> Self {
85 Self {
86 max_nelmts_bits: 32,
87 idx_blk_elmts: 4,
88 sup_blk_min_data_ptrs: 4,
89 data_blk_min_elmts: 16,
90 max_dblk_page_nelmts_bits: 10,
91 }
92 }
93}
94
95#[derive(Debug, Clone, PartialEq, Eq)]
97pub struct FixedArrayParams {
98 pub max_dblk_page_nelmts_bits: u8,
99}
100
101impl FixedArrayParams {
102 pub fn default_params() -> Self {
103 Self {
104 max_dblk_page_nelmts_bits: 10,
108 }
109 }
110}
111
112#[derive(Debug, Clone, PartialEq, Eq)]
118pub struct Bt2Params {
119 pub node_size: u32,
120 pub split_percent: u8,
121 pub merge_percent: u8,
122}
123
124impl Bt2Params {
125 pub fn default_params() -> Self {
129 use crate::format::chunk_index::btree_v2::{
130 BT2_MERGE_PERCENT, BT2_NODE_SIZE, BT2_SPLIT_PERCENT,
131 };
132 Self {
133 node_size: BT2_NODE_SIZE,
134 split_percent: BT2_SPLIT_PERCENT,
135 merge_percent: BT2_MERGE_PERCENT,
136 }
137 }
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149pub struct SingleChunkFilter {
150 pub nbytes: u64,
152 pub filter_mask: u32,
155}
156
157#[derive(Debug, Clone, PartialEq)]
159pub enum DataLayoutMessage {
160 Contiguous {
162 address: u64,
164 size: u64,
166 },
167 Compact {
169 data: Vec<u8>,
171 },
172 ChunkedV3 {
177 chunk_dims: Vec<u64>,
180 b_tree_address: u64,
182 },
183 ChunkedV4 {
186 version: u8,
193 flags: u8,
194 chunk_dims: Vec<u64>,
196 index_type: ChunkIndexType,
198 earray_params: Option<EarrayParams>,
200 farray_params: Option<FixedArrayParams>,
202 bt2_params: Option<Bt2Params>,
204 single_chunk_filter: Option<SingleChunkFilter>,
207 index_address: u64,
209 },
210}
211
212impl DataLayoutMessage {
213 pub fn contiguous_unallocated(size: u64) -> Self {
215 Self::Contiguous {
216 address: UNDEF_ADDR,
217 size,
218 }
219 }
220
221 pub fn contiguous(address: u64, size: u64) -> Self {
223 Self::Contiguous { address, size }
224 }
225
226 pub fn compact(data: Vec<u8>) -> Self {
228 Self::Compact { data }
229 }
230
231 pub fn chunked_v3_btree_v1(chunk_dims: Vec<u64>, b_tree_address: u64) -> Self {
235 Self::ChunkedV3 {
236 chunk_dims,
237 b_tree_address,
238 }
239 }
240
241 pub fn chunked_v4_earray(
247 version: u8,
248 chunk_dims: Vec<u64>,
249 earray_params: EarrayParams,
250 index_address: u64,
251 ) -> Self {
252 Self::ChunkedV4 {
253 version,
254 flags: 0,
255 chunk_dims,
256 index_type: ChunkIndexType::ExtensibleArray,
257 earray_params: Some(earray_params),
258 farray_params: None,
259 bt2_params: None,
260 single_chunk_filter: None,
261 index_address,
262 }
263 }
264
265 pub fn chunked_v4_farray(
269 version: u8,
270 chunk_dims: Vec<u64>,
271 farray_params: FixedArrayParams,
272 index_address: u64,
273 ) -> Self {
274 Self::ChunkedV4 {
275 version,
276 flags: 0,
277 chunk_dims,
278 index_type: ChunkIndexType::FixedArray,
279 earray_params: None,
280 farray_params: Some(farray_params),
281 bt2_params: None,
282 single_chunk_filter: None,
283 index_address,
284 }
285 }
286
287 pub fn chunked_v4_btree_v2(
291 version: u8,
292 chunk_dims: Vec<u64>,
293 bt2_params: Bt2Params,
294 index_address: u64,
295 ) -> Self {
296 Self::ChunkedV4 {
297 version,
298 flags: 0,
299 chunk_dims,
300 index_type: ChunkIndexType::BTreeV2,
301 earray_params: None,
302 farray_params: None,
303 bt2_params: Some(bt2_params),
304 single_chunk_filter: None,
305 index_address,
306 }
307 }
308
309 pub fn chunked_v4_single(chunk_dims: Vec<u64>, index_address: u64) -> Self {
313 Self::ChunkedV4 {
314 version: VERSION_4,
315 flags: 0,
316 chunk_dims,
317 index_type: ChunkIndexType::SingleChunk,
318 earray_params: None,
319 farray_params: None,
320 bt2_params: None,
321 single_chunk_filter: None,
322 index_address,
323 }
324 }
325
326 pub fn encode(&self, ctx: &FormatContext) -> Vec<u8> {
329 match self {
330 Self::Contiguous { address, size } => {
331 let sa = ctx.sizeof_addr as usize;
332 let ss = ctx.sizeof_size as usize;
333 let mut buf = Vec::with_capacity(2 + sa + ss);
334 buf.push(VERSION_3);
335 buf.push(CLASS_CONTIGUOUS);
336 buf.extend_from_slice(&address.to_le_bytes()[..sa]);
337 buf.extend_from_slice(&size.to_le_bytes()[..ss]);
338 buf
339 }
340 Self::Compact { data } => {
341 let mut buf = Vec::with_capacity(2 + 2 + data.len());
342 buf.push(VERSION_3);
343 buf.push(CLASS_COMPACT);
344 buf.extend_from_slice(&(data.len() as u16).to_le_bytes());
345 buf.extend_from_slice(data);
346 buf
347 }
348 Self::ChunkedV3 {
349 chunk_dims,
350 b_tree_address,
351 } => {
352 let sa = ctx.sizeof_addr as usize;
353 let ndims = chunk_dims.len() as u8;
354 let mut buf = Vec::with_capacity(3 + sa + chunk_dims.len() * 4);
355 buf.push(VERSION_3);
356 buf.push(CLASS_CHUNKED);
357 buf.push(ndims);
358 buf.extend_from_slice(&b_tree_address.to_le_bytes()[..sa]);
359 for &d in chunk_dims {
361 buf.extend_from_slice(&(d as u32).to_le_bytes());
362 }
363 buf
364 }
365 Self::ChunkedV4 {
366 version,
367 flags,
368 chunk_dims,
369 index_type,
370 earray_params,
371 farray_params,
372 bt2_params,
373 single_chunk_filter,
374 index_address,
375 } => {
376 let sa = ctx.sizeof_addr as usize;
377 let ndims = chunk_dims.len() as u8;
378
379 let max_dim = chunk_dims.iter().copied().max().unwrap_or(1);
382 let enc_bytes = enc_bytes_for_value(max_dim);
383
384 debug_assert!(matches!(*version, VERSION_4 | VERSION_5));
385 let mut buf = Vec::with_capacity(64);
386 buf.push(*version);
387 buf.push(CLASS_CHUNKED);
388 buf.push(*flags);
389 buf.push(ndims);
390 buf.push(enc_bytes);
391
392 for &d in chunk_dims {
394 buf.extend_from_slice(&d.to_le_bytes()[..enc_bytes as usize]);
395 }
396
397 buf.push(*index_type as u8);
399
400 match *index_type {
402 ChunkIndexType::ExtensibleArray => {
403 if let Some(ref params) = earray_params {
404 buf.push(params.max_nelmts_bits);
405 buf.push(params.idx_blk_elmts);
406 buf.push(params.sup_blk_min_data_ptrs);
407 buf.push(params.data_blk_min_elmts);
408 buf.push(params.max_dblk_page_nelmts_bits);
409 }
410 }
411 ChunkIndexType::FixedArray => {
412 if let Some(ref params) = farray_params {
413 buf.push(params.max_dblk_page_nelmts_bits);
414 }
415 }
416 ChunkIndexType::BTreeV2 => {
417 if let Some(ref params) = bt2_params {
423 buf.extend_from_slice(¶ms.node_size.to_le_bytes());
424 buf.push(params.split_percent);
425 buf.push(params.merge_percent);
426 }
427 }
428 ChunkIndexType::SingleChunk if *flags & 0x02 != 0 => {
434 if let Some(scf) = single_chunk_filter {
435 let ss = ctx.sizeof_size as usize;
436 buf.extend_from_slice(&scf.nbytes.to_le_bytes()[..ss]);
437 buf.extend_from_slice(&scf.filter_mask.to_le_bytes());
438 }
439 }
440 _ => {}
442 }
443
444 buf.extend_from_slice(&index_address.to_le_bytes()[..sa]);
446
447 buf
448 }
449 }
450 }
451
452 pub fn decode(buf: &[u8], ctx: &FormatContext) -> FormatResult<(Self, usize)> {
455 if buf.len() < 2 {
456 return Err(FormatError::BufferTooShort {
457 needed: 2,
458 available: buf.len(),
459 });
460 }
461
462 let version = buf[0];
463 let class = buf[1];
464
465 match (version, class) {
466 (VERSION_3, CLASS_CONTIGUOUS) => {
467 let sa = ctx.sizeof_addr as usize;
468 let ss = ctx.sizeof_size as usize;
469 let mut pos = 2;
470 let needed = pos + sa + ss;
471 if buf.len() < needed {
472 return Err(FormatError::BufferTooShort {
473 needed,
474 available: buf.len(),
475 });
476 }
477 let address = read_addr(&buf[pos..], sa);
478 pos += sa;
479 let size = read_size(&buf[pos..], ss);
480 pos += ss;
481 Ok((Self::Contiguous { address, size }, pos))
482 }
483 (VERSION_3, CLASS_COMPACT) => {
484 let mut pos = 2;
485 if buf.len() < pos + 2 {
486 return Err(FormatError::BufferTooShort {
487 needed: pos + 2,
488 available: buf.len(),
489 });
490 }
491 let compact_size = u16::from_le_bytes([buf[pos], buf[pos + 1]]) as usize;
492 pos += 2;
493 if buf.len() < pos + compact_size {
494 return Err(FormatError::BufferTooShort {
495 needed: pos + compact_size,
496 available: buf.len(),
497 });
498 }
499 let data = buf[pos..pos + compact_size].to_vec();
500 pos += compact_size;
501 Ok((Self::Compact { data }, pos))
502 }
503 (VERSION_3, CLASS_CHUNKED) => {
504 let sa = ctx.sizeof_addr as usize;
507 let mut pos = 2;
508 if buf.len() < pos + 1 {
509 return Err(FormatError::BufferTooShort {
510 needed: pos + 1,
511 available: buf.len(),
512 });
513 }
514 let ndims = buf[pos] as usize;
515 pos += 1;
516
517 if ndims < 2 {
521 return Err(FormatError::InvalidData(format!(
522 "chunked v3 layout dimensionality {ndims} is too small"
523 )));
524 }
525
526 if buf.len() < pos + sa {
527 return Err(FormatError::BufferTooShort {
528 needed: pos + sa,
529 available: buf.len(),
530 });
531 }
532 let b_tree_address = read_addr(&buf[pos..], sa);
533 pos += sa;
534
535 let dim_data_len = ndims * 4;
536 if buf.len() < pos + dim_data_len {
537 return Err(FormatError::BufferTooShort {
538 needed: pos + dim_data_len,
539 available: buf.len(),
540 });
541 }
542 let mut chunk_dims = Vec::with_capacity(ndims);
543 for _ in 0..ndims {
544 let d = u32::from_le_bytes([buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]])
545 as u64;
546 if d == 0 {
547 return Err(FormatError::InvalidData(
548 "chunked v3 layout has a zero chunk dimension".into(),
549 ));
550 }
551 chunk_dims.push(d);
552 pos += 4;
553 }
554
555 Ok((
556 Self::ChunkedV3 {
557 chunk_dims,
558 b_tree_address,
559 },
560 pos,
561 ))
562 }
563 (VERSION_4 | VERSION_5, CLASS_CHUNKED) => {
564 let sa = ctx.sizeof_addr as usize;
565 let mut pos = 2;
566
567 if buf.len() < pos + 3 {
569 return Err(FormatError::BufferTooShort {
570 needed: pos + 3,
571 available: buf.len(),
572 });
573 }
574 let flags = buf[pos];
575 pos += 1;
576 let ndims = buf[pos] as usize;
577 pos += 1;
578 let enc_bytes = buf[pos] as usize;
579 pos += 1;
580
581 if !(1..=8).contains(&enc_bytes) {
584 return Err(FormatError::InvalidData(format!(
585 "chunked layout encoded dimension size {enc_bytes} is out of range"
586 )));
587 }
588 if ndims < 2 {
591 return Err(FormatError::InvalidData(format!(
592 "chunked v4 layout dimensionality {ndims} is too small"
593 )));
594 }
595
596 let dim_data_len = ndims * enc_bytes;
598 if buf.len() < pos + dim_data_len {
599 return Err(FormatError::BufferTooShort {
600 needed: pos + dim_data_len,
601 available: buf.len(),
602 });
603 }
604 let mut chunk_dims = Vec::with_capacity(ndims);
605 for _ in 0..ndims {
606 let d = read_size(&buf[pos..], enc_bytes);
607 if d == 0 {
608 return Err(FormatError::InvalidData(
609 "chunked v4 layout has a zero chunk dimension".into(),
610 ));
611 }
612 chunk_dims.push(d);
613 pos += enc_bytes;
614 }
615
616 if buf.len() < pos + 1 {
618 return Err(FormatError::BufferTooShort {
619 needed: pos + 1,
620 available: buf.len(),
621 });
622 }
623 let idx_type_raw = buf[pos];
624 pos += 1;
625 let index_type = ChunkIndexType::from_u8(idx_type_raw).ok_or_else(|| {
626 FormatError::UnsupportedFeature(format!("chunk index type {}", idx_type_raw))
627 })?;
628
629 let mut earray_params = None;
631 let mut farray_params = None;
632 let mut bt2_params = None;
633 let mut single_chunk_filter = None;
634
635 match index_type {
636 ChunkIndexType::ExtensibleArray => {
637 if buf.len() < pos + 5 {
638 return Err(FormatError::BufferTooShort {
639 needed: pos + 5,
640 available: buf.len(),
641 });
642 }
643 let ep = EarrayParams {
644 max_nelmts_bits: buf[pos],
645 idx_blk_elmts: buf[pos + 1],
646 sup_blk_min_data_ptrs: buf[pos + 2],
647 data_blk_min_elmts: buf[pos + 3],
648 max_dblk_page_nelmts_bits: buf[pos + 4],
649 };
650 if ep.max_nelmts_bits == 0
652 || ep.idx_blk_elmts == 0
653 || ep.sup_blk_min_data_ptrs == 0
654 || ep.data_blk_min_elmts == 0
655 || ep.max_dblk_page_nelmts_bits == 0
656 {
657 return Err(FormatError::InvalidData(
658 "extensible-array layout parameter is zero".into(),
659 ));
660 }
661 earray_params = Some(ep);
662 pos += 5;
663 }
664 ChunkIndexType::FixedArray => {
665 if buf.len() < pos + 1 {
666 return Err(FormatError::BufferTooShort {
667 needed: pos + 1,
668 available: buf.len(),
669 });
670 }
671 farray_params = Some(FixedArrayParams {
677 max_dblk_page_nelmts_bits: buf[pos],
678 });
679 pos += 1;
680 }
681 ChunkIndexType::BTreeV2 => {
682 if buf.len() < pos + 6 {
687 return Err(FormatError::BufferTooShort {
688 needed: pos + 6,
689 available: buf.len(),
690 });
691 }
692 bt2_params = Some(Bt2Params {
693 node_size: u32::from_le_bytes([
694 buf[pos],
695 buf[pos + 1],
696 buf[pos + 2],
697 buf[pos + 3],
698 ]),
699 split_percent: buf[pos + 4],
700 merge_percent: buf[pos + 5],
701 });
702 pos += 6;
703 }
704 ChunkIndexType::SingleChunk if flags & 0x02 != 0 => {
711 let ss = ctx.sizeof_size as usize;
712 let extra = ss + 4;
713 if buf.len() < pos + extra {
714 return Err(FormatError::BufferTooShort {
715 needed: pos + extra,
716 available: buf.len(),
717 });
718 }
719 let nbytes = read_size(&buf[pos..], ss);
720 pos += ss;
721 let filter_mask = u32::from_le_bytes([
722 buf[pos],
723 buf[pos + 1],
724 buf[pos + 2],
725 buf[pos + 3],
726 ]);
727 pos += 4;
728 single_chunk_filter = Some(SingleChunkFilter {
729 nbytes,
730 filter_mask,
731 });
732 }
733 _ => {}
736 }
737
738 if buf.len() < pos + sa {
740 return Err(FormatError::BufferTooShort {
741 needed: pos + sa,
742 available: buf.len(),
743 });
744 }
745 let index_address = read_addr(&buf[pos..], sa);
746 pos += sa;
747
748 Ok((
749 Self::ChunkedV4 {
750 version: buf[0],
751 flags,
752 chunk_dims,
753 index_type,
754 earray_params,
755 farray_params,
756 bt2_params,
757 single_chunk_filter,
758 index_address,
759 },
760 pos,
761 ))
762 }
763 (VERSION_3, other) => Err(FormatError::UnsupportedFeature(format!(
764 "data layout class {}",
765 other
766 ))),
767 (v, _) => Err(FormatError::InvalidVersion(v)),
768 }
769 }
770}
771
772fn enc_bytes_for_value(v: u64) -> u8 {
776 if v == 0 {
777 return 1;
778 }
779 let bits_needed = 64 - v.leading_zeros(); bits_needed.div_ceil(8) as u8
781}
782
783#[cfg(test)]
786mod tests {
787 use super::*;
788
789 fn ctx8() -> FormatContext {
790 FormatContext {
791 sizeof_addr: 8,
792 sizeof_size: 8,
793 }
794 }
795
796 fn ctx4() -> FormatContext {
797 FormatContext {
798 sizeof_addr: 4,
799 sizeof_size: 4,
800 }
801 }
802
803 #[test]
804 fn roundtrip_contiguous() {
805 let msg = DataLayoutMessage::contiguous(0x1000, 4096);
806 let encoded = msg.encode(&ctx8());
807 assert_eq!(encoded.len(), 18);
809 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
810 assert_eq!(consumed, 18);
811 assert_eq!(decoded, msg);
812 }
813
814 #[test]
815 fn roundtrip_contiguous_ctx4() {
816 let msg = DataLayoutMessage::contiguous(0x800, 256);
817 let encoded = msg.encode(&ctx4());
818 assert_eq!(encoded.len(), 10);
820 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx4()).unwrap();
821 assert_eq!(consumed, 10);
822 assert_eq!(decoded, msg);
823 }
824
825 #[test]
826 fn roundtrip_contiguous_unallocated() {
827 let msg = DataLayoutMessage::contiguous_unallocated(1024);
828 let encoded = msg.encode(&ctx8());
829 let (decoded, _) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
830 assert_eq!(decoded, msg);
831 match decoded {
832 DataLayoutMessage::Contiguous { address, size } => {
833 assert_eq!(address, UNDEF_ADDR);
834 assert_eq!(size, 1024);
835 }
836 _ => panic!("expected Contiguous"),
837 }
838 }
839
840 #[test]
841 fn roundtrip_contiguous_undef_ctx4() {
842 let msg = DataLayoutMessage::contiguous_unallocated(512);
843 let encoded = msg.encode(&ctx4());
844 let (decoded, _) = DataLayoutMessage::decode(&encoded, &ctx4()).unwrap();
845 match decoded {
846 DataLayoutMessage::Contiguous { address, .. } => {
847 assert_eq!(address, UNDEF_ADDR);
848 }
849 _ => panic!("expected Contiguous"),
850 }
851 }
852
853 #[test]
854 fn roundtrip_compact() {
855 let data = vec![1, 2, 3, 4, 5, 6, 7, 8];
856 let msg = DataLayoutMessage::compact(data.clone());
857 let encoded = msg.encode(&ctx8());
858 assert_eq!(encoded.len(), 12);
860 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
861 assert_eq!(consumed, 12);
862 assert_eq!(decoded, msg);
863 }
864
865 #[test]
866 fn roundtrip_compact_empty() {
867 let msg = DataLayoutMessage::compact(vec![]);
868 let encoded = msg.encode(&ctx8());
869 assert_eq!(encoded.len(), 4); let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
871 assert_eq!(consumed, 4);
872 assert_eq!(decoded, msg);
873 }
874
875 #[test]
876 fn decode_bad_version() {
877 let buf = [2u8, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
878 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
879 match err {
880 FormatError::InvalidVersion(2) => {}
881 other => panic!("unexpected error: {:?}", other),
882 }
883 }
884
885 #[test]
886 fn decode_unsupported_class() {
887 let buf = [3u8, 3]; let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
889 match err {
890 FormatError::UnsupportedFeature(_) => {}
891 other => panic!("unexpected error: {:?}", other),
892 }
893 }
894
895 #[test]
896 fn decode_buffer_too_short() {
897 let buf = [3u8];
898 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
899 match err {
900 FormatError::BufferTooShort { .. } => {}
901 other => panic!("unexpected error: {:?}", other),
902 }
903 }
904
905 #[test]
906 fn decode_contiguous_truncated() {
907 let buf = [3u8, 1, 0, 0];
909 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
910 match err {
911 FormatError::BufferTooShort { .. } => {}
912 other => panic!("unexpected error: {:?}", other),
913 }
914 }
915
916 #[test]
917 fn version_and_class_bytes() {
918 let encoded = DataLayoutMessage::contiguous(0, 0).encode(&ctx8());
919 assert_eq!(encoded[0], 3);
920 assert_eq!(encoded[1], 1);
921
922 let encoded = DataLayoutMessage::compact(vec![]).encode(&ctx8());
923 assert_eq!(encoded[0], 3);
924 assert_eq!(encoded[1], 0);
925 }
926
927 #[test]
928 fn roundtrip_chunked_v4_earray() {
929 let params = EarrayParams::default_params();
930 let msg = DataLayoutMessage::chunked_v4_earray(4, vec![1, 256, 256], params, 0x2000);
931 let encoded = msg.encode(&ctx8());
932 assert_eq!(encoded[0], 4); assert_eq!(encoded[1], 2); let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
935 assert_eq!(consumed, encoded.len());
936 assert_eq!(decoded, msg);
937 }
938
939 #[test]
940 fn roundtrip_chunked_v4_earray_ctx4() {
941 let params = EarrayParams::default_params();
942 let msg = DataLayoutMessage::chunked_v4_earray(4, vec![1, 128], params, 0x1000);
943 let encoded = msg.encode(&ctx4());
944 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx4()).unwrap();
945 assert_eq!(consumed, encoded.len());
946 assert_eq!(decoded, msg);
947 }
948
949 #[test]
954 fn roundtrip_chunked_v5_earray() {
955 let params = EarrayParams::default_params();
956 let v5 = DataLayoutMessage::chunked_v4_earray(5, vec![1, 256, 256], params.clone(), 0x2000);
957 let encoded = v5.encode(&ctx8());
958 assert_eq!(encoded[0], 5); assert_eq!(encoded[1], 2); let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
961 assert_eq!(consumed, encoded.len());
962 assert_eq!(decoded, v5);
963
964 let v4 = DataLayoutMessage::chunked_v4_earray(4, vec![1, 256, 256], params, 0x2000);
966 let encoded_v4 = v4.encode(&ctx8());
967 assert_eq!(encoded[1..], encoded_v4[1..]);
968 }
969
970 #[test]
971 fn roundtrip_chunked_v4_single() {
972 let msg = DataLayoutMessage::chunked_v4_single(vec![100, 200], 0x3000);
973 let encoded = msg.encode(&ctx8());
974 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
975 assert_eq!(consumed, encoded.len());
976 assert_eq!(decoded, msg);
977 }
978
979 #[test]
984 fn roundtrip_chunked_v4_btree_v2_params() {
985 for ctx in [ctx8(), ctx4()] {
986 let msg = DataLayoutMessage::chunked_v4_btree_v2(
987 4,
988 vec![2, 2, 8],
989 Bt2Params {
990 node_size: 512,
991 split_percent: 90,
992 merge_percent: 30,
993 },
994 0x2000,
995 );
996 let encoded = msg.encode(&ctx);
997 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx).unwrap();
998 assert_eq!(consumed, encoded.len());
999 assert_eq!(decoded, msg);
1000 }
1001 }
1002
1003 #[test]
1008 fn roundtrip_chunked_v4_single_filtered() {
1009 for ctx in [ctx8(), ctx4()] {
1010 let msg = DataLayoutMessage::ChunkedV4 {
1011 version: 4,
1012 flags: 0x02,
1013 chunk_dims: vec![100, 200, 4],
1014 index_type: ChunkIndexType::SingleChunk,
1015 earray_params: None,
1016 farray_params: None,
1017 bt2_params: None,
1018 single_chunk_filter: Some(SingleChunkFilter {
1019 nbytes: 12345,
1020 filter_mask: 0b101,
1021 }),
1022 index_address: 0x3000,
1023 };
1024 let encoded = msg.encode(&ctx);
1025 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx).unwrap();
1026 assert_eq!(consumed, encoded.len());
1027 assert_eq!(decoded, msg);
1028 match decoded {
1030 DataLayoutMessage::ChunkedV4 {
1031 single_chunk_filter: Some(scf),
1032 ..
1033 } => {
1034 assert_eq!(scf.nbytes, 12345);
1035 assert_eq!(scf.filter_mask, 0b101);
1036 }
1037 other => panic!("expected filtered single-chunk layout, got {other:?}"),
1038 }
1039 }
1040 }
1041
1042 #[test]
1043 fn chunked_v4_enc_bytes() {
1044 let params = EarrayParams::default_params();
1046 let msg = DataLayoutMessage::chunked_v4_earray(4, vec![1, 256, 256], params, 0x2000);
1047 let encoded = msg.encode(&ctx8());
1048 assert_eq!(encoded.len(), 25);
1051 assert_eq!(encoded[4], 2); }
1053
1054 #[test]
1055 fn roundtrip_chunked_v3_btree_v1() {
1056 let msg = DataLayoutMessage::chunked_v3_btree_v1(vec![8, 4], 0x1234);
1058 let encoded = msg.encode(&ctx8());
1059 assert_eq!(encoded.len(), 19);
1061 assert_eq!(encoded[0], 3);
1062 assert_eq!(encoded[1], 2);
1063 assert_eq!(encoded[2], 2); let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
1065 assert_eq!(consumed, encoded.len());
1066 assert_eq!(decoded, msg);
1067 }
1068
1069 #[test]
1070 fn roundtrip_chunked_v3_btree_v1_2d_ctx4() {
1071 let msg = DataLayoutMessage::chunked_v3_btree_v1(vec![2, 3, 8], 0x800);
1073 let encoded = msg.encode(&ctx4());
1074 assert_eq!(encoded.len(), 19);
1076 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx4()).unwrap();
1077 assert_eq!(consumed, encoded.len());
1078 assert_eq!(decoded, msg);
1079 }
1080
1081 #[test]
1082 fn chunked_v3_undef_btree_addr() {
1083 let msg = DataLayoutMessage::chunked_v3_btree_v1(vec![16, 4], UNDEF_ADDR);
1084 let encoded = msg.encode(&ctx8());
1085 let (decoded, _) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
1086 match decoded {
1087 DataLayoutMessage::ChunkedV3 { b_tree_address, .. } => {
1088 assert_eq!(b_tree_address, UNDEF_ADDR);
1089 }
1090 _ => panic!("expected ChunkedV3"),
1091 }
1092 }
1093
1094 #[test]
1095 fn chunked_v3_rejects_ndims_too_small() {
1096 let buf = [3u8, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
1098 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
1099 assert!(matches!(err, FormatError::InvalidData(_)));
1100 }
1101
1102 #[test]
1103 fn chunked_v3_rejects_zero_dim() {
1104 let mut buf = vec![3u8, 2, 2];
1106 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();
1110 assert!(matches!(err, FormatError::InvalidData(_)));
1111 }
1112
1113 #[test]
1114 fn chunked_v3_truncated() {
1115 let buf = [3u8, 2, 2];
1117 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
1118 assert!(matches!(err, FormatError::BufferTooShort { .. }));
1119 }
1120
1121 #[test]
1122 fn chunked_v4_large_dims() {
1123 let params = EarrayParams::default_params();
1125 let msg = DataLayoutMessage::chunked_v4_earray(4, vec![1, 65536], params, 0x4000);
1126 let encoded = msg.encode(&ctx8());
1127 assert_eq!(encoded[4], 3); }
1129}