1use crate::format::bytes::{read_le_addr as read_addr, read_le_uint as read_size};
32use crate::format::{FormatContext, FormatError, FormatResult, UNDEF_ADDR};
33
34const VERSION_3: u8 = 3;
35const VERSION_4: u8 = 4;
36const VERSION_5: u8 = 5;
41const CLASS_COMPACT: u8 = 0;
42const CLASS_CONTIGUOUS: u8 = 1;
43const CLASS_CHUNKED: u8 = 2;
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47#[repr(u8)]
48pub enum ChunkIndexType {
49 SingleChunk = 1,
50 Implicit = 2,
51 FixedArray = 3,
52 ExtensibleArray = 4,
53 BTreeV2 = 5,
54}
55
56impl ChunkIndexType {
57 pub fn from_u8(v: u8) -> Option<Self> {
58 match v {
59 1 => Some(Self::SingleChunk),
60 2 => Some(Self::Implicit),
61 3 => Some(Self::FixedArray),
62 4 => Some(Self::ExtensibleArray),
63 5 => Some(Self::BTreeV2),
64 _ => None,
65 }
66 }
67}
68
69#[derive(Debug, Clone, PartialEq, Eq)]
71pub struct EarrayParams {
72 pub max_nelmts_bits: u8,
73 pub idx_blk_elmts: u8,
74 pub sup_blk_min_data_ptrs: u8,
75 pub data_blk_min_elmts: u8,
76 pub max_dblk_page_nelmts_bits: u8,
77}
78
79impl EarrayParams {
80 pub fn default_params() -> Self {
82 Self {
83 max_nelmts_bits: 32,
84 idx_blk_elmts: 4,
85 sup_blk_min_data_ptrs: 4,
86 data_blk_min_elmts: 16,
87 max_dblk_page_nelmts_bits: 10,
88 }
89 }
90}
91
92#[derive(Debug, Clone, PartialEq, Eq)]
94pub struct FixedArrayParams {
95 pub max_dblk_page_nelmts_bits: u8,
96}
97
98impl FixedArrayParams {
99 pub fn default_params() -> Self {
100 Self {
101 max_dblk_page_nelmts_bits: 10,
105 }
106 }
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118pub struct SingleChunkFilter {
119 pub nbytes: u64,
121 pub filter_mask: u32,
124}
125
126#[derive(Debug, Clone, PartialEq)]
128pub enum DataLayoutMessage {
129 Contiguous {
131 address: u64,
133 size: u64,
135 },
136 Compact {
138 data: Vec<u8>,
140 },
141 ChunkedV3 {
146 chunk_dims: Vec<u64>,
149 b_tree_address: u64,
151 },
152 ChunkedV4 {
154 flags: u8,
155 chunk_dims: Vec<u64>,
157 index_type: ChunkIndexType,
159 earray_params: Option<EarrayParams>,
161 farray_params: Option<FixedArrayParams>,
163 single_chunk_filter: Option<SingleChunkFilter>,
166 index_address: u64,
168 },
169}
170
171impl DataLayoutMessage {
172 pub fn contiguous_unallocated(size: u64) -> Self {
174 Self::Contiguous {
175 address: UNDEF_ADDR,
176 size,
177 }
178 }
179
180 pub fn contiguous(address: u64, size: u64) -> Self {
182 Self::Contiguous { address, size }
183 }
184
185 pub fn compact(data: Vec<u8>) -> Self {
187 Self::Compact { data }
188 }
189
190 pub fn chunked_v3_btree_v1(chunk_dims: Vec<u64>, b_tree_address: u64) -> Self {
194 Self::ChunkedV3 {
195 chunk_dims,
196 b_tree_address,
197 }
198 }
199
200 pub fn chunked_v4_earray(
206 chunk_dims: Vec<u64>,
207 earray_params: EarrayParams,
208 index_address: u64,
209 ) -> Self {
210 Self::ChunkedV4 {
211 flags: 0,
212 chunk_dims,
213 index_type: ChunkIndexType::ExtensibleArray,
214 earray_params: Some(earray_params),
215 farray_params: None,
216 single_chunk_filter: None,
217 index_address,
218 }
219 }
220
221 pub fn chunked_v4_farray(
225 chunk_dims: Vec<u64>,
226 farray_params: FixedArrayParams,
227 index_address: u64,
228 ) -> Self {
229 Self::ChunkedV4 {
230 flags: 0,
231 chunk_dims,
232 index_type: ChunkIndexType::FixedArray,
233 earray_params: None,
234 farray_params: Some(farray_params),
235 single_chunk_filter: None,
236 index_address,
237 }
238 }
239
240 pub fn chunked_v4_btree_v2(chunk_dims: Vec<u64>, index_address: u64) -> Self {
244 Self::ChunkedV4 {
245 flags: 0,
246 chunk_dims,
247 index_type: ChunkIndexType::BTreeV2,
248 earray_params: None,
249 farray_params: None,
250 single_chunk_filter: None,
251 index_address,
252 }
253 }
254
255 pub fn chunked_v4_single(chunk_dims: Vec<u64>, index_address: u64) -> Self {
259 Self::ChunkedV4 {
260 flags: 0,
261 chunk_dims,
262 index_type: ChunkIndexType::SingleChunk,
263 earray_params: None,
264 farray_params: None,
265 single_chunk_filter: None,
266 index_address,
267 }
268 }
269
270 pub fn encode(&self, ctx: &FormatContext) -> Vec<u8> {
273 match self {
274 Self::Contiguous { address, size } => {
275 let sa = ctx.sizeof_addr as usize;
276 let ss = ctx.sizeof_size as usize;
277 let mut buf = Vec::with_capacity(2 + sa + ss);
278 buf.push(VERSION_3);
279 buf.push(CLASS_CONTIGUOUS);
280 buf.extend_from_slice(&address.to_le_bytes()[..sa]);
281 buf.extend_from_slice(&size.to_le_bytes()[..ss]);
282 buf
283 }
284 Self::Compact { data } => {
285 let mut buf = Vec::with_capacity(2 + 2 + data.len());
286 buf.push(VERSION_3);
287 buf.push(CLASS_COMPACT);
288 buf.extend_from_slice(&(data.len() as u16).to_le_bytes());
289 buf.extend_from_slice(data);
290 buf
291 }
292 Self::ChunkedV3 {
293 chunk_dims,
294 b_tree_address,
295 } => {
296 let sa = ctx.sizeof_addr as usize;
297 let ndims = chunk_dims.len() as u8;
298 let mut buf = Vec::with_capacity(3 + sa + chunk_dims.len() * 4);
299 buf.push(VERSION_3);
300 buf.push(CLASS_CHUNKED);
301 buf.push(ndims);
302 buf.extend_from_slice(&b_tree_address.to_le_bytes()[..sa]);
303 for &d in chunk_dims {
305 buf.extend_from_slice(&(d as u32).to_le_bytes());
306 }
307 buf
308 }
309 Self::ChunkedV4 {
310 flags,
311 chunk_dims,
312 index_type,
313 earray_params,
314 farray_params,
315 single_chunk_filter,
316 index_address,
317 } => {
318 let sa = ctx.sizeof_addr as usize;
319 let ndims = chunk_dims.len() as u8;
320
321 let max_dim = chunk_dims.iter().copied().max().unwrap_or(1);
324 let enc_bytes = enc_bytes_for_value(max_dim);
325
326 let mut buf = Vec::with_capacity(64);
327 buf.push(VERSION_4);
328 buf.push(CLASS_CHUNKED);
329 buf.push(*flags);
330 buf.push(ndims);
331 buf.push(enc_bytes);
332
333 for &d in chunk_dims {
335 buf.extend_from_slice(&d.to_le_bytes()[..enc_bytes as usize]);
336 }
337
338 buf.push(*index_type as u8);
340
341 match *index_type {
343 ChunkIndexType::ExtensibleArray => {
344 if let Some(ref params) = earray_params {
345 buf.push(params.max_nelmts_bits);
346 buf.push(params.idx_blk_elmts);
347 buf.push(params.sup_blk_min_data_ptrs);
348 buf.push(params.data_blk_min_elmts);
349 buf.push(params.max_dblk_page_nelmts_bits);
350 }
351 }
352 ChunkIndexType::FixedArray => {
353 if let Some(ref params) = farray_params {
354 buf.push(params.max_dblk_page_nelmts_bits);
355 }
356 }
357 ChunkIndexType::BTreeV2 => {
358 buf.extend_from_slice(&2048u32.to_le_bytes());
363 buf.push(100);
364 buf.push(40);
365 }
366 ChunkIndexType::SingleChunk => {
367 if *flags & 0x02 != 0 {
373 if let Some(scf) = single_chunk_filter {
374 let ss = ctx.sizeof_size as usize;
375 buf.extend_from_slice(&scf.nbytes.to_le_bytes()[..ss]);
376 buf.extend_from_slice(&scf.filter_mask.to_le_bytes());
377 }
378 }
379 }
380 _ => {}
382 }
383
384 buf.extend_from_slice(&index_address.to_le_bytes()[..sa]);
386
387 buf
388 }
389 }
390 }
391
392 pub fn decode(buf: &[u8], ctx: &FormatContext) -> FormatResult<(Self, usize)> {
395 if buf.len() < 2 {
396 return Err(FormatError::BufferTooShort {
397 needed: 2,
398 available: buf.len(),
399 });
400 }
401
402 let version = buf[0];
403 let class = buf[1];
404
405 match (version, class) {
406 (VERSION_3, CLASS_CONTIGUOUS) => {
407 let sa = ctx.sizeof_addr as usize;
408 let ss = ctx.sizeof_size as usize;
409 let mut pos = 2;
410 let needed = pos + sa + ss;
411 if buf.len() < needed {
412 return Err(FormatError::BufferTooShort {
413 needed,
414 available: buf.len(),
415 });
416 }
417 let address = read_addr(&buf[pos..], sa);
418 pos += sa;
419 let size = read_size(&buf[pos..], ss);
420 pos += ss;
421 Ok((Self::Contiguous { address, size }, pos))
422 }
423 (VERSION_3, CLASS_COMPACT) => {
424 let mut pos = 2;
425 if buf.len() < pos + 2 {
426 return Err(FormatError::BufferTooShort {
427 needed: pos + 2,
428 available: buf.len(),
429 });
430 }
431 let compact_size = u16::from_le_bytes([buf[pos], buf[pos + 1]]) as usize;
432 pos += 2;
433 if buf.len() < pos + compact_size {
434 return Err(FormatError::BufferTooShort {
435 needed: pos + compact_size,
436 available: buf.len(),
437 });
438 }
439 let data = buf[pos..pos + compact_size].to_vec();
440 pos += compact_size;
441 Ok((Self::Compact { data }, pos))
442 }
443 (VERSION_3, CLASS_CHUNKED) => {
444 let sa = ctx.sizeof_addr as usize;
447 let mut pos = 2;
448 if buf.len() < pos + 1 {
449 return Err(FormatError::BufferTooShort {
450 needed: pos + 1,
451 available: buf.len(),
452 });
453 }
454 let ndims = buf[pos] as usize;
455 pos += 1;
456
457 if ndims < 2 {
461 return Err(FormatError::InvalidData(format!(
462 "chunked v3 layout dimensionality {ndims} is too small"
463 )));
464 }
465
466 if buf.len() < pos + sa {
467 return Err(FormatError::BufferTooShort {
468 needed: pos + sa,
469 available: buf.len(),
470 });
471 }
472 let b_tree_address = read_addr(&buf[pos..], sa);
473 pos += sa;
474
475 let dim_data_len = ndims * 4;
476 if buf.len() < pos + dim_data_len {
477 return Err(FormatError::BufferTooShort {
478 needed: pos + dim_data_len,
479 available: buf.len(),
480 });
481 }
482 let mut chunk_dims = Vec::with_capacity(ndims);
483 for _ in 0..ndims {
484 let d = u32::from_le_bytes([buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]])
485 as u64;
486 if d == 0 {
487 return Err(FormatError::InvalidData(
488 "chunked v3 layout has a zero chunk dimension".into(),
489 ));
490 }
491 chunk_dims.push(d);
492 pos += 4;
493 }
494
495 Ok((
496 Self::ChunkedV3 {
497 chunk_dims,
498 b_tree_address,
499 },
500 pos,
501 ))
502 }
503 (VERSION_4 | VERSION_5, CLASS_CHUNKED) => {
504 let sa = ctx.sizeof_addr as usize;
505 let mut pos = 2;
506
507 if buf.len() < pos + 3 {
509 return Err(FormatError::BufferTooShort {
510 needed: pos + 3,
511 available: buf.len(),
512 });
513 }
514 let flags = buf[pos];
515 pos += 1;
516 let ndims = buf[pos] as usize;
517 pos += 1;
518 let enc_bytes = buf[pos] as usize;
519 pos += 1;
520
521 if !(1..=8).contains(&enc_bytes) {
524 return Err(FormatError::InvalidData(format!(
525 "chunked layout encoded dimension size {enc_bytes} is out of range"
526 )));
527 }
528 if ndims < 2 {
531 return Err(FormatError::InvalidData(format!(
532 "chunked v4 layout dimensionality {ndims} is too small"
533 )));
534 }
535
536 let dim_data_len = ndims * enc_bytes;
538 if buf.len() < pos + dim_data_len {
539 return Err(FormatError::BufferTooShort {
540 needed: pos + dim_data_len,
541 available: buf.len(),
542 });
543 }
544 let mut chunk_dims = Vec::with_capacity(ndims);
545 for _ in 0..ndims {
546 let d = read_size(&buf[pos..], enc_bytes);
547 if d == 0 {
548 return Err(FormatError::InvalidData(
549 "chunked v4 layout has a zero chunk dimension".into(),
550 ));
551 }
552 chunk_dims.push(d);
553 pos += enc_bytes;
554 }
555
556 if buf.len() < pos + 1 {
558 return Err(FormatError::BufferTooShort {
559 needed: pos + 1,
560 available: buf.len(),
561 });
562 }
563 let idx_type_raw = buf[pos];
564 pos += 1;
565 let index_type = ChunkIndexType::from_u8(idx_type_raw).ok_or_else(|| {
566 FormatError::UnsupportedFeature(format!("chunk index type {}", idx_type_raw))
567 })?;
568
569 let mut earray_params = None;
571 let mut farray_params = None;
572 let mut single_chunk_filter = None;
573
574 match index_type {
575 ChunkIndexType::ExtensibleArray => {
576 if buf.len() < pos + 5 {
577 return Err(FormatError::BufferTooShort {
578 needed: pos + 5,
579 available: buf.len(),
580 });
581 }
582 let ep = EarrayParams {
583 max_nelmts_bits: buf[pos],
584 idx_blk_elmts: buf[pos + 1],
585 sup_blk_min_data_ptrs: buf[pos + 2],
586 data_blk_min_elmts: buf[pos + 3],
587 max_dblk_page_nelmts_bits: buf[pos + 4],
588 };
589 if ep.max_nelmts_bits == 0
591 || ep.idx_blk_elmts == 0
592 || ep.sup_blk_min_data_ptrs == 0
593 || ep.data_blk_min_elmts == 0
594 || ep.max_dblk_page_nelmts_bits == 0
595 {
596 return Err(FormatError::InvalidData(
597 "extensible-array layout parameter is zero".into(),
598 ));
599 }
600 earray_params = Some(ep);
601 pos += 5;
602 }
603 ChunkIndexType::FixedArray => {
604 if buf.len() < pos + 1 {
605 return Err(FormatError::BufferTooShort {
606 needed: pos + 1,
607 available: buf.len(),
608 });
609 }
610 farray_params = Some(FixedArrayParams {
616 max_dblk_page_nelmts_bits: buf[pos],
617 });
618 pos += 1;
619 }
620 ChunkIndexType::BTreeV2 => {
621 if buf.len() < pos + 6 {
625 return Err(FormatError::BufferTooShort {
626 needed: pos + 6,
627 available: buf.len(),
628 });
629 }
630 pos += 6;
631 }
632 ChunkIndexType::SingleChunk if flags & 0x02 != 0 => {
639 let ss = ctx.sizeof_size as usize;
640 let extra = ss + 4;
641 if buf.len() < pos + extra {
642 return Err(FormatError::BufferTooShort {
643 needed: pos + extra,
644 available: buf.len(),
645 });
646 }
647 let nbytes = read_size(&buf[pos..], ss);
648 pos += ss;
649 let filter_mask = u32::from_le_bytes([
650 buf[pos],
651 buf[pos + 1],
652 buf[pos + 2],
653 buf[pos + 3],
654 ]);
655 pos += 4;
656 single_chunk_filter = Some(SingleChunkFilter {
657 nbytes,
658 filter_mask,
659 });
660 }
661 _ => {}
664 }
665
666 if buf.len() < pos + sa {
668 return Err(FormatError::BufferTooShort {
669 needed: pos + sa,
670 available: buf.len(),
671 });
672 }
673 let index_address = read_addr(&buf[pos..], sa);
674 pos += sa;
675
676 Ok((
677 Self::ChunkedV4 {
678 flags,
679 chunk_dims,
680 index_type,
681 earray_params,
682 farray_params,
683 single_chunk_filter,
684 index_address,
685 },
686 pos,
687 ))
688 }
689 (VERSION_3, other) => Err(FormatError::UnsupportedFeature(format!(
690 "data layout class {}",
691 other
692 ))),
693 (v, _) => Err(FormatError::InvalidVersion(v)),
694 }
695 }
696}
697
698fn enc_bytes_for_value(v: u64) -> u8 {
702 if v == 0 {
703 return 1;
704 }
705 let bits_needed = 64 - v.leading_zeros(); bits_needed.div_ceil(8) as u8
707}
708
709#[cfg(test)]
712mod tests {
713 use super::*;
714
715 fn ctx8() -> FormatContext {
716 FormatContext {
717 sizeof_addr: 8,
718 sizeof_size: 8,
719 }
720 }
721
722 fn ctx4() -> FormatContext {
723 FormatContext {
724 sizeof_addr: 4,
725 sizeof_size: 4,
726 }
727 }
728
729 #[test]
730 fn roundtrip_contiguous() {
731 let msg = DataLayoutMessage::contiguous(0x1000, 4096);
732 let encoded = msg.encode(&ctx8());
733 assert_eq!(encoded.len(), 18);
735 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
736 assert_eq!(consumed, 18);
737 assert_eq!(decoded, msg);
738 }
739
740 #[test]
741 fn roundtrip_contiguous_ctx4() {
742 let msg = DataLayoutMessage::contiguous(0x800, 256);
743 let encoded = msg.encode(&ctx4());
744 assert_eq!(encoded.len(), 10);
746 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx4()).unwrap();
747 assert_eq!(consumed, 10);
748 assert_eq!(decoded, msg);
749 }
750
751 #[test]
752 fn roundtrip_contiguous_unallocated() {
753 let msg = DataLayoutMessage::contiguous_unallocated(1024);
754 let encoded = msg.encode(&ctx8());
755 let (decoded, _) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
756 assert_eq!(decoded, msg);
757 match decoded {
758 DataLayoutMessage::Contiguous { address, size } => {
759 assert_eq!(address, UNDEF_ADDR);
760 assert_eq!(size, 1024);
761 }
762 _ => panic!("expected Contiguous"),
763 }
764 }
765
766 #[test]
767 fn roundtrip_contiguous_undef_ctx4() {
768 let msg = DataLayoutMessage::contiguous_unallocated(512);
769 let encoded = msg.encode(&ctx4());
770 let (decoded, _) = DataLayoutMessage::decode(&encoded, &ctx4()).unwrap();
771 match decoded {
772 DataLayoutMessage::Contiguous { address, .. } => {
773 assert_eq!(address, UNDEF_ADDR);
774 }
775 _ => panic!("expected Contiguous"),
776 }
777 }
778
779 #[test]
780 fn roundtrip_compact() {
781 let data = vec![1, 2, 3, 4, 5, 6, 7, 8];
782 let msg = DataLayoutMessage::compact(data.clone());
783 let encoded = msg.encode(&ctx8());
784 assert_eq!(encoded.len(), 12);
786 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
787 assert_eq!(consumed, 12);
788 assert_eq!(decoded, msg);
789 }
790
791 #[test]
792 fn roundtrip_compact_empty() {
793 let msg = DataLayoutMessage::compact(vec![]);
794 let encoded = msg.encode(&ctx8());
795 assert_eq!(encoded.len(), 4); let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
797 assert_eq!(consumed, 4);
798 assert_eq!(decoded, msg);
799 }
800
801 #[test]
802 fn decode_bad_version() {
803 let buf = [2u8, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
804 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
805 match err {
806 FormatError::InvalidVersion(2) => {}
807 other => panic!("unexpected error: {:?}", other),
808 }
809 }
810
811 #[test]
812 fn decode_unsupported_class() {
813 let buf = [3u8, 3]; let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
815 match err {
816 FormatError::UnsupportedFeature(_) => {}
817 other => panic!("unexpected error: {:?}", other),
818 }
819 }
820
821 #[test]
822 fn decode_buffer_too_short() {
823 let buf = [3u8];
824 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
825 match err {
826 FormatError::BufferTooShort { .. } => {}
827 other => panic!("unexpected error: {:?}", other),
828 }
829 }
830
831 #[test]
832 fn decode_contiguous_truncated() {
833 let buf = [3u8, 1, 0, 0];
835 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
836 match err {
837 FormatError::BufferTooShort { .. } => {}
838 other => panic!("unexpected error: {:?}", other),
839 }
840 }
841
842 #[test]
843 fn version_and_class_bytes() {
844 let encoded = DataLayoutMessage::contiguous(0, 0).encode(&ctx8());
845 assert_eq!(encoded[0], 3);
846 assert_eq!(encoded[1], 1);
847
848 let encoded = DataLayoutMessage::compact(vec![]).encode(&ctx8());
849 assert_eq!(encoded[0], 3);
850 assert_eq!(encoded[1], 0);
851 }
852
853 #[test]
854 fn roundtrip_chunked_v4_earray() {
855 let params = EarrayParams::default_params();
856 let msg = DataLayoutMessage::chunked_v4_earray(vec![1, 256, 256], params, 0x2000);
857 let encoded = msg.encode(&ctx8());
858 assert_eq!(encoded[0], 4); assert_eq!(encoded[1], 2); let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
861 assert_eq!(consumed, encoded.len());
862 assert_eq!(decoded, msg);
863 }
864
865 #[test]
866 fn roundtrip_chunked_v4_earray_ctx4() {
867 let params = EarrayParams::default_params();
868 let msg = DataLayoutMessage::chunked_v4_earray(vec![1, 128], params, 0x1000);
869 let encoded = msg.encode(&ctx4());
870 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx4()).unwrap();
871 assert_eq!(consumed, encoded.len());
872 assert_eq!(decoded, msg);
873 }
874
875 #[test]
876 fn roundtrip_chunked_v4_single() {
877 let msg = DataLayoutMessage::chunked_v4_single(vec![100, 200], 0x3000);
878 let encoded = msg.encode(&ctx8());
879 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
880 assert_eq!(consumed, encoded.len());
881 assert_eq!(decoded, msg);
882 }
883
884 #[test]
889 fn roundtrip_chunked_v4_single_filtered() {
890 for ctx in [ctx8(), ctx4()] {
891 let msg = DataLayoutMessage::ChunkedV4 {
892 flags: 0x02,
893 chunk_dims: vec![100, 200, 4],
894 index_type: ChunkIndexType::SingleChunk,
895 earray_params: None,
896 farray_params: None,
897 single_chunk_filter: Some(SingleChunkFilter {
898 nbytes: 12345,
899 filter_mask: 0b101,
900 }),
901 index_address: 0x3000,
902 };
903 let encoded = msg.encode(&ctx);
904 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx).unwrap();
905 assert_eq!(consumed, encoded.len());
906 assert_eq!(decoded, msg);
907 match decoded {
909 DataLayoutMessage::ChunkedV4 {
910 single_chunk_filter: Some(scf),
911 ..
912 } => {
913 assert_eq!(scf.nbytes, 12345);
914 assert_eq!(scf.filter_mask, 0b101);
915 }
916 other => panic!("expected filtered single-chunk layout, got {other:?}"),
917 }
918 }
919 }
920
921 #[test]
922 fn chunked_v4_enc_bytes() {
923 let params = EarrayParams::default_params();
925 let msg = DataLayoutMessage::chunked_v4_earray(vec![1, 256, 256], params, 0x2000);
926 let encoded = msg.encode(&ctx8());
927 assert_eq!(encoded.len(), 25);
930 assert_eq!(encoded[4], 2); }
932
933 #[test]
934 fn roundtrip_chunked_v3_btree_v1() {
935 let msg = DataLayoutMessage::chunked_v3_btree_v1(vec![8, 4], 0x1234);
937 let encoded = msg.encode(&ctx8());
938 assert_eq!(encoded.len(), 19);
940 assert_eq!(encoded[0], 3);
941 assert_eq!(encoded[1], 2);
942 assert_eq!(encoded[2], 2); let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
944 assert_eq!(consumed, encoded.len());
945 assert_eq!(decoded, msg);
946 }
947
948 #[test]
949 fn roundtrip_chunked_v3_btree_v1_2d_ctx4() {
950 let msg = DataLayoutMessage::chunked_v3_btree_v1(vec![2, 3, 8], 0x800);
952 let encoded = msg.encode(&ctx4());
953 assert_eq!(encoded.len(), 19);
955 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx4()).unwrap();
956 assert_eq!(consumed, encoded.len());
957 assert_eq!(decoded, msg);
958 }
959
960 #[test]
961 fn chunked_v3_undef_btree_addr() {
962 let msg = DataLayoutMessage::chunked_v3_btree_v1(vec![16, 4], UNDEF_ADDR);
963 let encoded = msg.encode(&ctx8());
964 let (decoded, _) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
965 match decoded {
966 DataLayoutMessage::ChunkedV3 { b_tree_address, .. } => {
967 assert_eq!(b_tree_address, UNDEF_ADDR);
968 }
969 _ => panic!("expected ChunkedV3"),
970 }
971 }
972
973 #[test]
974 fn chunked_v3_rejects_ndims_too_small() {
975 let buf = [3u8, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
977 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
978 assert!(matches!(err, FormatError::InvalidData(_)));
979 }
980
981 #[test]
982 fn chunked_v3_rejects_zero_dim() {
983 let mut buf = vec![3u8, 2, 2];
985 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();
989 assert!(matches!(err, FormatError::InvalidData(_)));
990 }
991
992 #[test]
993 fn chunked_v3_truncated() {
994 let buf = [3u8, 2, 2];
996 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
997 assert!(matches!(err, FormatError::BufferTooShort { .. }));
998 }
999
1000 #[test]
1001 fn chunked_v4_large_dims() {
1002 let params = EarrayParams::default_params();
1004 let msg = DataLayoutMessage::chunked_v4_earray(vec![1, 65536], params, 0x4000);
1005 let encoded = msg.encode(&ctx8());
1006 assert_eq!(encoded[4], 3); }
1008}