1#[cfg(not(feature = "std"))]
8use alloc::{format, string::String, vec::Vec};
9
10use crate::address::{BaseAddress, StoredAddress};
11use crate::bytes::read_offset;
12use crate::convert::{Narrow, is_undefined_addr};
13use crate::datatype::{CharacterSet, Datatype};
14use crate::error::FormatError;
15use crate::global_heap::GlobalHeapIndex;
16#[cfg(test)]
17use crate::source::BytesSource;
18use crate::source::Source;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
26pub struct VlenStringReadOptions {
27 max_elements: Option<usize>,
28 max_payload_bytes: Option<usize>,
29}
30
31impl VlenStringReadOptions {
32 pub const fn new() -> Self {
34 Self {
35 max_elements: None,
36 max_payload_bytes: None,
37 }
38 }
39
40 pub const fn with_max_elements(mut self, max_elements: usize) -> Self {
42 self.max_elements = Some(max_elements);
43 self
44 }
45
46 pub const fn with_max_payload_bytes(mut self, max_payload_bytes: usize) -> Self {
48 self.max_payload_bytes = Some(max_payload_bytes);
49 self
50 }
51
52 pub const fn max_elements(&self) -> Option<usize> {
54 self.max_elements
55 }
56
57 pub const fn max_payload_bytes(&self) -> Option<usize> {
59 self.max_payload_bytes
60 }
61}
62
63#[derive(Debug, Clone)]
65pub struct VlElement {
66 pub length: u32,
68 pub collection_address: StoredAddress,
70 pub object_index: u32,
72}
73
74pub fn parse_vl_references(
76 raw_data: &[u8],
77 num_elements: u64,
78 offset_size: u8,
79) -> Result<Vec<VlElement>, FormatError> {
80 let elem_size = 4 + offset_size as u64 + 4; let total = num_elements
82 .checked_mul(elem_size)
83 .ok_or(FormatError::OffsetOverflow {
84 offset: num_elements,
85 length: elem_size,
86 })?
87 .to_usize()?;
88 if raw_data.len() < total {
89 return Err(FormatError::UnexpectedEof {
90 expected: total,
91 available: raw_data.len(),
92 });
93 }
94
95 let mut elements = Vec::with_capacity(num_elements.to_usize()?);
96 let mut pos = 0;
97
98 for _ in 0..num_elements {
99 let length = u32::from_le_bytes([
100 raw_data[pos],
101 raw_data[pos + 1],
102 raw_data[pos + 2],
103 raw_data[pos + 3],
104 ]);
105 pos += 4;
106
107 let collection_address = StoredAddress::new(read_offset(raw_data, pos, offset_size)?);
108 pos += offset_size as usize;
109
110 let object_index = u32::from_le_bytes([
111 raw_data[pos],
112 raw_data[pos + 1],
113 raw_data[pos + 2],
114 raw_data[pos + 3],
115 ]);
116 pos += 4;
117
118 elements.push(VlElement {
119 length,
120 collection_address,
121 object_index,
122 });
123 }
124
125 Ok(elements)
126}
127
128pub(crate) fn is_vlen_string_datatype(datatype: &Datatype) -> bool {
131 match datatype {
132 Datatype::VariableLength {
133 is_string: true, ..
134 } => true,
135 Datatype::VariableLength {
136 is_string: false,
137 base_type,
138 ..
139 } => matches!(
140 base_type.as_ref(),
141 Datatype::String {
142 size: 1,
143 charset: CharacterSet::Ascii,
144 ..
145 }
146 ),
147 _ => false,
148 }
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
156pub(crate) struct EmbeddedVlSlot {
157 pub byte_offset: usize,
159 pub element_size: usize,
163}
164
165pub(crate) struct EmbeddedVlData {
169 pub raw: Vec<u8>,
171 pub offsets: Vec<usize>,
173 pub objects: Vec<VlByteObject>,
175}
176
177pub(crate) fn embedded_vlen_slots(datatype: &Datatype) -> Option<Vec<EmbeddedVlSlot>> {
186 let element_size = datatype.type_size() as usize;
190 let capacity = element_size / VL_REF_BYTES;
191 let mut slots = Vec::new();
192 if !collect_vlen_slots(datatype, 0, capacity, &mut slots) {
193 return None;
194 }
195 if slots.len() > capacity
198 || slots.iter().any(|s| {
199 s.byte_offset
200 .checked_add(VL_REF_BYTES)
201 .is_none_or(|end| end > element_size)
202 })
203 {
204 return None;
205 }
206 Some(slots)
207}
208
209const VL_REF_BYTES: usize = 16;
212
213fn collect_vlen_slots(
219 datatype: &Datatype,
220 base: usize,
221 capacity: usize,
222 out: &mut Vec<EmbeddedVlSlot>,
223) -> bool {
224 if out.len() > capacity {
226 return true;
227 }
228 match datatype {
229 Datatype::VariableLength { base_type, .. } => {
230 let element_size = if is_vlen_string_datatype(datatype) {
233 1
234 } else {
235 (base_type.type_size() as usize).max(1)
236 };
237 out.push(EmbeddedVlSlot {
238 byte_offset: base,
239 element_size,
240 });
241 true
242 }
243 Datatype::Compound { members, .. } => {
244 for m in members {
245 let Some(at) = usize::try_from(m.byte_offset)
246 .ok()
247 .and_then(|off| base.checked_add(off))
248 else {
249 return false;
250 };
251 if !collect_vlen_slots(&m.datatype, at, capacity, out) {
252 return false;
253 }
254 }
255 true
256 }
257 Datatype::Array {
258 base_type,
259 dimensions,
260 } => {
261 let mut probe = Vec::new();
273 if !collect_vlen_slots(base_type, 0, capacity, &mut probe) {
274 return false;
275 }
276 if probe.is_empty() {
278 return true;
279 }
280 let count = dimensions
281 .iter()
282 .copied()
283 .fold(1u64, |a, b| a.saturating_mul(u64::from(b)));
284 if count > capacity as u64 {
291 return false;
292 }
293 let entries = usize::try_from(count).unwrap_or(usize::MAX);
294 let stride = base_type.type_size() as usize;
295 for i in 0..entries {
296 let Some(at) = i.checked_mul(stride).and_then(|off| base.checked_add(off)) else {
297 return false;
298 };
299 for slot in &probe {
300 let Some(byte_offset) = at.checked_add(slot.byte_offset) else {
301 return false;
302 };
303 out.push(EmbeddedVlSlot {
304 byte_offset,
305 element_size: slot.element_size,
306 });
307 if out.len() > capacity {
310 return true;
311 }
312 }
313 }
314 true
315 }
316 _ => true,
319 }
320}
321
322fn check_element_limit(
323 num_elements: u64,
324 options: VlenStringReadOptions,
325) -> Result<(), FormatError> {
326 if let Some(limit) = options.max_elements
327 && num_elements > limit as u64
328 {
329 return Err(FormatError::VariableLengthElementLimitExceeded {
330 limit,
331 actual: num_elements,
332 });
333 }
334 Ok(())
335}
336
337fn payload_size(refs: &[VlElement], options: VlenStringReadOptions) -> Result<u64, FormatError> {
338 let mut required = 0u64;
339 for element in refs {
340 required =
341 required
342 .checked_add(u64::from(element.length))
343 .ok_or(FormatError::OffsetOverflow {
344 offset: required,
345 length: u64::from(element.length),
346 })?;
347 }
348 if let Some(limit) = options.max_payload_bytes
349 && required > limit as u64
350 {
351 return Err(FormatError::VariableLengthByteLimitExceeded { limit, required });
352 }
353 Ok(required)
354}
355
356pub fn vlen_string_payload_size(
358 raw_data: &[u8],
359 num_elements: u64,
360 offset_size: u8,
361) -> Result<u64, FormatError> {
362 check_element_limit(num_elements, VlenStringReadOptions::default())?;
363 let refs = parse_vl_references(raw_data, num_elements, offset_size)?;
364 payload_size(&refs, VlenStringReadOptions::default())
365}
366
367pub fn visit_vl_strings_from_source<S, F>(
370 source: &S,
371 raw_data: &[u8],
372 num_elements: u64,
373 offset_size: u8,
374 length_size: u8,
375 base_address: BaseAddress,
376 options: VlenStringReadOptions,
377 mut visitor: F,
378) -> Result<(), FormatError>
379where
380 S: Source + ?Sized,
381 F: FnMut(&str),
382{
383 check_element_limit(num_elements, options)?;
384 let refs = parse_vl_references(raw_data, num_elements, offset_size)?;
385 payload_size(&refs, options)?;
386
387 let mut wanted: Vec<(u64, Vec<u16>)> = Vec::new();
395 for element in &refs {
396 if is_undefined_addr(element.collection_address.get(), offset_size)
397 || (element.length == 0 && element.collection_address.get() == 0)
398 {
399 continue;
400 }
401 let Ok(address) = base_address.absolute(element.collection_address) else {
402 continue;
403 };
404 let Ok(index) = u16::try_from(element.object_index) else {
405 continue;
406 };
407 match wanted.binary_search_by_key(&address, |&(a, _)| a) {
408 Ok(pos) => wanted[pos].1.push(index),
409 Err(pos) => wanted.insert(pos, (address, Vec::from([index]))),
410 }
411 }
412 for (_, indices) in &mut wanted {
413 indices.sort_unstable();
414 }
415
416 let mut collections: Vec<(u64, GlobalHeapIndex)> = Vec::new();
417 for element in &refs {
418 if element.length == 0
419 && (is_undefined_addr(element.collection_address.get(), offset_size)
420 || element.collection_address.get() == 0)
421 {
422 visitor("");
423 continue;
424 }
425 if is_undefined_addr(element.collection_address.get(), offset_size) {
426 return Err(FormatError::VlDataError(
427 "non-empty VL element has an undefined heap address".into(),
428 ));
429 }
430
431 let collection_address = base_address.absolute(element.collection_address)?;
432 let collection_pos = match collections
433 .iter()
434 .position(|(address, _)| *address == collection_address)
435 {
436 Some(pos) => pos,
437 None => {
438 let keep = wanted
439 .binary_search_by_key(&collection_address, |&(a, _)| a)
440 .map(|pos| wanted[pos].1.as_slice())
441 .unwrap_or(&[]);
442 let collection = GlobalHeapIndex::parse_filtered(
443 source,
444 collection_address,
445 length_size,
446 |i| keep.binary_search(&i).is_ok(),
447 )?;
448 collections.push((collection_address, collection));
449 collections.len() - 1
450 }
451 };
452
453 let index: u16 = element.object_index.narrow_or_else(|| {
454 FormatError::VlDataError(format!(
455 "global heap object index {} does not fit u16",
456 element.object_index
457 ))
458 })?;
459 let object = collections[collection_pos].1.get_object(index).ok_or(
460 FormatError::GlobalHeapObjectNotFound {
461 collection_address,
462 index,
463 },
464 )?;
465 if u64::from(element.length) > object.size {
466 return Err(FormatError::VlDataError(format!(
467 "VL element length {} exceeds global heap object size {}",
468 element.length, object.size
469 )));
470 }
471
472 let bytes = source.read_exact_at(object.data_address, element.length as usize)?;
473 let string = String::from_utf8_lossy(&bytes);
474 visitor(&string);
475 }
476
477 Ok(())
478}
479
480pub fn read_vl_strings_from_source<S: Source + ?Sized>(
482 source: &S,
483 raw_data: &[u8],
484 num_elements: u64,
485 offset_size: u8,
486 length_size: u8,
487 base_address: BaseAddress,
488 options: VlenStringReadOptions,
489) -> Result<Vec<String>, FormatError> {
490 let mut strings = Vec::new();
491 visit_vl_strings_from_source(
492 source,
493 raw_data,
494 num_elements,
495 offset_size,
496 length_size,
497 base_address,
498 options,
499 |string| strings.push(String::from(string)),
500 )?;
501 Ok(strings)
502}
503
504#[derive(Debug, Clone, PartialEq, Eq)]
513pub(crate) enum VlByteObject {
514 Null,
516 Bytes(Vec<u8>),
518}
519
520pub(crate) fn read_vl_byte_objects_from_source<S: Source + ?Sized>(
534 source: &S,
535 raw_data: &[u8],
536 num_elements: u64,
537 offset_size: u8,
538 length_size: u8,
539 base_address: BaseAddress,
540 element_size: usize,
541 options: VlenStringReadOptions,
542) -> Result<Vec<VlByteObject>, FormatError> {
543 check_element_limit(num_elements, options)?;
544 let refs = parse_vl_references(raw_data, num_elements, offset_size)?;
545 payload_size(&refs, options)?;
546
547 let mut objects = Vec::with_capacity(refs.len());
548 let mut collections: Vec<(u64, GlobalHeapIndex)> = Vec::new();
549 for element in &refs {
550 if element.length == 0
551 && (is_undefined_addr(element.collection_address.get(), offset_size)
552 || element.collection_address.get() == 0)
553 {
554 objects.push(VlByteObject::Null);
555 continue;
556 }
557 if is_undefined_addr(element.collection_address.get(), offset_size) {
558 return Err(FormatError::VlDataError(
559 "non-empty VL element has an undefined heap address".into(),
560 ));
561 }
562
563 let collection_address = base_address.absolute(element.collection_address)?;
564 let collection_pos = match collections
565 .iter()
566 .position(|(address, _)| *address == collection_address)
567 {
568 Some(pos) => pos,
569 None => {
570 let collection = GlobalHeapIndex::parse(source, collection_address, length_size)?;
571 collections.push((collection_address, collection));
572 collections.len() - 1
573 }
574 };
575
576 let index: u16 = element.object_index.narrow_or_else(|| {
577 FormatError::VlDataError(format!(
578 "global heap object index {} does not fit u16",
579 element.object_index
580 ))
581 })?;
582 let object = collections[collection_pos].1.get_object(index).ok_or(
583 FormatError::GlobalHeapObjectNotFound {
584 collection_address,
585 index,
586 },
587 )?;
588 let byte_len = (element.length as u64)
592 .checked_mul(element_size as u64)
593 .ok_or(FormatError::OffsetOverflow {
594 offset: u64::from(element.length),
595 length: element_size as u64,
596 })?;
597 if byte_len > object.size {
598 return Err(FormatError::VlDataError(format!(
599 "VL element length {} ({} bytes) exceeds global heap object size {}",
600 element.length, byte_len, object.size
601 )));
602 }
603
604 let bytes = source.read_exact_at(object.data_address, byte_len.to_usize()?)?;
605 objects.push(VlByteObject::Bytes(bytes));
606 }
607
608 Ok(objects)
609}
610
611#[cfg(test)]
616pub fn read_vl_strings(
617 file_data: &[u8],
618 raw_data: &[u8],
619 num_elements: u64,
620 offset_size: u8,
621 length_size: u8,
622) -> Result<Vec<String>, FormatError> {
623 read_vl_strings_from_source(
624 &BytesSource::new(file_data),
625 raw_data,
626 num_elements,
627 offset_size,
628 length_size,
629 BaseAddress::ZERO,
630 VlenStringReadOptions::default(),
631 )
632}
633
634#[cfg(test)]
635mod tests {
636 use super::*;
637
638 fn build_gcol_at(
640 file_data: &mut Vec<u8>,
641 offset: usize,
642 objects: &[(u16, &[u8])], ) {
644 let length_size = 8usize;
645
646 let header_size = 8 + length_size;
648 let mut obj_total = 0usize;
649 for (_, data) in objects {
650 let padded = (data.len() + 7) & !7;
651 obj_total += 8 + length_size + padded;
652 }
653 obj_total += 2; let collection_size = header_size + obj_total;
655 let needed = offset + collection_size;
656 if file_data.len() < needed {
657 file_data.resize(needed, 0);
658 }
659
660 let mut pos = offset;
661 file_data[pos..pos + 4].copy_from_slice(b"GCOL");
663 file_data[pos + 4] = 1; pos += 8;
666 file_data[pos..pos + 8].copy_from_slice(&(collection_size as u64).to_le_bytes());
667 pos += 8;
668
669 for (index, data) in objects {
670 file_data[pos..pos + 2].copy_from_slice(&index.to_le_bytes());
671 file_data[pos + 2..pos + 4].copy_from_slice(&1u16.to_le_bytes()); pos += 8;
674 file_data[pos..pos + 8].copy_from_slice(&(data.len() as u64).to_le_bytes());
675 pos += 8;
676 file_data[pos..pos + data.len()].copy_from_slice(data);
677 let padded = (data.len() + 7) & !7;
678 pos += padded;
679 }
680 file_data[pos..pos + 2].copy_from_slice(&0u16.to_le_bytes());
682 }
683
684 fn build_vl_refs(
686 strings: &[&str],
687 collection_address: u64,
688 start_index: u16,
689 offset_size: u8,
690 ) -> Vec<u8> {
691 let mut raw = Vec::new();
692 for (i, s) in strings.iter().enumerate() {
693 raw.extend_from_slice(&(s.len() as u32).to_le_bytes());
694 match offset_size {
695 4 => raw.extend_from_slice(&(collection_address as u32).to_le_bytes()),
696 8 => raw.extend_from_slice(&collection_address.to_le_bytes()),
697 _ => panic!("unsupported"),
698 }
699 raw.extend_from_slice(&(start_index as u32 + i as u32).to_le_bytes());
700 }
701 raw
702 }
703
704 #[test]
705 fn parse_vl_references_two_elements() {
706 let raw = build_vl_refs(&["hello", "world"], 0x1000, 1, 8);
707 let refs = parse_vl_references(&raw, 2, 8).unwrap();
708 assert_eq!(refs.len(), 2);
709 assert_eq!(refs[0].length, 5);
710 assert_eq!(refs[0].collection_address, StoredAddress::new(0x1000));
711 assert_eq!(refs[0].object_index, 1);
712 assert_eq!(refs[1].length, 5);
713 assert_eq!(refs[1].object_index, 2);
714 }
715
716 #[test]
717 fn read_vl_strings_from_heap() {
718 let gcol_offset = 256usize;
719 let mut file_data = vec![0u8; 512];
720 build_gcol_at(&mut file_data, gcol_offset, &[(1, b"Alice"), (2, b"Bob")]);
721
722 let raw = build_vl_refs(&["Alice", "Bob"], gcol_offset as u64, 1, 8);
723 let strings = read_vl_strings(&file_data, &raw, 2, 8, 8).unwrap();
724 assert_eq!(strings, vec!["Alice", "Bob"]);
725 }
726
727 #[cfg(feature = "std")]
728 #[test]
729 fn read_vl_strings_from_seekable_source() {
730 use std::io::Cursor;
731
732 use crate::source::ReadSeekSource;
733
734 let gcol_offset = 256usize;
735 let mut file_data = vec![0u8; 512];
736 build_gcol_at(&mut file_data, gcol_offset, &[(1, b"Alice"), (2, b"Bob")]);
737 let raw = build_vl_refs(&["Alice", "Bob"], gcol_offset as u64, 1, 8);
738 let source = ReadSeekSource::new(Cursor::new(file_data)).unwrap();
739
740 let strings = read_vl_strings_from_source(
741 &source,
742 &raw,
743 2,
744 8,
745 8,
746 BaseAddress::ZERO,
747 VlenStringReadOptions::default(),
748 )
749 .unwrap();
750 assert_eq!(strings, vec!["Alice", "Bob"]);
751 }
752
753 #[test]
754 fn null_vl_element_empty_string() {
755 let mut raw = Vec::new();
757 raw.extend_from_slice(&0u32.to_le_bytes()); raw.extend_from_slice(&u64::MAX.to_le_bytes()); raw.extend_from_slice(&0u32.to_le_bytes()); let file_data = vec![0u8; 16];
762 let strings = read_vl_strings(&file_data, &raw, 1, 8, 8).unwrap();
763 assert_eq!(strings, vec![""]);
764 }
765
766 #[test]
767 fn null_vl_element_zero_address() {
768 let mut raw = Vec::new();
769 raw.extend_from_slice(&0u32.to_le_bytes());
770 raw.extend_from_slice(&0u64.to_le_bytes());
771 raw.extend_from_slice(&0u32.to_le_bytes());
772
773 let file_data = vec![0u8; 16];
774 let strings = read_vl_strings(&file_data, &raw, 1, 8, 8).unwrap();
775 assert_eq!(strings, vec![""]);
776 }
777
778 #[test]
779 fn parse_vl_references_truncated_error() {
780 let raw = vec![0u8; 10]; let err = parse_vl_references(&raw, 1, 8).unwrap_err();
782 assert!(matches!(err, FormatError::UnexpectedEof { .. }));
783 }
784}
785
786#[cfg(test)]
787mod embedded_slot_tests {
788 use super::*;
789 use crate::{
790 DatatypeByteOrder,
791 datatype::{CompoundMember, StringPadding, layout::FixedPointLayout},
792 };
793
794 fn vlen_string() -> Datatype {
795 Datatype::VariableLength {
796 is_string: true,
797 base_type: Box::new(Datatype::String {
798 size: 1,
799 charset: CharacterSet::Utf8,
800 padding: StringPadding::NullTerminate,
801 }),
802 padding: Some(StringPadding::NullTerminate),
803 charset: Some(CharacterSet::Utf8),
804 }
805 }
806
807 fn vlen_i32_sequence() -> Datatype {
808 Datatype::VariableLength {
809 is_string: false,
810 base_type: Box::new(Datatype::FixedPoint {
811 size: 4,
812 byte_order: DatatypeByteOrder::LittleEndian,
813 layout: FixedPointLayout {
814 signed: true,
815 bit_offset: 0,
816 bit_precision: 32,
817 },
818 }),
819 padding: None,
820 charset: None,
821 }
822 }
823
824 fn i32_type() -> Datatype {
825 Datatype::FixedPoint {
826 size: 4,
827 byte_order: DatatypeByteOrder::LittleEndian,
828 layout: FixedPointLayout {
829 signed: true,
830 bit_offset: 0,
831 bit_precision: 32,
832 },
833 }
834 }
835
836 fn member(name: &str, byte_offset: u64, datatype: Datatype) -> CompoundMember {
837 CompoundMember {
838 name: name.to_string(),
839 byte_offset,
840 datatype,
841 }
842 }
843
844 #[test]
845 fn a_plain_datatype_reaches_no_reference() {
846 assert_eq!(embedded_vlen_slots(&i32_type()), Some(Vec::new()));
847 }
848
849 #[test]
850 fn a_top_level_vlen_is_its_own_slot() {
851 assert_eq!(
852 embedded_vlen_slots(&vlen_string()),
853 Some(vec![EmbeddedVlSlot {
854 byte_offset: 0,
855 element_size: 1
856 }])
857 );
858 }
859
860 #[test]
864 fn compound_members_keep_their_own_element_size() {
865 let dt = Datatype::Compound {
866 size: 40,
867 members: vec![
868 member("label", 0, vlen_string()),
869 member("id", 16, i32_type()),
870 member("samples", 24, vlen_i32_sequence()),
871 ],
872 };
873 assert_eq!(
874 embedded_vlen_slots(&dt),
875 Some(vec![
876 EmbeddedVlSlot {
877 byte_offset: 0,
878 element_size: 1
879 },
880 EmbeddedVlSlot {
881 byte_offset: 24,
882 element_size: 4
883 },
884 ])
885 );
886 }
887
888 #[test]
891 fn nested_compounds_and_arrays_are_walked() {
892 let inner = Datatype::Compound {
893 size: 20,
894 members: vec![member("id", 0, i32_type()), member("s", 4, vlen_string())],
895 };
896 let nested = Datatype::Compound {
897 size: 24,
898 members: vec![
899 member("n", 0, i32_type()),
900 member("inner", 4, inner.clone()),
901 ],
902 };
903 assert_eq!(
904 embedded_vlen_slots(&nested),
905 Some(vec![EmbeddedVlSlot {
906 byte_offset: 8,
907 element_size: 1
908 }])
909 );
910
911 let array = Datatype::Array {
912 base_type: Box::new(inner),
913 dimensions: vec![3],
914 };
915 assert_eq!(
916 embedded_vlen_slots(&array),
917 Some(vec![
918 EmbeddedVlSlot {
919 byte_offset: 4,
920 element_size: 1
921 },
922 EmbeddedVlSlot {
923 byte_offset: 24,
924 element_size: 1
925 },
926 EmbeddedVlSlot {
927 byte_offset: 44,
928 element_size: 1
929 },
930 ])
931 );
932 }
933
934 #[test]
938 fn a_datatype_too_small_for_its_own_references_is_rejected() {
939 let dt = Datatype::Compound {
940 size: 8, members: vec![member("s", 0, vlen_string())],
942 };
943 assert_eq!(embedded_vlen_slots(&dt), None);
944 }
945
946 #[test]
949 fn an_array_declaring_absurd_dimensions_is_rejected_not_walked() {
950 let dt = Datatype::Array {
951 base_type: Box::new(vlen_string()),
952 dimensions: vec![u32::MAX, u32::MAX],
953 };
954 assert_eq!(embedded_vlen_slots(&dt), None);
955 }
956
957 #[test]
966 fn deeply_nested_arrays_cost_time_proportional_to_their_slots() {
967 let mut dt = vlen_string();
968 for _ in 0..17 {
969 dt = Datatype::Array {
970 base_type: Box::new(dt),
971 dimensions: vec![2],
972 };
973 }
974 let started = std::time::Instant::now();
975 let slots = embedded_vlen_slots(&dt).expect("a well-formed nesting must be walkable");
976 let elapsed = started.elapsed();
977
978 assert_eq!(slots.len(), 1 << 17, "one slot per leaf entry");
979 assert!(
984 elapsed < std::time::Duration::from_secs(10),
985 "walking {} slots took {elapsed:?}; the walk is no longer linear in its output",
986 slots.len()
987 );
988 }
989
990 #[test]
996 fn a_member_offset_beyond_the_address_space_is_rejected() {
997 let dt = Datatype::Compound {
998 size: 40,
999 members: vec![member("s", u64::MAX - 8, vlen_string())],
1000 };
1001 assert_eq!(embedded_vlen_slots(&dt), None);
1002 }
1003}