1#[cfg(not(feature = "std"))]
8use alloc::{format, string::String, vec::Vec};
9
10use crate::convert::{TryToUsize, is_undefined_addr};
11use crate::datatype::{CharacterSet, Datatype};
12use crate::error::FormatError;
13use crate::global_heap::GlobalHeapIndex;
14#[cfg(test)]
15use crate::source::BytesSource;
16use crate::source::Source;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
24pub struct VlenStringReadOptions {
25 max_elements: Option<usize>,
26 max_payload_bytes: Option<usize>,
27}
28
29impl VlenStringReadOptions {
30 pub const fn new() -> Self {
32 Self {
33 max_elements: None,
34 max_payload_bytes: None,
35 }
36 }
37
38 pub const fn with_max_elements(mut self, max_elements: usize) -> Self {
40 self.max_elements = Some(max_elements);
41 self
42 }
43
44 pub const fn with_max_payload_bytes(mut self, max_payload_bytes: usize) -> Self {
46 self.max_payload_bytes = Some(max_payload_bytes);
47 self
48 }
49
50 pub const fn max_elements(&self) -> Option<usize> {
52 self.max_elements
53 }
54
55 pub const fn max_payload_bytes(&self) -> Option<usize> {
57 self.max_payload_bytes
58 }
59}
60
61#[derive(Debug, Clone)]
63pub struct VlElement {
64 pub length: u32,
66 pub collection_address: u64,
68 pub object_index: u32,
70}
71
72fn ensure_len(data: &[u8], offset: usize, needed: usize) -> Result<(), FormatError> {
73 match offset.checked_add(needed) {
74 Some(end) if end <= data.len() => Ok(()),
75 _ => Err(FormatError::UnexpectedEof {
76 expected: offset.saturating_add(needed),
77 available: data.len(),
78 }),
79 }
80}
81
82fn read_offset(data: &[u8], pos: usize, offset_size: u8) -> Result<u64, FormatError> {
83 let s = offset_size as usize;
84 ensure_len(data, pos, s)?;
85 let slice = &data[pos..pos + s];
86 Ok(match offset_size {
87 2 => u16::from_le_bytes([slice[0], slice[1]]) as u64,
88 4 => u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]) as u64,
89 8 => u64::from_le_bytes([
90 slice[0], slice[1], slice[2], slice[3], slice[4], slice[5], slice[6], slice[7],
91 ]),
92 _ => return Err(FormatError::InvalidOffsetSize(offset_size)),
93 })
94}
95
96pub fn parse_vl_references(
98 raw_data: &[u8],
99 num_elements: u64,
100 offset_size: u8,
101) -> Result<Vec<VlElement>, FormatError> {
102 let elem_size = 4 + offset_size as u64 + 4; let total = num_elements
104 .checked_mul(elem_size)
105 .ok_or(FormatError::OffsetOverflow {
106 offset: num_elements,
107 length: elem_size,
108 })?
109 .to_usize()?;
110 if raw_data.len() < total {
111 return Err(FormatError::UnexpectedEof {
112 expected: total,
113 available: raw_data.len(),
114 });
115 }
116
117 let mut elements = Vec::with_capacity(num_elements.to_usize()?);
118 let mut pos = 0;
119
120 for _ in 0..num_elements {
121 let length = u32::from_le_bytes([
122 raw_data[pos],
123 raw_data[pos + 1],
124 raw_data[pos + 2],
125 raw_data[pos + 3],
126 ]);
127 pos += 4;
128
129 let collection_address = read_offset(raw_data, pos, offset_size)?;
130 pos += offset_size as usize;
131
132 let object_index = u32::from_le_bytes([
133 raw_data[pos],
134 raw_data[pos + 1],
135 raw_data[pos + 2],
136 raw_data[pos + 3],
137 ]);
138 pos += 4;
139
140 elements.push(VlElement {
141 length,
142 collection_address,
143 object_index,
144 });
145 }
146
147 Ok(elements)
148}
149
150pub(crate) fn is_vlen_string_datatype(datatype: &Datatype) -> bool {
153 match datatype {
154 Datatype::VariableLength {
155 is_string: true, ..
156 } => true,
157 Datatype::VariableLength {
158 is_string: false,
159 base_type,
160 ..
161 } => matches!(
162 base_type.as_ref(),
163 Datatype::String {
164 size: 1,
165 charset: CharacterSet::Ascii,
166 ..
167 }
168 ),
169 _ => false,
170 }
171}
172
173#[derive(Debug, Clone, Copy, PartialEq, Eq)]
178pub(crate) struct EmbeddedVlSlot {
179 pub byte_offset: usize,
181 pub element_size: usize,
185}
186
187pub(crate) struct EmbeddedVlData {
191 pub raw: Vec<u8>,
193 pub offsets: Vec<usize>,
195 pub objects: Vec<VlByteObject>,
197}
198
199pub(crate) fn embedded_vlen_slots(datatype: &Datatype) -> Option<Vec<EmbeddedVlSlot>> {
208 let element_size = datatype.type_size() as usize;
212 let capacity = element_size / VL_REF_BYTES;
213 let mut slots = Vec::new();
214 if !collect_vlen_slots(datatype, 0, capacity, &mut slots) {
215 return None;
216 }
217 if slots.len() > capacity
220 || slots.iter().any(|s| {
221 s.byte_offset
222 .checked_add(VL_REF_BYTES)
223 .is_none_or(|end| end > element_size)
224 })
225 {
226 return None;
227 }
228 Some(slots)
229}
230
231const VL_REF_BYTES: usize = 16;
234
235fn collect_vlen_slots(
241 datatype: &Datatype,
242 base: usize,
243 capacity: usize,
244 out: &mut Vec<EmbeddedVlSlot>,
245) -> bool {
246 if out.len() > capacity {
248 return true;
249 }
250 match datatype {
251 Datatype::VariableLength { base_type, .. } => {
252 let element_size = if is_vlen_string_datatype(datatype) {
255 1
256 } else {
257 (base_type.type_size() as usize).max(1)
258 };
259 out.push(EmbeddedVlSlot {
260 byte_offset: base,
261 element_size,
262 });
263 true
264 }
265 Datatype::Compound { members, .. } => {
266 for m in members {
267 let Some(at) = usize::try_from(m.byte_offset)
268 .ok()
269 .and_then(|off| base.checked_add(off))
270 else {
271 return false;
272 };
273 if !collect_vlen_slots(&m.datatype, at, capacity, out) {
274 return false;
275 }
276 }
277 true
278 }
279 Datatype::Array {
280 base_type,
281 dimensions,
282 } => {
283 let mut probe = Vec::new();
295 if !collect_vlen_slots(base_type, 0, capacity, &mut probe) {
296 return false;
297 }
298 if probe.is_empty() {
300 return true;
301 }
302 let count = dimensions
303 .iter()
304 .copied()
305 .fold(1u64, |a, b| a.saturating_mul(u64::from(b)));
306 if count > capacity as u64 {
313 return false;
314 }
315 let entries = usize::try_from(count).unwrap_or(usize::MAX);
316 let stride = base_type.type_size() as usize;
317 for i in 0..entries {
318 let Some(at) = i.checked_mul(stride).and_then(|off| base.checked_add(off)) else {
319 return false;
320 };
321 for slot in &probe {
322 let Some(byte_offset) = at.checked_add(slot.byte_offset) else {
323 return false;
324 };
325 out.push(EmbeddedVlSlot {
326 byte_offset,
327 element_size: slot.element_size,
328 });
329 if out.len() > capacity {
332 return true;
333 }
334 }
335 }
336 true
337 }
338 _ => true,
341 }
342}
343
344fn check_element_limit(
345 num_elements: u64,
346 options: VlenStringReadOptions,
347) -> Result<(), FormatError> {
348 if let Some(limit) = options.max_elements
349 && num_elements > limit as u64
350 {
351 return Err(FormatError::VariableLengthElementLimitExceeded {
352 limit,
353 actual: num_elements,
354 });
355 }
356 Ok(())
357}
358
359fn payload_size(refs: &[VlElement], options: VlenStringReadOptions) -> Result<u64, FormatError> {
360 let mut required = 0u64;
361 for element in refs {
362 required =
363 required
364 .checked_add(u64::from(element.length))
365 .ok_or(FormatError::OffsetOverflow {
366 offset: required,
367 length: u64::from(element.length),
368 })?;
369 }
370 if let Some(limit) = options.max_payload_bytes
371 && required > limit as u64
372 {
373 return Err(FormatError::VariableLengthByteLimitExceeded { limit, required });
374 }
375 Ok(required)
376}
377
378pub fn vlen_string_payload_size(
380 raw_data: &[u8],
381 num_elements: u64,
382 offset_size: u8,
383) -> Result<u64, FormatError> {
384 check_element_limit(num_elements, VlenStringReadOptions::default())?;
385 let refs = parse_vl_references(raw_data, num_elements, offset_size)?;
386 payload_size(&refs, VlenStringReadOptions::default())
387}
388
389pub fn visit_vl_strings_from_source<S, F>(
392 source: &S,
393 raw_data: &[u8],
394 num_elements: u64,
395 offset_size: u8,
396 length_size: u8,
397 base_address: u64,
398 options: VlenStringReadOptions,
399 mut visitor: F,
400) -> Result<(), FormatError>
401where
402 S: Source + ?Sized,
403 F: FnMut(&str),
404{
405 check_element_limit(num_elements, options)?;
406 let refs = parse_vl_references(raw_data, num_elements, offset_size)?;
407 payload_size(&refs, options)?;
408
409 let mut wanted: Vec<(u64, Vec<u16>)> = Vec::new();
417 for element in &refs {
418 if is_undefined_addr(element.collection_address, offset_size)
419 || (element.length == 0 && element.collection_address == 0)
420 {
421 continue;
422 }
423 let Some(address) = element.collection_address.checked_add(base_address) else {
424 continue;
425 };
426 let Ok(index) = u16::try_from(element.object_index) else {
427 continue;
428 };
429 match wanted.binary_search_by_key(&address, |&(a, _)| a) {
430 Ok(pos) => wanted[pos].1.push(index),
431 Err(pos) => wanted.insert(pos, (address, Vec::from([index]))),
432 }
433 }
434 for (_, indices) in &mut wanted {
435 indices.sort_unstable();
436 }
437
438 let mut collections: Vec<(u64, GlobalHeapIndex)> = Vec::new();
439 for element in &refs {
440 if element.length == 0
441 && (is_undefined_addr(element.collection_address, offset_size)
442 || element.collection_address == 0)
443 {
444 visitor("");
445 continue;
446 }
447 if is_undefined_addr(element.collection_address, offset_size) {
448 return Err(FormatError::VlDataError(
449 "non-empty VL element has an undefined heap address".into(),
450 ));
451 }
452
453 let collection_address = element.collection_address.checked_add(base_address).ok_or(
454 FormatError::OffsetOverflow {
455 offset: element.collection_address,
456 length: base_address,
457 },
458 )?;
459 let collection_pos = match collections
460 .iter()
461 .position(|(address, _)| *address == collection_address)
462 {
463 Some(pos) => pos,
464 None => {
465 let keep = wanted
466 .binary_search_by_key(&collection_address, |&(a, _)| a)
467 .map(|pos| wanted[pos].1.as_slice())
468 .unwrap_or(&[]);
469 let collection = GlobalHeapIndex::parse_filtered(
470 source,
471 collection_address,
472 length_size,
473 |i| keep.binary_search(&i).is_ok(),
474 )?;
475 collections.push((collection_address, collection));
476 collections.len() - 1
477 }
478 };
479
480 let index = u16::try_from(element.object_index).map_err(|_| {
481 FormatError::VlDataError(format!(
482 "global heap object index {} does not fit u16",
483 element.object_index
484 ))
485 })?;
486 let object = collections[collection_pos].1.get_object(index).ok_or(
487 FormatError::GlobalHeapObjectNotFound {
488 collection_address,
489 index,
490 },
491 )?;
492 if u64::from(element.length) > object.size {
493 return Err(FormatError::VlDataError(format!(
494 "VL element length {} exceeds global heap object size {}",
495 element.length, object.size
496 )));
497 }
498
499 let bytes = source.read_exact_at(object.data_address, element.length as usize)?;
500 let string = String::from_utf8_lossy(&bytes);
501 visitor(&string);
502 }
503
504 Ok(())
505}
506
507pub fn read_vl_strings_from_source<S: Source + ?Sized>(
509 source: &S,
510 raw_data: &[u8],
511 num_elements: u64,
512 offset_size: u8,
513 length_size: u8,
514 base_address: u64,
515 options: VlenStringReadOptions,
516) -> Result<Vec<String>, FormatError> {
517 let mut strings = Vec::new();
518 visit_vl_strings_from_source(
519 source,
520 raw_data,
521 num_elements,
522 offset_size,
523 length_size,
524 base_address,
525 options,
526 |string| strings.push(String::from(string)),
527 )?;
528 Ok(strings)
529}
530
531#[derive(Debug, Clone, PartialEq, Eq)]
540pub(crate) enum VlByteObject {
541 Null,
543 Bytes(Vec<u8>),
545}
546
547pub(crate) fn read_vl_byte_objects_from_source<S: Source + ?Sized>(
561 source: &S,
562 raw_data: &[u8],
563 num_elements: u64,
564 offset_size: u8,
565 length_size: u8,
566 base_address: u64,
567 element_size: usize,
568 options: VlenStringReadOptions,
569) -> Result<Vec<VlByteObject>, FormatError> {
570 check_element_limit(num_elements, options)?;
571 let refs = parse_vl_references(raw_data, num_elements, offset_size)?;
572 payload_size(&refs, options)?;
573
574 let mut objects = Vec::with_capacity(refs.len());
575 let mut collections: Vec<(u64, GlobalHeapIndex)> = Vec::new();
576 for element in &refs {
577 if element.length == 0
578 && (is_undefined_addr(element.collection_address, offset_size)
579 || element.collection_address == 0)
580 {
581 objects.push(VlByteObject::Null);
582 continue;
583 }
584 if is_undefined_addr(element.collection_address, offset_size) {
585 return Err(FormatError::VlDataError(
586 "non-empty VL element has an undefined heap address".into(),
587 ));
588 }
589
590 let collection_address = element.collection_address.checked_add(base_address).ok_or(
591 FormatError::OffsetOverflow {
592 offset: element.collection_address,
593 length: base_address,
594 },
595 )?;
596 let collection_pos = match collections
597 .iter()
598 .position(|(address, _)| *address == collection_address)
599 {
600 Some(pos) => pos,
601 None => {
602 let collection = GlobalHeapIndex::parse(source, collection_address, length_size)?;
603 collections.push((collection_address, collection));
604 collections.len() - 1
605 }
606 };
607
608 let index = u16::try_from(element.object_index).map_err(|_| {
609 FormatError::VlDataError(format!(
610 "global heap object index {} does not fit u16",
611 element.object_index
612 ))
613 })?;
614 let object = collections[collection_pos].1.get_object(index).ok_or(
615 FormatError::GlobalHeapObjectNotFound {
616 collection_address,
617 index,
618 },
619 )?;
620 let byte_len = (element.length as u64)
624 .checked_mul(element_size as u64)
625 .ok_or(FormatError::OffsetOverflow {
626 offset: u64::from(element.length),
627 length: element_size as u64,
628 })?;
629 if byte_len > object.size {
630 return Err(FormatError::VlDataError(format!(
631 "VL element length {} ({} bytes) exceeds global heap object size {}",
632 element.length, byte_len, object.size
633 )));
634 }
635
636 let bytes = source.read_exact_at(object.data_address, byte_len.to_usize()?)?;
637 objects.push(VlByteObject::Bytes(bytes));
638 }
639
640 Ok(objects)
641}
642
643#[cfg(test)]
648pub fn read_vl_strings(
649 file_data: &[u8],
650 raw_data: &[u8],
651 num_elements: u64,
652 offset_size: u8,
653 length_size: u8,
654) -> Result<Vec<String>, FormatError> {
655 read_vl_strings_from_source(
656 &BytesSource::new(file_data),
657 raw_data,
658 num_elements,
659 offset_size,
660 length_size,
661 0,
662 VlenStringReadOptions::default(),
663 )
664}
665
666#[cfg(test)]
667mod tests {
668 use super::*;
669
670 fn build_gcol_at(
672 file_data: &mut Vec<u8>,
673 offset: usize,
674 objects: &[(u16, &[u8])], ) {
676 let length_size = 8usize;
677
678 let header_size = 8 + length_size;
680 let mut obj_total = 0usize;
681 for (_, data) in objects {
682 let padded = (data.len() + 7) & !7;
683 obj_total += 8 + length_size + padded;
684 }
685 obj_total += 2; let collection_size = header_size + obj_total;
687 let needed = offset + collection_size;
688 if file_data.len() < needed {
689 file_data.resize(needed, 0);
690 }
691
692 let mut pos = offset;
693 file_data[pos..pos + 4].copy_from_slice(b"GCOL");
695 file_data[pos + 4] = 1; pos += 8;
698 file_data[pos..pos + 8].copy_from_slice(&(collection_size as u64).to_le_bytes());
699 pos += 8;
700
701 for (index, data) in objects {
702 file_data[pos..pos + 2].copy_from_slice(&index.to_le_bytes());
703 file_data[pos + 2..pos + 4].copy_from_slice(&1u16.to_le_bytes()); pos += 8;
706 file_data[pos..pos + 8].copy_from_slice(&(data.len() as u64).to_le_bytes());
707 pos += 8;
708 file_data[pos..pos + data.len()].copy_from_slice(data);
709 let padded = (data.len() + 7) & !7;
710 pos += padded;
711 }
712 file_data[pos..pos + 2].copy_from_slice(&0u16.to_le_bytes());
714 }
715
716 fn build_vl_refs(
718 strings: &[&str],
719 collection_address: u64,
720 start_index: u16,
721 offset_size: u8,
722 ) -> Vec<u8> {
723 let mut raw = Vec::new();
724 for (i, s) in strings.iter().enumerate() {
725 raw.extend_from_slice(&(s.len() as u32).to_le_bytes());
726 match offset_size {
727 4 => raw.extend_from_slice(&(collection_address as u32).to_le_bytes()),
728 8 => raw.extend_from_slice(&collection_address.to_le_bytes()),
729 _ => panic!("unsupported"),
730 }
731 raw.extend_from_slice(&(start_index as u32 + i as u32).to_le_bytes());
732 }
733 raw
734 }
735
736 #[test]
737 fn parse_vl_references_two_elements() {
738 let raw = build_vl_refs(&["hello", "world"], 0x1000, 1, 8);
739 let refs = parse_vl_references(&raw, 2, 8).unwrap();
740 assert_eq!(refs.len(), 2);
741 assert_eq!(refs[0].length, 5);
742 assert_eq!(refs[0].collection_address, 0x1000);
743 assert_eq!(refs[0].object_index, 1);
744 assert_eq!(refs[1].length, 5);
745 assert_eq!(refs[1].object_index, 2);
746 }
747
748 #[test]
749 fn read_vl_strings_from_heap() {
750 let gcol_offset = 256usize;
751 let mut file_data = vec![0u8; 512];
752 build_gcol_at(&mut file_data, gcol_offset, &[(1, b"Alice"), (2, b"Bob")]);
753
754 let raw = build_vl_refs(&["Alice", "Bob"], gcol_offset as u64, 1, 8);
755 let strings = read_vl_strings(&file_data, &raw, 2, 8, 8).unwrap();
756 assert_eq!(strings, vec!["Alice", "Bob"]);
757 }
758
759 #[cfg(feature = "std")]
760 #[test]
761 fn read_vl_strings_from_seekable_source() {
762 use std::io::Cursor;
763
764 use crate::source::ReadSeekSource;
765
766 let gcol_offset = 256usize;
767 let mut file_data = vec![0u8; 512];
768 build_gcol_at(&mut file_data, gcol_offset, &[(1, b"Alice"), (2, b"Bob")]);
769 let raw = build_vl_refs(&["Alice", "Bob"], gcol_offset as u64, 1, 8);
770 let source = ReadSeekSource::new(Cursor::new(file_data)).unwrap();
771
772 let strings = read_vl_strings_from_source(
773 &source,
774 &raw,
775 2,
776 8,
777 8,
778 0,
779 VlenStringReadOptions::default(),
780 )
781 .unwrap();
782 assert_eq!(strings, vec!["Alice", "Bob"]);
783 }
784
785 #[test]
786 fn null_vl_element_empty_string() {
787 let mut raw = Vec::new();
789 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];
794 let strings = read_vl_strings(&file_data, &raw, 1, 8, 8).unwrap();
795 assert_eq!(strings, vec![""]);
796 }
797
798 #[test]
799 fn null_vl_element_zero_address() {
800 let mut raw = Vec::new();
801 raw.extend_from_slice(&0u32.to_le_bytes());
802 raw.extend_from_slice(&0u64.to_le_bytes());
803 raw.extend_from_slice(&0u32.to_le_bytes());
804
805 let file_data = vec![0u8; 16];
806 let strings = read_vl_strings(&file_data, &raw, 1, 8, 8).unwrap();
807 assert_eq!(strings, vec![""]);
808 }
809
810 #[test]
811 fn parse_vl_references_truncated_error() {
812 let raw = vec![0u8; 10]; let err = parse_vl_references(&raw, 1, 8).unwrap_err();
814 assert!(matches!(err, FormatError::UnexpectedEof { .. }));
815 }
816}
817
818#[cfg(test)]
819mod embedded_slot_tests {
820 use super::*;
821 use crate::datatype::{CompoundMember, StringPadding};
822
823 fn vlen_string() -> Datatype {
824 Datatype::VariableLength {
825 is_string: true,
826 base_type: Box::new(Datatype::String {
827 size: 1,
828 charset: CharacterSet::Utf8,
829 padding: StringPadding::NullTerminate,
830 }),
831 padding: Some(StringPadding::NullTerminate),
832 charset: Some(CharacterSet::Utf8),
833 }
834 }
835
836 fn vlen_i32_sequence() -> Datatype {
837 Datatype::VariableLength {
838 is_string: false,
839 base_type: Box::new(Datatype::FixedPoint {
840 size: 4,
841 signed: true,
842 byte_order: crate::datatype::DatatypeByteOrder::LittleEndian,
843 bit_offset: 0,
844 bit_precision: 32,
845 }),
846 padding: None,
847 charset: None,
848 }
849 }
850
851 fn i32_type() -> Datatype {
852 Datatype::FixedPoint {
853 size: 4,
854 signed: true,
855 byte_order: crate::datatype::DatatypeByteOrder::LittleEndian,
856 bit_offset: 0,
857 bit_precision: 32,
858 }
859 }
860
861 fn member(name: &str, byte_offset: u64, datatype: Datatype) -> CompoundMember {
862 CompoundMember {
863 name: name.to_string(),
864 byte_offset,
865 datatype,
866 }
867 }
868
869 #[test]
870 fn a_plain_datatype_reaches_no_reference() {
871 assert_eq!(embedded_vlen_slots(&i32_type()), Some(Vec::new()));
872 }
873
874 #[test]
875 fn a_top_level_vlen_is_its_own_slot() {
876 assert_eq!(
877 embedded_vlen_slots(&vlen_string()),
878 Some(vec![EmbeddedVlSlot {
879 byte_offset: 0,
880 element_size: 1
881 }])
882 );
883 }
884
885 #[test]
889 fn compound_members_keep_their_own_element_size() {
890 let dt = Datatype::Compound {
891 size: 40,
892 members: vec![
893 member("label", 0, vlen_string()),
894 member("id", 16, i32_type()),
895 member("samples", 24, vlen_i32_sequence()),
896 ],
897 };
898 assert_eq!(
899 embedded_vlen_slots(&dt),
900 Some(vec![
901 EmbeddedVlSlot {
902 byte_offset: 0,
903 element_size: 1
904 },
905 EmbeddedVlSlot {
906 byte_offset: 24,
907 element_size: 4
908 },
909 ])
910 );
911 }
912
913 #[test]
916 fn nested_compounds_and_arrays_are_walked() {
917 let inner = Datatype::Compound {
918 size: 20,
919 members: vec![member("id", 0, i32_type()), member("s", 4, vlen_string())],
920 };
921 let nested = Datatype::Compound {
922 size: 24,
923 members: vec![
924 member("n", 0, i32_type()),
925 member("inner", 4, inner.clone()),
926 ],
927 };
928 assert_eq!(
929 embedded_vlen_slots(&nested),
930 Some(vec![EmbeddedVlSlot {
931 byte_offset: 8,
932 element_size: 1
933 }])
934 );
935
936 let array = Datatype::Array {
937 base_type: Box::new(inner),
938 dimensions: vec![3],
939 };
940 assert_eq!(
941 embedded_vlen_slots(&array),
942 Some(vec![
943 EmbeddedVlSlot {
944 byte_offset: 4,
945 element_size: 1
946 },
947 EmbeddedVlSlot {
948 byte_offset: 24,
949 element_size: 1
950 },
951 EmbeddedVlSlot {
952 byte_offset: 44,
953 element_size: 1
954 },
955 ])
956 );
957 }
958
959 #[test]
963 fn a_datatype_too_small_for_its_own_references_is_rejected() {
964 let dt = Datatype::Compound {
965 size: 8, members: vec![member("s", 0, vlen_string())],
967 };
968 assert_eq!(embedded_vlen_slots(&dt), None);
969 }
970
971 #[test]
974 fn an_array_declaring_absurd_dimensions_is_rejected_not_walked() {
975 let dt = Datatype::Array {
976 base_type: Box::new(vlen_string()),
977 dimensions: vec![u32::MAX, u32::MAX],
978 };
979 assert_eq!(embedded_vlen_slots(&dt), None);
980 }
981
982 #[test]
991 fn deeply_nested_arrays_cost_time_proportional_to_their_slots() {
992 let mut dt = vlen_string();
993 for _ in 0..17 {
994 dt = Datatype::Array {
995 base_type: Box::new(dt),
996 dimensions: vec![2],
997 };
998 }
999 let started = std::time::Instant::now();
1000 let slots = embedded_vlen_slots(&dt).expect("a well-formed nesting must be walkable");
1001 let elapsed = started.elapsed();
1002
1003 assert_eq!(slots.len(), 1 << 17, "one slot per leaf entry");
1004 assert!(
1009 elapsed < std::time::Duration::from_secs(10),
1010 "walking {} slots took {elapsed:?}; the walk is no longer linear in its output",
1011 slots.len()
1012 );
1013 }
1014
1015 #[test]
1021 fn a_member_offset_beyond_the_address_space_is_rejected() {
1022 let dt = Datatype::Compound {
1023 size: 40,
1024 members: vec![member("s", u64::MAX - 8, vlen_string())],
1025 };
1026 assert_eq!(embedded_vlen_slots(&dt), None);
1027 }
1028}