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, PartialEq)]
111pub enum DataLayoutMessage {
112 Contiguous {
114 address: u64,
116 size: u64,
118 },
119 Compact {
121 data: Vec<u8>,
123 },
124 ChunkedV3 {
129 chunk_dims: Vec<u64>,
132 b_tree_address: u64,
134 },
135 ChunkedV4 {
137 flags: u8,
138 chunk_dims: Vec<u64>,
140 index_type: ChunkIndexType,
142 earray_params: Option<EarrayParams>,
144 farray_params: Option<FixedArrayParams>,
146 index_address: u64,
148 },
149}
150
151impl DataLayoutMessage {
152 pub fn contiguous_unallocated(size: u64) -> Self {
154 Self::Contiguous {
155 address: UNDEF_ADDR,
156 size,
157 }
158 }
159
160 pub fn contiguous(address: u64, size: u64) -> Self {
162 Self::Contiguous { address, size }
163 }
164
165 pub fn compact(data: Vec<u8>) -> Self {
167 Self::Compact { data }
168 }
169
170 pub fn chunked_v3_btree_v1(chunk_dims: Vec<u64>, b_tree_address: u64) -> Self {
174 Self::ChunkedV3 {
175 chunk_dims,
176 b_tree_address,
177 }
178 }
179
180 pub fn chunked_v4_earray(
186 chunk_dims: Vec<u64>,
187 earray_params: EarrayParams,
188 index_address: u64,
189 ) -> Self {
190 Self::ChunkedV4 {
191 flags: 0,
192 chunk_dims,
193 index_type: ChunkIndexType::ExtensibleArray,
194 earray_params: Some(earray_params),
195 farray_params: None,
196 index_address,
197 }
198 }
199
200 pub fn chunked_v4_farray(
204 chunk_dims: Vec<u64>,
205 farray_params: FixedArrayParams,
206 index_address: u64,
207 ) -> Self {
208 Self::ChunkedV4 {
209 flags: 0,
210 chunk_dims,
211 index_type: ChunkIndexType::FixedArray,
212 earray_params: None,
213 farray_params: Some(farray_params),
214 index_address,
215 }
216 }
217
218 pub fn chunked_v4_btree_v2(chunk_dims: Vec<u64>, index_address: u64) -> Self {
222 Self::ChunkedV4 {
223 flags: 0,
224 chunk_dims,
225 index_type: ChunkIndexType::BTreeV2,
226 earray_params: None,
227 farray_params: None,
228 index_address,
229 }
230 }
231
232 pub fn chunked_v4_single(chunk_dims: Vec<u64>, index_address: u64) -> Self {
236 Self::ChunkedV4 {
237 flags: 0,
238 chunk_dims,
239 index_type: ChunkIndexType::SingleChunk,
240 earray_params: None,
241 farray_params: None,
242 index_address,
243 }
244 }
245
246 pub fn encode(&self, ctx: &FormatContext) -> Vec<u8> {
249 match self {
250 Self::Contiguous { address, size } => {
251 let sa = ctx.sizeof_addr as usize;
252 let ss = ctx.sizeof_size as usize;
253 let mut buf = Vec::with_capacity(2 + sa + ss);
254 buf.push(VERSION_3);
255 buf.push(CLASS_CONTIGUOUS);
256 buf.extend_from_slice(&address.to_le_bytes()[..sa]);
257 buf.extend_from_slice(&size.to_le_bytes()[..ss]);
258 buf
259 }
260 Self::Compact { data } => {
261 let mut buf = Vec::with_capacity(2 + 2 + data.len());
262 buf.push(VERSION_3);
263 buf.push(CLASS_COMPACT);
264 buf.extend_from_slice(&(data.len() as u16).to_le_bytes());
265 buf.extend_from_slice(data);
266 buf
267 }
268 Self::ChunkedV3 {
269 chunk_dims,
270 b_tree_address,
271 } => {
272 let sa = ctx.sizeof_addr as usize;
273 let ndims = chunk_dims.len() as u8;
274 let mut buf = Vec::with_capacity(3 + sa + chunk_dims.len() * 4);
275 buf.push(VERSION_3);
276 buf.push(CLASS_CHUNKED);
277 buf.push(ndims);
278 buf.extend_from_slice(&b_tree_address.to_le_bytes()[..sa]);
279 for &d in chunk_dims {
281 buf.extend_from_slice(&(d as u32).to_le_bytes());
282 }
283 buf
284 }
285 Self::ChunkedV4 {
286 flags,
287 chunk_dims,
288 index_type,
289 earray_params,
290 farray_params,
291 index_address,
292 } => {
293 let sa = ctx.sizeof_addr as usize;
294 let ndims = chunk_dims.len() as u8;
295
296 let max_dim = chunk_dims.iter().copied().max().unwrap_or(1);
299 let enc_bytes = enc_bytes_for_value(max_dim);
300
301 let mut buf = Vec::with_capacity(64);
302 buf.push(VERSION_4);
303 buf.push(CLASS_CHUNKED);
304 buf.push(*flags);
305 buf.push(ndims);
306 buf.push(enc_bytes);
307
308 for &d in chunk_dims {
310 buf.extend_from_slice(&d.to_le_bytes()[..enc_bytes as usize]);
311 }
312
313 buf.push(*index_type as u8);
315
316 match *index_type {
318 ChunkIndexType::ExtensibleArray => {
319 if let Some(ref params) = earray_params {
320 buf.push(params.max_nelmts_bits);
321 buf.push(params.idx_blk_elmts);
322 buf.push(params.sup_blk_min_data_ptrs);
323 buf.push(params.data_blk_min_elmts);
324 buf.push(params.max_dblk_page_nelmts_bits);
325 }
326 }
327 ChunkIndexType::FixedArray => {
328 if let Some(ref params) = farray_params {
329 buf.push(params.max_dblk_page_nelmts_bits);
330 }
331 }
332 ChunkIndexType::BTreeV2 => {
333 buf.extend_from_slice(&2048u32.to_le_bytes());
338 buf.push(100);
339 buf.push(40);
340 }
341 _ => {}
343 }
344
345 buf.extend_from_slice(&index_address.to_le_bytes()[..sa]);
347
348 buf
349 }
350 }
351 }
352
353 pub fn decode(buf: &[u8], ctx: &FormatContext) -> FormatResult<(Self, usize)> {
356 if buf.len() < 2 {
357 return Err(FormatError::BufferTooShort {
358 needed: 2,
359 available: buf.len(),
360 });
361 }
362
363 let version = buf[0];
364 let class = buf[1];
365
366 match (version, class) {
367 (VERSION_3, CLASS_CONTIGUOUS) => {
368 let sa = ctx.sizeof_addr as usize;
369 let ss = ctx.sizeof_size as usize;
370 let mut pos = 2;
371 let needed = pos + sa + ss;
372 if buf.len() < needed {
373 return Err(FormatError::BufferTooShort {
374 needed,
375 available: buf.len(),
376 });
377 }
378 let address = read_addr(&buf[pos..], sa);
379 pos += sa;
380 let size = read_size(&buf[pos..], ss);
381 pos += ss;
382 Ok((Self::Contiguous { address, size }, pos))
383 }
384 (VERSION_3, CLASS_COMPACT) => {
385 let mut pos = 2;
386 if buf.len() < pos + 2 {
387 return Err(FormatError::BufferTooShort {
388 needed: pos + 2,
389 available: buf.len(),
390 });
391 }
392 let compact_size = u16::from_le_bytes([buf[pos], buf[pos + 1]]) as usize;
393 pos += 2;
394 if buf.len() < pos + compact_size {
395 return Err(FormatError::BufferTooShort {
396 needed: pos + compact_size,
397 available: buf.len(),
398 });
399 }
400 let data = buf[pos..pos + compact_size].to_vec();
401 pos += compact_size;
402 Ok((Self::Compact { data }, pos))
403 }
404 (VERSION_3, CLASS_CHUNKED) => {
405 let sa = ctx.sizeof_addr as usize;
408 let mut pos = 2;
409 if buf.len() < pos + 1 {
410 return Err(FormatError::BufferTooShort {
411 needed: pos + 1,
412 available: buf.len(),
413 });
414 }
415 let ndims = buf[pos] as usize;
416 pos += 1;
417
418 if ndims < 2 {
422 return Err(FormatError::InvalidData(format!(
423 "chunked v3 layout dimensionality {ndims} is too small"
424 )));
425 }
426
427 if buf.len() < pos + sa {
428 return Err(FormatError::BufferTooShort {
429 needed: pos + sa,
430 available: buf.len(),
431 });
432 }
433 let b_tree_address = read_addr(&buf[pos..], sa);
434 pos += sa;
435
436 let dim_data_len = ndims * 4;
437 if buf.len() < pos + dim_data_len {
438 return Err(FormatError::BufferTooShort {
439 needed: pos + dim_data_len,
440 available: buf.len(),
441 });
442 }
443 let mut chunk_dims = Vec::with_capacity(ndims);
444 for _ in 0..ndims {
445 let d = u32::from_le_bytes([buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]])
446 as u64;
447 if d == 0 {
448 return Err(FormatError::InvalidData(
449 "chunked v3 layout has a zero chunk dimension".into(),
450 ));
451 }
452 chunk_dims.push(d);
453 pos += 4;
454 }
455
456 Ok((
457 Self::ChunkedV3 {
458 chunk_dims,
459 b_tree_address,
460 },
461 pos,
462 ))
463 }
464 (VERSION_4 | VERSION_5, CLASS_CHUNKED) => {
465 let sa = ctx.sizeof_addr as usize;
466 let mut pos = 2;
467
468 if buf.len() < pos + 3 {
470 return Err(FormatError::BufferTooShort {
471 needed: pos + 3,
472 available: buf.len(),
473 });
474 }
475 let flags = buf[pos];
476 pos += 1;
477 let ndims = buf[pos] as usize;
478 pos += 1;
479 let enc_bytes = buf[pos] as usize;
480 pos += 1;
481
482 if !(1..=8).contains(&enc_bytes) {
485 return Err(FormatError::InvalidData(format!(
486 "chunked layout encoded dimension size {enc_bytes} is out of range"
487 )));
488 }
489 if ndims < 2 {
492 return Err(FormatError::InvalidData(format!(
493 "chunked v4 layout dimensionality {ndims} is too small"
494 )));
495 }
496
497 let dim_data_len = ndims * enc_bytes;
499 if buf.len() < pos + dim_data_len {
500 return Err(FormatError::BufferTooShort {
501 needed: pos + dim_data_len,
502 available: buf.len(),
503 });
504 }
505 let mut chunk_dims = Vec::with_capacity(ndims);
506 for _ in 0..ndims {
507 let d = read_size(&buf[pos..], enc_bytes);
508 if d == 0 {
509 return Err(FormatError::InvalidData(
510 "chunked v4 layout has a zero chunk dimension".into(),
511 ));
512 }
513 chunk_dims.push(d);
514 pos += enc_bytes;
515 }
516
517 if buf.len() < pos + 1 {
519 return Err(FormatError::BufferTooShort {
520 needed: pos + 1,
521 available: buf.len(),
522 });
523 }
524 let idx_type_raw = buf[pos];
525 pos += 1;
526 let index_type = ChunkIndexType::from_u8(idx_type_raw).ok_or_else(|| {
527 FormatError::UnsupportedFeature(format!("chunk index type {}", idx_type_raw))
528 })?;
529
530 let mut earray_params = None;
532 let mut farray_params = None;
533
534 match index_type {
535 ChunkIndexType::ExtensibleArray => {
536 if buf.len() < pos + 5 {
537 return Err(FormatError::BufferTooShort {
538 needed: pos + 5,
539 available: buf.len(),
540 });
541 }
542 let ep = EarrayParams {
543 max_nelmts_bits: buf[pos],
544 idx_blk_elmts: buf[pos + 1],
545 sup_blk_min_data_ptrs: buf[pos + 2],
546 data_blk_min_elmts: buf[pos + 3],
547 max_dblk_page_nelmts_bits: buf[pos + 4],
548 };
549 if ep.max_nelmts_bits == 0
551 || ep.idx_blk_elmts == 0
552 || ep.sup_blk_min_data_ptrs == 0
553 || ep.data_blk_min_elmts == 0
554 || ep.max_dblk_page_nelmts_bits == 0
555 {
556 return Err(FormatError::InvalidData(
557 "extensible-array layout parameter is zero".into(),
558 ));
559 }
560 earray_params = Some(ep);
561 pos += 5;
562 }
563 ChunkIndexType::FixedArray => {
564 if buf.len() < pos + 1 {
565 return Err(FormatError::BufferTooShort {
566 needed: pos + 1,
567 available: buf.len(),
568 });
569 }
570 farray_params = Some(FixedArrayParams {
576 max_dblk_page_nelmts_bits: buf[pos],
577 });
578 pos += 1;
579 }
580 ChunkIndexType::BTreeV2 => {
581 if buf.len() < pos + 6 {
585 return Err(FormatError::BufferTooShort {
586 needed: pos + 6,
587 available: buf.len(),
588 });
589 }
590 pos += 6;
591 }
592 ChunkIndexType::SingleChunk if flags & 0x02 != 0 => {
597 let extra = ctx.sizeof_size as usize + 4;
598 if buf.len() < pos + extra {
599 return Err(FormatError::BufferTooShort {
600 needed: pos + extra,
601 available: buf.len(),
602 });
603 }
604 pos += extra;
605 }
606 _ => {}
609 }
610
611 if buf.len() < pos + sa {
613 return Err(FormatError::BufferTooShort {
614 needed: pos + sa,
615 available: buf.len(),
616 });
617 }
618 let index_address = read_addr(&buf[pos..], sa);
619 pos += sa;
620
621 Ok((
622 Self::ChunkedV4 {
623 flags,
624 chunk_dims,
625 index_type,
626 earray_params,
627 farray_params,
628 index_address,
629 },
630 pos,
631 ))
632 }
633 (VERSION_3, other) => Err(FormatError::UnsupportedFeature(format!(
634 "data layout class {}",
635 other
636 ))),
637 (v, _) => Err(FormatError::InvalidVersion(v)),
638 }
639 }
640}
641
642fn enc_bytes_for_value(v: u64) -> u8 {
646 if v == 0 {
647 return 1;
648 }
649 let bits_needed = 64 - v.leading_zeros(); bits_needed.div_ceil(8) as u8
651}
652
653#[cfg(test)]
656mod tests {
657 use super::*;
658
659 fn ctx8() -> FormatContext {
660 FormatContext {
661 sizeof_addr: 8,
662 sizeof_size: 8,
663 }
664 }
665
666 fn ctx4() -> FormatContext {
667 FormatContext {
668 sizeof_addr: 4,
669 sizeof_size: 4,
670 }
671 }
672
673 #[test]
674 fn roundtrip_contiguous() {
675 let msg = DataLayoutMessage::contiguous(0x1000, 4096);
676 let encoded = msg.encode(&ctx8());
677 assert_eq!(encoded.len(), 18);
679 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
680 assert_eq!(consumed, 18);
681 assert_eq!(decoded, msg);
682 }
683
684 #[test]
685 fn roundtrip_contiguous_ctx4() {
686 let msg = DataLayoutMessage::contiguous(0x800, 256);
687 let encoded = msg.encode(&ctx4());
688 assert_eq!(encoded.len(), 10);
690 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx4()).unwrap();
691 assert_eq!(consumed, 10);
692 assert_eq!(decoded, msg);
693 }
694
695 #[test]
696 fn roundtrip_contiguous_unallocated() {
697 let msg = DataLayoutMessage::contiguous_unallocated(1024);
698 let encoded = msg.encode(&ctx8());
699 let (decoded, _) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
700 assert_eq!(decoded, msg);
701 match decoded {
702 DataLayoutMessage::Contiguous { address, size } => {
703 assert_eq!(address, UNDEF_ADDR);
704 assert_eq!(size, 1024);
705 }
706 _ => panic!("expected Contiguous"),
707 }
708 }
709
710 #[test]
711 fn roundtrip_contiguous_undef_ctx4() {
712 let msg = DataLayoutMessage::contiguous_unallocated(512);
713 let encoded = msg.encode(&ctx4());
714 let (decoded, _) = DataLayoutMessage::decode(&encoded, &ctx4()).unwrap();
715 match decoded {
716 DataLayoutMessage::Contiguous { address, .. } => {
717 assert_eq!(address, UNDEF_ADDR);
718 }
719 _ => panic!("expected Contiguous"),
720 }
721 }
722
723 #[test]
724 fn roundtrip_compact() {
725 let data = vec![1, 2, 3, 4, 5, 6, 7, 8];
726 let msg = DataLayoutMessage::compact(data.clone());
727 let encoded = msg.encode(&ctx8());
728 assert_eq!(encoded.len(), 12);
730 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
731 assert_eq!(consumed, 12);
732 assert_eq!(decoded, msg);
733 }
734
735 #[test]
736 fn roundtrip_compact_empty() {
737 let msg = DataLayoutMessage::compact(vec![]);
738 let encoded = msg.encode(&ctx8());
739 assert_eq!(encoded.len(), 4); let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
741 assert_eq!(consumed, 4);
742 assert_eq!(decoded, msg);
743 }
744
745 #[test]
746 fn decode_bad_version() {
747 let buf = [2u8, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
748 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
749 match err {
750 FormatError::InvalidVersion(2) => {}
751 other => panic!("unexpected error: {:?}", other),
752 }
753 }
754
755 #[test]
756 fn decode_unsupported_class() {
757 let buf = [3u8, 3]; let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
759 match err {
760 FormatError::UnsupportedFeature(_) => {}
761 other => panic!("unexpected error: {:?}", other),
762 }
763 }
764
765 #[test]
766 fn decode_buffer_too_short() {
767 let buf = [3u8];
768 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
769 match err {
770 FormatError::BufferTooShort { .. } => {}
771 other => panic!("unexpected error: {:?}", other),
772 }
773 }
774
775 #[test]
776 fn decode_contiguous_truncated() {
777 let buf = [3u8, 1, 0, 0];
779 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
780 match err {
781 FormatError::BufferTooShort { .. } => {}
782 other => panic!("unexpected error: {:?}", other),
783 }
784 }
785
786 #[test]
787 fn version_and_class_bytes() {
788 let encoded = DataLayoutMessage::contiguous(0, 0).encode(&ctx8());
789 assert_eq!(encoded[0], 3);
790 assert_eq!(encoded[1], 1);
791
792 let encoded = DataLayoutMessage::compact(vec![]).encode(&ctx8());
793 assert_eq!(encoded[0], 3);
794 assert_eq!(encoded[1], 0);
795 }
796
797 #[test]
798 fn roundtrip_chunked_v4_earray() {
799 let params = EarrayParams::default_params();
800 let msg = DataLayoutMessage::chunked_v4_earray(vec![1, 256, 256], params, 0x2000);
801 let encoded = msg.encode(&ctx8());
802 assert_eq!(encoded[0], 4); assert_eq!(encoded[1], 2); let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
805 assert_eq!(consumed, encoded.len());
806 assert_eq!(decoded, msg);
807 }
808
809 #[test]
810 fn roundtrip_chunked_v4_earray_ctx4() {
811 let params = EarrayParams::default_params();
812 let msg = DataLayoutMessage::chunked_v4_earray(vec![1, 128], params, 0x1000);
813 let encoded = msg.encode(&ctx4());
814 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx4()).unwrap();
815 assert_eq!(consumed, encoded.len());
816 assert_eq!(decoded, msg);
817 }
818
819 #[test]
820 fn roundtrip_chunked_v4_single() {
821 let msg = DataLayoutMessage::chunked_v4_single(vec![100, 200], 0x3000);
822 let encoded = msg.encode(&ctx8());
823 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
824 assert_eq!(consumed, encoded.len());
825 assert_eq!(decoded, msg);
826 }
827
828 #[test]
829 fn chunked_v4_enc_bytes() {
830 let params = EarrayParams::default_params();
832 let msg = DataLayoutMessage::chunked_v4_earray(vec![1, 256, 256], params, 0x2000);
833 let encoded = msg.encode(&ctx8());
834 assert_eq!(encoded.len(), 25);
837 assert_eq!(encoded[4], 2); }
839
840 #[test]
841 fn roundtrip_chunked_v3_btree_v1() {
842 let msg = DataLayoutMessage::chunked_v3_btree_v1(vec![8, 4], 0x1234);
844 let encoded = msg.encode(&ctx8());
845 assert_eq!(encoded.len(), 19);
847 assert_eq!(encoded[0], 3);
848 assert_eq!(encoded[1], 2);
849 assert_eq!(encoded[2], 2); let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
851 assert_eq!(consumed, encoded.len());
852 assert_eq!(decoded, msg);
853 }
854
855 #[test]
856 fn roundtrip_chunked_v3_btree_v1_2d_ctx4() {
857 let msg = DataLayoutMessage::chunked_v3_btree_v1(vec![2, 3, 8], 0x800);
859 let encoded = msg.encode(&ctx4());
860 assert_eq!(encoded.len(), 19);
862 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx4()).unwrap();
863 assert_eq!(consumed, encoded.len());
864 assert_eq!(decoded, msg);
865 }
866
867 #[test]
868 fn chunked_v3_undef_btree_addr() {
869 let msg = DataLayoutMessage::chunked_v3_btree_v1(vec![16, 4], UNDEF_ADDR);
870 let encoded = msg.encode(&ctx8());
871 let (decoded, _) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
872 match decoded {
873 DataLayoutMessage::ChunkedV3 { b_tree_address, .. } => {
874 assert_eq!(b_tree_address, UNDEF_ADDR);
875 }
876 _ => panic!("expected ChunkedV3"),
877 }
878 }
879
880 #[test]
881 fn chunked_v3_rejects_ndims_too_small() {
882 let buf = [3u8, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
884 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
885 assert!(matches!(err, FormatError::InvalidData(_)));
886 }
887
888 #[test]
889 fn chunked_v3_rejects_zero_dim() {
890 let mut buf = vec![3u8, 2, 2];
892 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();
896 assert!(matches!(err, FormatError::InvalidData(_)));
897 }
898
899 #[test]
900 fn chunked_v3_truncated() {
901 let buf = [3u8, 2, 2];
903 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
904 assert!(matches!(err, FormatError::BufferTooShort { .. }));
905 }
906
907 #[test]
908 fn chunked_v4_large_dims() {
909 let params = EarrayParams::default_params();
911 let msg = DataLayoutMessage::chunked_v4_earray(vec![1, 65536], params, 0x4000);
912 let encoded = msg.encode(&ctx8());
913 assert_eq!(encoded[4], 3); }
915}