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, Copy, PartialEq, Eq)]
121pub struct SingleChunkFilter {
122 pub nbytes: u64,
124 pub filter_mask: u32,
127}
128
129#[derive(Debug, Clone, PartialEq)]
131pub enum DataLayoutMessage {
132 Contiguous {
134 address: u64,
136 size: u64,
138 },
139 Compact {
141 data: Vec<u8>,
143 },
144 ChunkedV3 {
149 chunk_dims: Vec<u64>,
152 b_tree_address: u64,
154 },
155 ChunkedV4 {
158 version: u8,
165 flags: u8,
166 chunk_dims: Vec<u64>,
168 index_type: ChunkIndexType,
170 earray_params: Option<EarrayParams>,
172 farray_params: Option<FixedArrayParams>,
174 single_chunk_filter: Option<SingleChunkFilter>,
177 index_address: u64,
179 },
180}
181
182impl DataLayoutMessage {
183 pub fn contiguous_unallocated(size: u64) -> Self {
185 Self::Contiguous {
186 address: UNDEF_ADDR,
187 size,
188 }
189 }
190
191 pub fn contiguous(address: u64, size: u64) -> Self {
193 Self::Contiguous { address, size }
194 }
195
196 pub fn compact(data: Vec<u8>) -> Self {
198 Self::Compact { data }
199 }
200
201 pub fn chunked_v3_btree_v1(chunk_dims: Vec<u64>, b_tree_address: u64) -> Self {
205 Self::ChunkedV3 {
206 chunk_dims,
207 b_tree_address,
208 }
209 }
210
211 pub fn chunked_v4_earray(
217 version: u8,
218 chunk_dims: Vec<u64>,
219 earray_params: EarrayParams,
220 index_address: u64,
221 ) -> Self {
222 Self::ChunkedV4 {
223 version,
224 flags: 0,
225 chunk_dims,
226 index_type: ChunkIndexType::ExtensibleArray,
227 earray_params: Some(earray_params),
228 farray_params: None,
229 single_chunk_filter: None,
230 index_address,
231 }
232 }
233
234 pub fn chunked_v4_farray(
238 version: u8,
239 chunk_dims: Vec<u64>,
240 farray_params: FixedArrayParams,
241 index_address: u64,
242 ) -> Self {
243 Self::ChunkedV4 {
244 version,
245 flags: 0,
246 chunk_dims,
247 index_type: ChunkIndexType::FixedArray,
248 earray_params: None,
249 farray_params: Some(farray_params),
250 single_chunk_filter: None,
251 index_address,
252 }
253 }
254
255 pub fn chunked_v4_btree_v2(version: u8, chunk_dims: Vec<u64>, index_address: u64) -> Self {
259 Self::ChunkedV4 {
260 version,
261 flags: 0,
262 chunk_dims,
263 index_type: ChunkIndexType::BTreeV2,
264 earray_params: None,
265 farray_params: None,
266 single_chunk_filter: None,
267 index_address,
268 }
269 }
270
271 pub fn chunked_v4_single(chunk_dims: Vec<u64>, index_address: u64) -> Self {
275 Self::ChunkedV4 {
276 version: VERSION_4,
277 flags: 0,
278 chunk_dims,
279 index_type: ChunkIndexType::SingleChunk,
280 earray_params: None,
281 farray_params: None,
282 single_chunk_filter: None,
283 index_address,
284 }
285 }
286
287 pub fn encode(&self, ctx: &FormatContext) -> Vec<u8> {
290 match self {
291 Self::Contiguous { address, size } => {
292 let sa = ctx.sizeof_addr as usize;
293 let ss = ctx.sizeof_size as usize;
294 let mut buf = Vec::with_capacity(2 + sa + ss);
295 buf.push(VERSION_3);
296 buf.push(CLASS_CONTIGUOUS);
297 buf.extend_from_slice(&address.to_le_bytes()[..sa]);
298 buf.extend_from_slice(&size.to_le_bytes()[..ss]);
299 buf
300 }
301 Self::Compact { data } => {
302 let mut buf = Vec::with_capacity(2 + 2 + data.len());
303 buf.push(VERSION_3);
304 buf.push(CLASS_COMPACT);
305 buf.extend_from_slice(&(data.len() as u16).to_le_bytes());
306 buf.extend_from_slice(data);
307 buf
308 }
309 Self::ChunkedV3 {
310 chunk_dims,
311 b_tree_address,
312 } => {
313 let sa = ctx.sizeof_addr as usize;
314 let ndims = chunk_dims.len() as u8;
315 let mut buf = Vec::with_capacity(3 + sa + chunk_dims.len() * 4);
316 buf.push(VERSION_3);
317 buf.push(CLASS_CHUNKED);
318 buf.push(ndims);
319 buf.extend_from_slice(&b_tree_address.to_le_bytes()[..sa]);
320 for &d in chunk_dims {
322 buf.extend_from_slice(&(d as u32).to_le_bytes());
323 }
324 buf
325 }
326 Self::ChunkedV4 {
327 version,
328 flags,
329 chunk_dims,
330 index_type,
331 earray_params,
332 farray_params,
333 single_chunk_filter,
334 index_address,
335 } => {
336 let sa = ctx.sizeof_addr as usize;
337 let ndims = chunk_dims.len() as u8;
338
339 let max_dim = chunk_dims.iter().copied().max().unwrap_or(1);
342 let enc_bytes = enc_bytes_for_value(max_dim);
343
344 debug_assert!(matches!(*version, VERSION_4 | VERSION_5));
345 let mut buf = Vec::with_capacity(64);
346 buf.push(*version);
347 buf.push(CLASS_CHUNKED);
348 buf.push(*flags);
349 buf.push(ndims);
350 buf.push(enc_bytes);
351
352 for &d in chunk_dims {
354 buf.extend_from_slice(&d.to_le_bytes()[..enc_bytes as usize]);
355 }
356
357 buf.push(*index_type as u8);
359
360 match *index_type {
362 ChunkIndexType::ExtensibleArray => {
363 if let Some(ref params) = earray_params {
364 buf.push(params.max_nelmts_bits);
365 buf.push(params.idx_blk_elmts);
366 buf.push(params.sup_blk_min_data_ptrs);
367 buf.push(params.data_blk_min_elmts);
368 buf.push(params.max_dblk_page_nelmts_bits);
369 }
370 }
371 ChunkIndexType::FixedArray => {
372 if let Some(ref params) = farray_params {
373 buf.push(params.max_dblk_page_nelmts_bits);
374 }
375 }
376 ChunkIndexType::BTreeV2 => {
377 use crate::format::chunk_index::btree_v2::{
380 BT2_MERGE_PERCENT, BT2_NODE_SIZE, BT2_SPLIT_PERCENT,
381 };
382 buf.extend_from_slice(&BT2_NODE_SIZE.to_le_bytes());
383 buf.push(BT2_SPLIT_PERCENT);
384 buf.push(BT2_MERGE_PERCENT);
385 }
386 ChunkIndexType::SingleChunk if *flags & 0x02 != 0 => {
392 if let Some(scf) = single_chunk_filter {
393 let ss = ctx.sizeof_size as usize;
394 buf.extend_from_slice(&scf.nbytes.to_le_bytes()[..ss]);
395 buf.extend_from_slice(&scf.filter_mask.to_le_bytes());
396 }
397 }
398 _ => {}
400 }
401
402 buf.extend_from_slice(&index_address.to_le_bytes()[..sa]);
404
405 buf
406 }
407 }
408 }
409
410 pub fn decode(buf: &[u8], ctx: &FormatContext) -> FormatResult<(Self, usize)> {
413 if buf.len() < 2 {
414 return Err(FormatError::BufferTooShort {
415 needed: 2,
416 available: buf.len(),
417 });
418 }
419
420 let version = buf[0];
421 let class = buf[1];
422
423 match (version, class) {
424 (VERSION_3, CLASS_CONTIGUOUS) => {
425 let sa = ctx.sizeof_addr as usize;
426 let ss = ctx.sizeof_size as usize;
427 let mut pos = 2;
428 let needed = pos + sa + ss;
429 if buf.len() < needed {
430 return Err(FormatError::BufferTooShort {
431 needed,
432 available: buf.len(),
433 });
434 }
435 let address = read_addr(&buf[pos..], sa);
436 pos += sa;
437 let size = read_size(&buf[pos..], ss);
438 pos += ss;
439 Ok((Self::Contiguous { address, size }, pos))
440 }
441 (VERSION_3, CLASS_COMPACT) => {
442 let mut pos = 2;
443 if buf.len() < pos + 2 {
444 return Err(FormatError::BufferTooShort {
445 needed: pos + 2,
446 available: buf.len(),
447 });
448 }
449 let compact_size = u16::from_le_bytes([buf[pos], buf[pos + 1]]) as usize;
450 pos += 2;
451 if buf.len() < pos + compact_size {
452 return Err(FormatError::BufferTooShort {
453 needed: pos + compact_size,
454 available: buf.len(),
455 });
456 }
457 let data = buf[pos..pos + compact_size].to_vec();
458 pos += compact_size;
459 Ok((Self::Compact { data }, pos))
460 }
461 (VERSION_3, CLASS_CHUNKED) => {
462 let sa = ctx.sizeof_addr as usize;
465 let mut pos = 2;
466 if buf.len() < pos + 1 {
467 return Err(FormatError::BufferTooShort {
468 needed: pos + 1,
469 available: buf.len(),
470 });
471 }
472 let ndims = buf[pos] as usize;
473 pos += 1;
474
475 if ndims < 2 {
479 return Err(FormatError::InvalidData(format!(
480 "chunked v3 layout dimensionality {ndims} is too small"
481 )));
482 }
483
484 if buf.len() < pos + sa {
485 return Err(FormatError::BufferTooShort {
486 needed: pos + sa,
487 available: buf.len(),
488 });
489 }
490 let b_tree_address = read_addr(&buf[pos..], sa);
491 pos += sa;
492
493 let dim_data_len = ndims * 4;
494 if buf.len() < pos + dim_data_len {
495 return Err(FormatError::BufferTooShort {
496 needed: pos + dim_data_len,
497 available: buf.len(),
498 });
499 }
500 let mut chunk_dims = Vec::with_capacity(ndims);
501 for _ in 0..ndims {
502 let d = u32::from_le_bytes([buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]])
503 as u64;
504 if d == 0 {
505 return Err(FormatError::InvalidData(
506 "chunked v3 layout has a zero chunk dimension".into(),
507 ));
508 }
509 chunk_dims.push(d);
510 pos += 4;
511 }
512
513 Ok((
514 Self::ChunkedV3 {
515 chunk_dims,
516 b_tree_address,
517 },
518 pos,
519 ))
520 }
521 (VERSION_4 | VERSION_5, CLASS_CHUNKED) => {
522 let sa = ctx.sizeof_addr as usize;
523 let mut pos = 2;
524
525 if buf.len() < pos + 3 {
527 return Err(FormatError::BufferTooShort {
528 needed: pos + 3,
529 available: buf.len(),
530 });
531 }
532 let flags = buf[pos];
533 pos += 1;
534 let ndims = buf[pos] as usize;
535 pos += 1;
536 let enc_bytes = buf[pos] as usize;
537 pos += 1;
538
539 if !(1..=8).contains(&enc_bytes) {
542 return Err(FormatError::InvalidData(format!(
543 "chunked layout encoded dimension size {enc_bytes} is out of range"
544 )));
545 }
546 if ndims < 2 {
549 return Err(FormatError::InvalidData(format!(
550 "chunked v4 layout dimensionality {ndims} is too small"
551 )));
552 }
553
554 let dim_data_len = ndims * enc_bytes;
556 if buf.len() < pos + dim_data_len {
557 return Err(FormatError::BufferTooShort {
558 needed: pos + dim_data_len,
559 available: buf.len(),
560 });
561 }
562 let mut chunk_dims = Vec::with_capacity(ndims);
563 for _ in 0..ndims {
564 let d = read_size(&buf[pos..], enc_bytes);
565 if d == 0 {
566 return Err(FormatError::InvalidData(
567 "chunked v4 layout has a zero chunk dimension".into(),
568 ));
569 }
570 chunk_dims.push(d);
571 pos += enc_bytes;
572 }
573
574 if buf.len() < pos + 1 {
576 return Err(FormatError::BufferTooShort {
577 needed: pos + 1,
578 available: buf.len(),
579 });
580 }
581 let idx_type_raw = buf[pos];
582 pos += 1;
583 let index_type = ChunkIndexType::from_u8(idx_type_raw).ok_or_else(|| {
584 FormatError::UnsupportedFeature(format!("chunk index type {}", idx_type_raw))
585 })?;
586
587 let mut earray_params = None;
589 let mut farray_params = None;
590 let mut single_chunk_filter = None;
591
592 match index_type {
593 ChunkIndexType::ExtensibleArray => {
594 if buf.len() < pos + 5 {
595 return Err(FormatError::BufferTooShort {
596 needed: pos + 5,
597 available: buf.len(),
598 });
599 }
600 let ep = EarrayParams {
601 max_nelmts_bits: buf[pos],
602 idx_blk_elmts: buf[pos + 1],
603 sup_blk_min_data_ptrs: buf[pos + 2],
604 data_blk_min_elmts: buf[pos + 3],
605 max_dblk_page_nelmts_bits: buf[pos + 4],
606 };
607 if ep.max_nelmts_bits == 0
609 || ep.idx_blk_elmts == 0
610 || ep.sup_blk_min_data_ptrs == 0
611 || ep.data_blk_min_elmts == 0
612 || ep.max_dblk_page_nelmts_bits == 0
613 {
614 return Err(FormatError::InvalidData(
615 "extensible-array layout parameter is zero".into(),
616 ));
617 }
618 earray_params = Some(ep);
619 pos += 5;
620 }
621 ChunkIndexType::FixedArray => {
622 if buf.len() < pos + 1 {
623 return Err(FormatError::BufferTooShort {
624 needed: pos + 1,
625 available: buf.len(),
626 });
627 }
628 farray_params = Some(FixedArrayParams {
634 max_dblk_page_nelmts_bits: buf[pos],
635 });
636 pos += 1;
637 }
638 ChunkIndexType::BTreeV2 => {
639 if buf.len() < pos + 6 {
643 return Err(FormatError::BufferTooShort {
644 needed: pos + 6,
645 available: buf.len(),
646 });
647 }
648 pos += 6;
649 }
650 ChunkIndexType::SingleChunk if flags & 0x02 != 0 => {
657 let ss = ctx.sizeof_size as usize;
658 let extra = ss + 4;
659 if buf.len() < pos + extra {
660 return Err(FormatError::BufferTooShort {
661 needed: pos + extra,
662 available: buf.len(),
663 });
664 }
665 let nbytes = read_size(&buf[pos..], ss);
666 pos += ss;
667 let filter_mask = u32::from_le_bytes([
668 buf[pos],
669 buf[pos + 1],
670 buf[pos + 2],
671 buf[pos + 3],
672 ]);
673 pos += 4;
674 single_chunk_filter = Some(SingleChunkFilter {
675 nbytes,
676 filter_mask,
677 });
678 }
679 _ => {}
682 }
683
684 if buf.len() < pos + sa {
686 return Err(FormatError::BufferTooShort {
687 needed: pos + sa,
688 available: buf.len(),
689 });
690 }
691 let index_address = read_addr(&buf[pos..], sa);
692 pos += sa;
693
694 Ok((
695 Self::ChunkedV4 {
696 version: buf[0],
697 flags,
698 chunk_dims,
699 index_type,
700 earray_params,
701 farray_params,
702 single_chunk_filter,
703 index_address,
704 },
705 pos,
706 ))
707 }
708 (VERSION_3, other) => Err(FormatError::UnsupportedFeature(format!(
709 "data layout class {}",
710 other
711 ))),
712 (v, _) => Err(FormatError::InvalidVersion(v)),
713 }
714 }
715}
716
717fn enc_bytes_for_value(v: u64) -> u8 {
721 if v == 0 {
722 return 1;
723 }
724 let bits_needed = 64 - v.leading_zeros(); bits_needed.div_ceil(8) as u8
726}
727
728#[cfg(test)]
731mod tests {
732 use super::*;
733
734 fn ctx8() -> FormatContext {
735 FormatContext {
736 sizeof_addr: 8,
737 sizeof_size: 8,
738 }
739 }
740
741 fn ctx4() -> FormatContext {
742 FormatContext {
743 sizeof_addr: 4,
744 sizeof_size: 4,
745 }
746 }
747
748 #[test]
749 fn roundtrip_contiguous() {
750 let msg = DataLayoutMessage::contiguous(0x1000, 4096);
751 let encoded = msg.encode(&ctx8());
752 assert_eq!(encoded.len(), 18);
754 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
755 assert_eq!(consumed, 18);
756 assert_eq!(decoded, msg);
757 }
758
759 #[test]
760 fn roundtrip_contiguous_ctx4() {
761 let msg = DataLayoutMessage::contiguous(0x800, 256);
762 let encoded = msg.encode(&ctx4());
763 assert_eq!(encoded.len(), 10);
765 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx4()).unwrap();
766 assert_eq!(consumed, 10);
767 assert_eq!(decoded, msg);
768 }
769
770 #[test]
771 fn roundtrip_contiguous_unallocated() {
772 let msg = DataLayoutMessage::contiguous_unallocated(1024);
773 let encoded = msg.encode(&ctx8());
774 let (decoded, _) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
775 assert_eq!(decoded, msg);
776 match decoded {
777 DataLayoutMessage::Contiguous { address, size } => {
778 assert_eq!(address, UNDEF_ADDR);
779 assert_eq!(size, 1024);
780 }
781 _ => panic!("expected Contiguous"),
782 }
783 }
784
785 #[test]
786 fn roundtrip_contiguous_undef_ctx4() {
787 let msg = DataLayoutMessage::contiguous_unallocated(512);
788 let encoded = msg.encode(&ctx4());
789 let (decoded, _) = DataLayoutMessage::decode(&encoded, &ctx4()).unwrap();
790 match decoded {
791 DataLayoutMessage::Contiguous { address, .. } => {
792 assert_eq!(address, UNDEF_ADDR);
793 }
794 _ => panic!("expected Contiguous"),
795 }
796 }
797
798 #[test]
799 fn roundtrip_compact() {
800 let data = vec![1, 2, 3, 4, 5, 6, 7, 8];
801 let msg = DataLayoutMessage::compact(data.clone());
802 let encoded = msg.encode(&ctx8());
803 assert_eq!(encoded.len(), 12);
805 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
806 assert_eq!(consumed, 12);
807 assert_eq!(decoded, msg);
808 }
809
810 #[test]
811 fn roundtrip_compact_empty() {
812 let msg = DataLayoutMessage::compact(vec![]);
813 let encoded = msg.encode(&ctx8());
814 assert_eq!(encoded.len(), 4); let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
816 assert_eq!(consumed, 4);
817 assert_eq!(decoded, msg);
818 }
819
820 #[test]
821 fn decode_bad_version() {
822 let buf = [2u8, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
823 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
824 match err {
825 FormatError::InvalidVersion(2) => {}
826 other => panic!("unexpected error: {:?}", other),
827 }
828 }
829
830 #[test]
831 fn decode_unsupported_class() {
832 let buf = [3u8, 3]; let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
834 match err {
835 FormatError::UnsupportedFeature(_) => {}
836 other => panic!("unexpected error: {:?}", other),
837 }
838 }
839
840 #[test]
841 fn decode_buffer_too_short() {
842 let buf = [3u8];
843 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
844 match err {
845 FormatError::BufferTooShort { .. } => {}
846 other => panic!("unexpected error: {:?}", other),
847 }
848 }
849
850 #[test]
851 fn decode_contiguous_truncated() {
852 let buf = [3u8, 1, 0, 0];
854 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
855 match err {
856 FormatError::BufferTooShort { .. } => {}
857 other => panic!("unexpected error: {:?}", other),
858 }
859 }
860
861 #[test]
862 fn version_and_class_bytes() {
863 let encoded = DataLayoutMessage::contiguous(0, 0).encode(&ctx8());
864 assert_eq!(encoded[0], 3);
865 assert_eq!(encoded[1], 1);
866
867 let encoded = DataLayoutMessage::compact(vec![]).encode(&ctx8());
868 assert_eq!(encoded[0], 3);
869 assert_eq!(encoded[1], 0);
870 }
871
872 #[test]
873 fn roundtrip_chunked_v4_earray() {
874 let params = EarrayParams::default_params();
875 let msg = DataLayoutMessage::chunked_v4_earray(4, vec![1, 256, 256], params, 0x2000);
876 let encoded = msg.encode(&ctx8());
877 assert_eq!(encoded[0], 4); assert_eq!(encoded[1], 2); let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
880 assert_eq!(consumed, encoded.len());
881 assert_eq!(decoded, msg);
882 }
883
884 #[test]
885 fn roundtrip_chunked_v4_earray_ctx4() {
886 let params = EarrayParams::default_params();
887 let msg = DataLayoutMessage::chunked_v4_earray(4, vec![1, 128], params, 0x1000);
888 let encoded = msg.encode(&ctx4());
889 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx4()).unwrap();
890 assert_eq!(consumed, encoded.len());
891 assert_eq!(decoded, msg);
892 }
893
894 #[test]
899 fn roundtrip_chunked_v5_earray() {
900 let params = EarrayParams::default_params();
901 let v5 = DataLayoutMessage::chunked_v4_earray(5, vec![1, 256, 256], params.clone(), 0x2000);
902 let encoded = v5.encode(&ctx8());
903 assert_eq!(encoded[0], 5); assert_eq!(encoded[1], 2); let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
906 assert_eq!(consumed, encoded.len());
907 assert_eq!(decoded, v5);
908
909 let v4 = DataLayoutMessage::chunked_v4_earray(4, vec![1, 256, 256], params, 0x2000);
911 let encoded_v4 = v4.encode(&ctx8());
912 assert_eq!(encoded[1..], encoded_v4[1..]);
913 }
914
915 #[test]
916 fn roundtrip_chunked_v4_single() {
917 let msg = DataLayoutMessage::chunked_v4_single(vec![100, 200], 0x3000);
918 let encoded = msg.encode(&ctx8());
919 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
920 assert_eq!(consumed, encoded.len());
921 assert_eq!(decoded, msg);
922 }
923
924 #[test]
929 fn roundtrip_chunked_v4_single_filtered() {
930 for ctx in [ctx8(), ctx4()] {
931 let msg = DataLayoutMessage::ChunkedV4 {
932 version: 4,
933 flags: 0x02,
934 chunk_dims: vec![100, 200, 4],
935 index_type: ChunkIndexType::SingleChunk,
936 earray_params: None,
937 farray_params: None,
938 single_chunk_filter: Some(SingleChunkFilter {
939 nbytes: 12345,
940 filter_mask: 0b101,
941 }),
942 index_address: 0x3000,
943 };
944 let encoded = msg.encode(&ctx);
945 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx).unwrap();
946 assert_eq!(consumed, encoded.len());
947 assert_eq!(decoded, msg);
948 match decoded {
950 DataLayoutMessage::ChunkedV4 {
951 single_chunk_filter: Some(scf),
952 ..
953 } => {
954 assert_eq!(scf.nbytes, 12345);
955 assert_eq!(scf.filter_mask, 0b101);
956 }
957 other => panic!("expected filtered single-chunk layout, got {other:?}"),
958 }
959 }
960 }
961
962 #[test]
963 fn chunked_v4_enc_bytes() {
964 let params = EarrayParams::default_params();
966 let msg = DataLayoutMessage::chunked_v4_earray(4, vec![1, 256, 256], params, 0x2000);
967 let encoded = msg.encode(&ctx8());
968 assert_eq!(encoded.len(), 25);
971 assert_eq!(encoded[4], 2); }
973
974 #[test]
975 fn roundtrip_chunked_v3_btree_v1() {
976 let msg = DataLayoutMessage::chunked_v3_btree_v1(vec![8, 4], 0x1234);
978 let encoded = msg.encode(&ctx8());
979 assert_eq!(encoded.len(), 19);
981 assert_eq!(encoded[0], 3);
982 assert_eq!(encoded[1], 2);
983 assert_eq!(encoded[2], 2); let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
985 assert_eq!(consumed, encoded.len());
986 assert_eq!(decoded, msg);
987 }
988
989 #[test]
990 fn roundtrip_chunked_v3_btree_v1_2d_ctx4() {
991 let msg = DataLayoutMessage::chunked_v3_btree_v1(vec![2, 3, 8], 0x800);
993 let encoded = msg.encode(&ctx4());
994 assert_eq!(encoded.len(), 19);
996 let (decoded, consumed) = DataLayoutMessage::decode(&encoded, &ctx4()).unwrap();
997 assert_eq!(consumed, encoded.len());
998 assert_eq!(decoded, msg);
999 }
1000
1001 #[test]
1002 fn chunked_v3_undef_btree_addr() {
1003 let msg = DataLayoutMessage::chunked_v3_btree_v1(vec![16, 4], UNDEF_ADDR);
1004 let encoded = msg.encode(&ctx8());
1005 let (decoded, _) = DataLayoutMessage::decode(&encoded, &ctx8()).unwrap();
1006 match decoded {
1007 DataLayoutMessage::ChunkedV3 { b_tree_address, .. } => {
1008 assert_eq!(b_tree_address, UNDEF_ADDR);
1009 }
1010 _ => panic!("expected ChunkedV3"),
1011 }
1012 }
1013
1014 #[test]
1015 fn chunked_v3_rejects_ndims_too_small() {
1016 let buf = [3u8, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
1018 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
1019 assert!(matches!(err, FormatError::InvalidData(_)));
1020 }
1021
1022 #[test]
1023 fn chunked_v3_rejects_zero_dim() {
1024 let mut buf = vec![3u8, 2, 2];
1026 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();
1030 assert!(matches!(err, FormatError::InvalidData(_)));
1031 }
1032
1033 #[test]
1034 fn chunked_v3_truncated() {
1035 let buf = [3u8, 2, 2];
1037 let err = DataLayoutMessage::decode(&buf, &ctx8()).unwrap_err();
1038 assert!(matches!(err, FormatError::BufferTooShort { .. }));
1039 }
1040
1041 #[test]
1042 fn chunked_v4_large_dims() {
1043 let params = EarrayParams::default_params();
1045 let msg = DataLayoutMessage::chunked_v4_earray(4, vec![1, 65536], params, 0x4000);
1046 let encoded = msg.encode(&ctx8());
1047 assert_eq!(encoded[4], 3); }
1049}