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 if *flags & 0x02 != 0 => {
372 if let Some(scf) = single_chunk_filter {
373 let ss = ctx.sizeof_size as usize;
374 buf.extend_from_slice(&scf.nbytes.to_le_bytes()[..ss]);
375 buf.extend_from_slice(&scf.filter_mask.to_le_bytes());
376 }
377 }
378 _ => {}
380 }
381
382 buf.extend_from_slice(&index_address.to_le_bytes()[..sa]);
384
385 buf
386 }
387 }
388 }
389
390 pub fn decode(buf: &[u8], ctx: &FormatContext) -> FormatResult<(Self, usize)> {
393 if buf.len() < 2 {
394 return Err(FormatError::BufferTooShort {
395 needed: 2,
396 available: buf.len(),
397 });
398 }
399
400 let version = buf[0];
401 let class = buf[1];
402
403 match (version, class) {
404 (VERSION_3, CLASS_CONTIGUOUS) => {
405 let sa = ctx.sizeof_addr as usize;
406 let ss = ctx.sizeof_size as usize;
407 let mut pos = 2;
408 let needed = pos + sa + ss;
409 if buf.len() < needed {
410 return Err(FormatError::BufferTooShort {
411 needed,
412 available: buf.len(),
413 });
414 }
415 let address = read_addr(&buf[pos..], sa);
416 pos += sa;
417 let size = read_size(&buf[pos..], ss);
418 pos += ss;
419 Ok((Self::Contiguous { address, size }, pos))
420 }
421 (VERSION_3, CLASS_COMPACT) => {
422 let mut pos = 2;
423 if buf.len() < pos + 2 {
424 return Err(FormatError::BufferTooShort {
425 needed: pos + 2,
426 available: buf.len(),
427 });
428 }
429 let compact_size = u16::from_le_bytes([buf[pos], buf[pos + 1]]) as usize;
430 pos += 2;
431 if buf.len() < pos + compact_size {
432 return Err(FormatError::BufferTooShort {
433 needed: pos + compact_size,
434 available: buf.len(),
435 });
436 }
437 let data = buf[pos..pos + compact_size].to_vec();
438 pos += compact_size;
439 Ok((Self::Compact { data }, pos))
440 }
441 (VERSION_3, CLASS_CHUNKED) => {
442 let sa = ctx.sizeof_addr as usize;
445 let mut pos = 2;
446 if buf.len() < pos + 1 {
447 return Err(FormatError::BufferTooShort {
448 needed: pos + 1,
449 available: buf.len(),
450 });
451 }
452 let ndims = buf[pos] as usize;
453 pos += 1;
454
455 if ndims < 2 {
459 return Err(FormatError::InvalidData(format!(
460 "chunked v3 layout dimensionality {ndims} is too small"
461 )));
462 }
463
464 if buf.len() < pos + sa {
465 return Err(FormatError::BufferTooShort {
466 needed: pos + sa,
467 available: buf.len(),
468 });
469 }
470 let b_tree_address = read_addr(&buf[pos..], sa);
471 pos += sa;
472
473 let dim_data_len = ndims * 4;
474 if buf.len() < pos + dim_data_len {
475 return Err(FormatError::BufferTooShort {
476 needed: pos + dim_data_len,
477 available: buf.len(),
478 });
479 }
480 let mut chunk_dims = Vec::with_capacity(ndims);
481 for _ in 0..ndims {
482 let d = u32::from_le_bytes([buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]])
483 as u64;
484 if d == 0 {
485 return Err(FormatError::InvalidData(
486 "chunked v3 layout has a zero chunk dimension".into(),
487 ));
488 }
489 chunk_dims.push(d);
490 pos += 4;
491 }
492
493 Ok((
494 Self::ChunkedV3 {
495 chunk_dims,
496 b_tree_address,
497 },
498 pos,
499 ))
500 }
501 (VERSION_4 | VERSION_5, CLASS_CHUNKED) => {
502 let sa = ctx.sizeof_addr as usize;
503 let mut pos = 2;
504
505 if buf.len() < pos + 3 {
507 return Err(FormatError::BufferTooShort {
508 needed: pos + 3,
509 available: buf.len(),
510 });
511 }
512 let flags = buf[pos];
513 pos += 1;
514 let ndims = buf[pos] as usize;
515 pos += 1;
516 let enc_bytes = buf[pos] as usize;
517 pos += 1;
518
519 if !(1..=8).contains(&enc_bytes) {
522 return Err(FormatError::InvalidData(format!(
523 "chunked layout encoded dimension size {enc_bytes} is out of range"
524 )));
525 }
526 if ndims < 2 {
529 return Err(FormatError::InvalidData(format!(
530 "chunked v4 layout dimensionality {ndims} is too small"
531 )));
532 }
533
534 let dim_data_len = ndims * enc_bytes;
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 = read_size(&buf[pos..], enc_bytes);
545 if d == 0 {
546 return Err(FormatError::InvalidData(
547 "chunked v4 layout has a zero chunk dimension".into(),
548 ));
549 }
550 chunk_dims.push(d);
551 pos += enc_bytes;
552 }
553
554 if buf.len() < pos + 1 {
556 return Err(FormatError::BufferTooShort {
557 needed: pos + 1,
558 available: buf.len(),
559 });
560 }
561 let idx_type_raw = buf[pos];
562 pos += 1;
563 let index_type = ChunkIndexType::from_u8(idx_type_raw).ok_or_else(|| {
564 FormatError::UnsupportedFeature(format!("chunk index type {}", idx_type_raw))
565 })?;
566
567 let mut earray_params = None;
569 let mut farray_params = None;
570 let mut single_chunk_filter = None;
571
572 match index_type {
573 ChunkIndexType::ExtensibleArray => {
574 if buf.len() < pos + 5 {
575 return Err(FormatError::BufferTooShort {
576 needed: pos + 5,
577 available: buf.len(),
578 });
579 }
580 let ep = EarrayParams {
581 max_nelmts_bits: buf[pos],
582 idx_blk_elmts: buf[pos + 1],
583 sup_blk_min_data_ptrs: buf[pos + 2],
584 data_blk_min_elmts: buf[pos + 3],
585 max_dblk_page_nelmts_bits: buf[pos + 4],
586 };
587 if ep.max_nelmts_bits == 0
589 || ep.idx_blk_elmts == 0
590 || ep.sup_blk_min_data_ptrs == 0
591 || ep.data_blk_min_elmts == 0
592 || ep.max_dblk_page_nelmts_bits == 0
593 {
594 return Err(FormatError::InvalidData(
595 "extensible-array layout parameter is zero".into(),
596 ));
597 }
598 earray_params = Some(ep);
599 pos += 5;
600 }
601 ChunkIndexType::FixedArray => {
602 if buf.len() < pos + 1 {
603 return Err(FormatError::BufferTooShort {
604 needed: pos + 1,
605 available: buf.len(),
606 });
607 }
608 farray_params = Some(FixedArrayParams {
614 max_dblk_page_nelmts_bits: buf[pos],
615 });
616 pos += 1;
617 }
618 ChunkIndexType::BTreeV2 => {
619 if buf.len() < pos + 6 {
623 return Err(FormatError::BufferTooShort {
624 needed: pos + 6,
625 available: buf.len(),
626 });
627 }
628 pos += 6;
629 }
630 ChunkIndexType::SingleChunk if flags & 0x02 != 0 => {
637 let ss = ctx.sizeof_size as usize;
638 let extra = ss + 4;
639 if buf.len() < pos + extra {
640 return Err(FormatError::BufferTooShort {
641 needed: pos + extra,
642 available: buf.len(),
643 });
644 }
645 let nbytes = read_size(&buf[pos..], ss);
646 pos += ss;
647 let filter_mask = u32::from_le_bytes([
648 buf[pos],
649 buf[pos + 1],
650 buf[pos + 2],
651 buf[pos + 3],
652 ]);
653 pos += 4;
654 single_chunk_filter = Some(SingleChunkFilter {
655 nbytes,
656 filter_mask,
657 });
658 }
659 _ => {}
662 }
663
664 if buf.len() < pos + sa {
666 return Err(FormatError::BufferTooShort {
667 needed: pos + sa,
668 available: buf.len(),
669 });
670 }
671 let index_address = read_addr(&buf[pos..], sa);
672 pos += sa;
673
674 Ok((
675 Self::ChunkedV4 {
676 flags,
677 chunk_dims,
678 index_type,
679 earray_params,
680 farray_params,
681 single_chunk_filter,
682 index_address,
683 },
684 pos,
685 ))
686 }
687 (VERSION_3, other) => Err(FormatError::UnsupportedFeature(format!(
688 "data layout class {}",
689 other
690 ))),
691 (v, _) => Err(FormatError::InvalidVersion(v)),
692 }
693 }
694}
695
696fn enc_bytes_for_value(v: u64) -> u8 {
700 if v == 0 {
701 return 1;
702 }
703 let bits_needed = 64 - v.leading_zeros(); bits_needed.div_ceil(8) as u8
705}
706
707#[cfg(test)]
710mod tests {
711 use super::*;
712
713 fn ctx8() -> FormatContext {
714 FormatContext {
715 sizeof_addr: 8,
716 sizeof_size: 8,
717 }
718 }
719
720 fn ctx4() -> FormatContext {
721 FormatContext {
722 sizeof_addr: 4,
723 sizeof_size: 4,
724 }
725 }
726
727 #[test]
728 fn roundtrip_contiguous() {
729 let msg = DataLayoutMessage::contiguous(0x1000, 4096);
730 let encoded = msg.encode(&ctx8());
731 assert_eq!(encoded.len(), 18);
733 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
734 assert_eq!(consumed, 18);
735 assert_eq!(decoded, msg);
736 }
737
738 #[test]
739 fn roundtrip_contiguous_ctx4() {
740 let msg = DataLayoutMessage::contiguous(0x800, 256);
741 let encoded = msg.encode(&ctx4());
742 assert_eq!(encoded.len(), 10);
744 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx4()).unwrap();
745 assert_eq!(consumed, 10);
746 assert_eq!(decoded, msg);
747 }
748
749 #[test]
750 fn roundtrip_contiguous_unallocated() {
751 let msg = DataLayoutMessage::contiguous_unallocated(1024);
752 let encoded = msg.encode(&ctx8());
753 let (decoded, _) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
754 assert_eq!(decoded, msg);
755 match decoded {
756 DataLayoutMessage::Contiguous { address, size } => {
757 assert_eq!(address, UNDEF_ADDR);
758 assert_eq!(size, 1024);
759 }
760 _ => panic!("expected Contiguous"),
761 }
762 }
763
764 #[test]
765 fn roundtrip_contiguous_undef_ctx4() {
766 let msg = DataLayoutMessage::contiguous_unallocated(512);
767 let encoded = msg.encode(&ctx4());
768 let (decoded, _) = DataLayoutMessage::decode(&encoded, &ctx4()).unwrap();
769 match decoded {
770 DataLayoutMessage::Contiguous { address, .. } => {
771 assert_eq!(address, UNDEF_ADDR);
772 }
773 _ => panic!("expected Contiguous"),
774 }
775 }
776
777 #[test]
778 fn roundtrip_compact() {
779 let data = vec![1, 2, 3, 4, 5, 6, 7, 8];
780 let msg = DataLayoutMessage::compact(data.clone());
781 let encoded = msg.encode(&ctx8());
782 assert_eq!(encoded.len(), 12);
784 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
785 assert_eq!(consumed, 12);
786 assert_eq!(decoded, msg);
787 }
788
789 #[test]
790 fn roundtrip_compact_empty() {
791 let msg = DataLayoutMessage::compact(vec![]);
792 let encoded = msg.encode(&ctx8());
793 assert_eq!(encoded.len(), 4); let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
795 assert_eq!(consumed, 4);
796 assert_eq!(decoded, msg);
797 }
798
799 #[test]
800 fn decode_bad_version() {
801 let buf = [2u8, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
802 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
803 match err {
804 FormatError::InvalidVersion(2) => {}
805 other => panic!("unexpected error: {:?}", other),
806 }
807 }
808
809 #[test]
810 fn decode_unsupported_class() {
811 let buf = [3u8, 3]; let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
813 match err {
814 FormatError::UnsupportedFeature(_) => {}
815 other => panic!("unexpected error: {:?}", other),
816 }
817 }
818
819 #[test]
820 fn decode_buffer_too_short() {
821 let buf = [3u8];
822 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
823 match err {
824 FormatError::BufferTooShort { .. } => {}
825 other => panic!("unexpected error: {:?}", other),
826 }
827 }
828
829 #[test]
830 fn decode_contiguous_truncated() {
831 let buf = [3u8, 1, 0, 0];
833 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
834 match err {
835 FormatError::BufferTooShort { .. } => {}
836 other => panic!("unexpected error: {:?}", other),
837 }
838 }
839
840 #[test]
841 fn version_and_class_bytes() {
842 let encoded = DataLayoutMessage::contiguous(0, 0).encode(&ctx8());
843 assert_eq!(encoded[0], 3);
844 assert_eq!(encoded[1], 1);
845
846 let encoded = DataLayoutMessage::compact(vec![]).encode(&ctx8());
847 assert_eq!(encoded[0], 3);
848 assert_eq!(encoded[1], 0);
849 }
850
851 #[test]
852 fn roundtrip_chunked_v4_earray() {
853 let params = EarrayParams::default_params();
854 let msg = DataLayoutMessage::chunked_v4_earray(vec![1, 256, 256], params, 0x2000);
855 let encoded = msg.encode(&ctx8());
856 assert_eq!(encoded[0], 4); assert_eq!(encoded[1], 2); let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
859 assert_eq!(consumed, encoded.len());
860 assert_eq!(decoded, msg);
861 }
862
863 #[test]
864 fn roundtrip_chunked_v4_earray_ctx4() {
865 let params = EarrayParams::default_params();
866 let msg = DataLayoutMessage::chunked_v4_earray(vec![1, 128], params, 0x1000);
867 let encoded = msg.encode(&ctx4());
868 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx4()).unwrap();
869 assert_eq!(consumed, encoded.len());
870 assert_eq!(decoded, msg);
871 }
872
873 #[test]
874 fn roundtrip_chunked_v4_single() {
875 let msg = DataLayoutMessage::chunked_v4_single(vec![100, 200], 0x3000);
876 let encoded = msg.encode(&ctx8());
877 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
878 assert_eq!(consumed, encoded.len());
879 assert_eq!(decoded, msg);
880 }
881
882 #[test]
887 fn roundtrip_chunked_v4_single_filtered() {
888 for ctx in [ctx8(), ctx4()] {
889 let msg = DataLayoutMessage::ChunkedV4 {
890 flags: 0x02,
891 chunk_dims: vec![100, 200, 4],
892 index_type: ChunkIndexType::SingleChunk,
893 earray_params: None,
894 farray_params: None,
895 single_chunk_filter: Some(SingleChunkFilter {
896 nbytes: 12345,
897 filter_mask: 0b101,
898 }),
899 index_address: 0x3000,
900 };
901 let encoded = msg.encode(&ctx);
902 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx).unwrap();
903 assert_eq!(consumed, encoded.len());
904 assert_eq!(decoded, msg);
905 match decoded {
907 DataLayoutMessage::ChunkedV4 {
908 single_chunk_filter: Some(scf),
909 ..
910 } => {
911 assert_eq!(scf.nbytes, 12345);
912 assert_eq!(scf.filter_mask, 0b101);
913 }
914 other => panic!("expected filtered single-chunk layout, got {other:?}"),
915 }
916 }
917 }
918
919 #[test]
920 fn chunked_v4_enc_bytes() {
921 let params = EarrayParams::default_params();
923 let msg = DataLayoutMessage::chunked_v4_earray(vec![1, 256, 256], params, 0x2000);
924 let encoded = msg.encode(&ctx8());
925 assert_eq!(encoded.len(), 25);
928 assert_eq!(encoded[4], 2); }
930
931 #[test]
932 fn roundtrip_chunked_v3_btree_v1() {
933 let msg = DataLayoutMessage::chunked_v3_btree_v1(vec![8, 4], 0x1234);
935 let encoded = msg.encode(&ctx8());
936 assert_eq!(encoded.len(), 19);
938 assert_eq!(encoded[0], 3);
939 assert_eq!(encoded[1], 2);
940 assert_eq!(encoded[2], 2); let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
942 assert_eq!(consumed, encoded.len());
943 assert_eq!(decoded, msg);
944 }
945
946 #[test]
947 fn roundtrip_chunked_v3_btree_v1_2d_ctx4() {
948 let msg = DataLayoutMessage::chunked_v3_btree_v1(vec![2, 3, 8], 0x800);
950 let encoded = msg.encode(&ctx4());
951 assert_eq!(encoded.len(), 19);
953 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx4()).unwrap();
954 assert_eq!(consumed, encoded.len());
955 assert_eq!(decoded, msg);
956 }
957
958 #[test]
959 fn chunked_v3_undef_btree_addr() {
960 let msg = DataLayoutMessage::chunked_v3_btree_v1(vec![16, 4], UNDEF_ADDR);
961 let encoded = msg.encode(&ctx8());
962 let (decoded, _) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
963 match decoded {
964 DataLayoutMessage::ChunkedV3 { b_tree_address, .. } => {
965 assert_eq!(b_tree_address, UNDEF_ADDR);
966 }
967 _ => panic!("expected ChunkedV3"),
968 }
969 }
970
971 #[test]
972 fn chunked_v3_rejects_ndims_too_small() {
973 let buf = [3u8, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
975 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
976 assert!(matches!(err, FormatError::InvalidData(_)));
977 }
978
979 #[test]
980 fn chunked_v3_rejects_zero_dim() {
981 let mut buf = vec![3u8, 2, 2];
983 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();
987 assert!(matches!(err, FormatError::InvalidData(_)));
988 }
989
990 #[test]
991 fn chunked_v3_truncated() {
992 let buf = [3u8, 2, 2];
994 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
995 assert!(matches!(err, FormatError::BufferTooShort { .. }));
996 }
997
998 #[test]
999 fn chunked_v4_large_dims() {
1000 let params = EarrayParams::default_params();
1002 let msg = DataLayoutMessage::chunked_v4_earray(vec![1, 65536], params, 0x4000);
1003 let encoded = msg.encode(&ctx8());
1004 assert_eq!(encoded[4], 3); }
1006}