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