1#[cfg(not(feature = "std"))]
7use alloc::{boxed::Box, string::String, vec, vec::Vec};
8
9use core::fmt;
10use core::num::{NonZeroU32, NonZeroUsize};
11
12use byteorder::{ByteOrder, LittleEndian};
13
14use crate::bytes::ensure_len;
15use crate::display::{DISPLAY_MAX_MEMBERS, Dims, EscapedName, QuotedBytes, write_elided};
16use crate::error::FormatError;
17
18#[derive(Debug, Clone, PartialEq)]
20pub enum DatatypeByteOrder {
21 LittleEndian,
22 BigEndian,
23 Vax,
24}
25
26#[derive(Debug, Clone, PartialEq)]
28pub enum StringPadding {
29 NullTerminate,
30 NullPad,
31 SpacePad,
32}
33
34#[derive(Debug, Clone, PartialEq)]
36pub enum CharacterSet {
37 Ascii,
38 Utf8,
39}
40
41#[derive(Debug, Clone, PartialEq)]
46#[non_exhaustive]
47pub enum ReferenceType {
48 Object,
49 DatasetRegion,
50}
51
52#[derive(Debug, Clone, PartialEq)]
59#[non_exhaustive]
60pub struct CompoundMember {
61 pub name: String,
63 pub byte_offset: u64,
65 pub datatype: Datatype,
67}
68
69#[derive(Debug, Clone, PartialEq)]
76#[non_exhaustive]
77pub struct EnumMember {
78 pub name: String,
80 pub value: Vec<u8>,
82}
83
84#[derive(Debug, Clone, PartialEq)]
93#[non_exhaustive]
94pub enum Datatype {
95 FixedPoint {
97 size: u32,
98 byte_order: DatatypeByteOrder,
99 signed: bool,
100 bit_offset: u16,
101 bit_precision: u16,
102 },
103 FloatingPoint {
105 size: u32,
106 byte_order: DatatypeByteOrder,
107 bit_offset: u16,
108 bit_precision: u16,
109 exponent_location: u8,
110 exponent_size: u8,
111 mantissa_location: u8,
112 mantissa_size: u8,
113 exponent_bias: u32,
114 },
115 Time {
117 size: u32,
118 byte_order: DatatypeByteOrder,
119 bit_precision: u16,
120 },
121 String {
123 size: u32,
124 padding: StringPadding,
125 charset: CharacterSet,
126 },
127 BitField {
129 size: u32,
130 byte_order: DatatypeByteOrder,
131 bit_offset: u16,
132 bit_precision: u16,
133 },
134 Opaque { size: u32, tag: Vec<u8> },
136 Compound {
138 size: u32,
139 members: Vec<CompoundMember>,
140 },
141 Reference { size: u32, ref_type: ReferenceType },
143 Enumeration {
145 size: u32,
146 base_type: Box<Datatype>,
147 members: Vec<EnumMember>,
148 },
149 VariableLength {
151 is_string: bool,
152 padding: Option<StringPadding>,
153 charset: Option<CharacterSet>,
154 base_type: Box<Datatype>,
155 },
156 Array {
158 base_type: Box<Datatype>,
159 dimensions: Vec<u32>,
160 },
161}
162
163impl fmt::Display for DatatypeByteOrder {
172 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173 f.pad(match self {
174 Self::LittleEndian => "le",
175 Self::BigEndian => "be",
176 Self::Vax => "vax",
177 })
178 }
179}
180
181impl fmt::Display for StringPadding {
182 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183 f.pad(match self {
184 Self::NullTerminate => "null-term",
185 Self::NullPad => "null-pad",
186 Self::SpacePad => "space-pad",
187 })
188 }
189}
190
191impl fmt::Display for CharacterSet {
192 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193 f.pad(match self {
194 Self::Ascii => "ascii",
195 Self::Utf8 => "utf8",
196 })
197 }
198}
199
200impl fmt::Display for ReferenceType {
201 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202 f.pad(match self {
203 Self::Object => "object_ref",
204 Self::DatasetRegion => "region_ref",
205 })
206 }
207}
208
209fn bit_width(size: u32) -> u64 {
214 u64::from(size) * 8
215}
216
217fn write_bit_span(
219 f: &mut fmt::Formatter<'_>,
220 size: u32,
221 bit_offset: u16,
222 bit_precision: u16,
223) -> fmt::Result {
224 if bit_offset != 0 || u64::from(bit_precision) != bit_width(size) {
225 let end = u64::from(bit_offset) + u64::from(bit_precision);
226 write!(f, "(bits {bit_offset}..{end})")?;
227 }
228 Ok(())
229}
230
231fn write_byte_order(f: &mut fmt::Formatter<'_>, byte_order: &DatatypeByteOrder) -> fmt::Result {
233 if *byte_order != DatatypeByteOrder::LittleEndian {
234 write!(f, " {byte_order}")?;
235 }
236 Ok(())
237}
238
239impl fmt::Display for Datatype {
240 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241 match self {
242 Self::FixedPoint {
243 size,
244 byte_order,
245 signed,
246 bit_offset,
247 bit_precision,
248 } => {
249 let sign = if *signed { 'i' } else { 'u' };
250 write!(f, "{sign}{}", bit_width(*size))?;
251 write_bit_span(f, *size, *bit_offset, *bit_precision)?;
252 write_byte_order(f, byte_order)
253 }
254 Self::FloatingPoint {
255 size,
256 byte_order,
257 bit_offset,
258 bit_precision,
259 ..
260 } => {
261 write!(f, "f{}", bit_width(*size))?;
262 write_bit_span(f, *size, *bit_offset, *bit_precision)?;
263 write_byte_order(f, byte_order)
264 }
265 Self::Time {
266 size,
267 byte_order,
268 bit_precision,
269 } => {
270 write!(f, "time{}", bit_width(*size))?;
271 write_bit_span(f, *size, 0, *bit_precision)?;
272 write_byte_order(f, byte_order)
273 }
274 Self::String {
275 size,
276 padding,
277 charset,
278 } => write!(f, "string[{size}] {charset} {padding}"),
279 Self::BitField {
280 size,
281 byte_order,
282 bit_offset,
283 bit_precision,
284 } => {
285 write!(f, "bitfield{}", bit_width(*size))?;
286 write_bit_span(f, *size, *bit_offset, *bit_precision)?;
287 write_byte_order(f, byte_order)
288 }
289 Self::Opaque { size, tag } => {
290 write!(f, "opaque[{size}]")?;
291 if !tag.is_empty() {
292 write!(f, " {}", QuotedBytes(tag))?;
293 }
294 Ok(())
295 }
296 Self::Compound { members, .. } => {
297 f.write_str("compound{")?;
298 for (i, member) in members.iter().take(DISPLAY_MAX_MEMBERS).enumerate() {
299 if i > 0 {
300 f.write_str(", ")?;
301 }
302 write!(f, "{}: {}", EscapedName(&member.name), member.datatype)?;
303 }
304 write_elided(f, members.len().saturating_sub(DISPLAY_MAX_MEMBERS))?;
305 f.write_str("}")
306 }
307 Self::Reference { ref_type, .. } => write!(f, "{ref_type}"),
308 Self::Enumeration {
309 base_type, members, ..
310 } => {
311 write!(f, "enum<{base_type}>[")?;
312 for (i, member) in members.iter().take(DISPLAY_MAX_MEMBERS).enumerate() {
313 if i > 0 {
314 f.write_str(", ")?;
315 }
316 write!(f, "{}", EscapedName(&member.name))?;
317 }
318 write_elided(f, members.len().saturating_sub(DISPLAY_MAX_MEMBERS))?;
319 f.write_str("]")
320 }
321 Self::VariableLength {
322 is_string,
323 charset,
324 base_type,
325 ..
326 } => {
327 if *is_string {
328 f.write_str("vlen_string")?;
329 if let Some(charset) = charset {
330 write!(f, " {charset}")?;
331 }
332 Ok(())
333 } else {
334 write!(f, "vlen<{base_type}>")
335 }
336 }
337 Self::Array {
338 base_type,
339 dimensions,
340 } => write!(f, "array<{base_type}, {}>", Dims(dimensions)),
341 }
342 }
343}
344
345fn parse_string_padding(val: u8) -> Result<StringPadding, FormatError> {
346 match val {
347 0 => Ok(StringPadding::NullTerminate),
348 1 => Ok(StringPadding::NullPad),
349 2 => Ok(StringPadding::SpacePad),
350 _ => Err(FormatError::InvalidStringPadding(val)),
351 }
352}
353
354fn parse_charset(val: u8) -> Result<CharacterSet, FormatError> {
355 match val {
356 0 => Ok(CharacterSet::Ascii),
357 1 => Ok(CharacterSet::Utf8),
358 _ => Err(FormatError::InvalidCharacterSet(val)),
359 }
360}
361
362fn read_null_terminated_string(data: &[u8], offset: usize) -> Result<(String, usize), FormatError> {
365 if offset >= data.len() {
366 return Err(FormatError::UnexpectedEof {
367 expected: offset + 1,
368 available: data.len(),
369 });
370 }
371 let remaining = &data[offset..];
372 let null_pos = remaining
373 .iter()
374 .position(|&b| b == 0)
375 .ok_or(FormatError::UnexpectedEof {
376 expected: offset + 1,
377 available: data.len(),
378 })?;
379 let name = String::from_utf8_lossy(&remaining[..null_pos]).into_owned();
380 Ok((name, null_pos + 1))
381}
382
383fn offset_bytes_for_size(compound_size: u32) -> usize {
385 if compound_size <= 0xFF {
386 1
387 } else if compound_size <= 0xFFFF {
388 2
389 } else {
390 4
391 }
392}
393
394fn read_uint(data: &[u8], offset: usize, nbytes: usize) -> Result<u64, FormatError> {
396 ensure_len(data, offset, nbytes)?;
397 let slice = &data[offset..offset + nbytes];
398 Ok(match nbytes {
399 1 => slice[0] as u64,
400 2 => LittleEndian::read_u16(slice) as u64,
401 4 => LittleEndian::read_u32(slice) as u64,
402 8 => LittleEndian::read_u64(slice),
403 _ => {
404 return Err(FormatError::UnexpectedEof {
405 expected: offset + nbytes,
406 available: data.len(),
407 });
408 }
409 })
410}
411
412impl Datatype {
413 pub(crate) fn parse(data: &[u8]) -> Result<(Datatype, usize), FormatError> {
431 ensure_len(data, 0, 8)?;
433
434 let class_and_version = data[0];
435 let class_id = class_and_version & 0x0F;
436 let version = (class_and_version >> 4) & 0x0F;
437
438 let bf0 = data[1];
440 let bf1 = data[2];
441 let bf2 = data[3];
442 let _bit_field_24 = (bf0 as u32) | ((bf1 as u32) << 8) | ((bf2 as u32) << 16);
443
444 let size = LittleEndian::read_u32(&data[4..8]);
445 let mut pos = 8;
446
447 let parsed = match class_id {
448 0 => {
449 ensure_len(data, pos, 4)?;
451 let byte_order = if bf0 & 0x01 == 0 {
452 DatatypeByteOrder::LittleEndian
453 } else {
454 DatatypeByteOrder::BigEndian
455 };
456 let signed = (bf0 >> 3) & 0x01 == 1;
457 let bit_offset = LittleEndian::read_u16(&data[pos..pos + 2]);
458 let bit_precision = LittleEndian::read_u16(&data[pos + 2..pos + 4]);
459 pos += 4;
460 Ok((
461 Datatype::FixedPoint {
462 size,
463 byte_order,
464 signed,
465 bit_offset,
466 bit_precision,
467 },
468 pos,
469 ))
470 }
471 1 => {
472 ensure_len(data, pos, 12)?;
474 let bo_low = bf0 & 0x01;
475 let bo_high = (bf0 >> 6) & 0x01;
476 let byte_order = match (bo_high, bo_low) {
477 (0, 0) => DatatypeByteOrder::LittleEndian,
478 (0, 1) => DatatypeByteOrder::BigEndian,
479 (1, 0) => DatatypeByteOrder::Vax,
480 (1, 1) => DatatypeByteOrder::Vax,
481 _ => unreachable!(),
482 };
483 let bit_offset = LittleEndian::read_u16(&data[pos..pos + 2]);
484 let bit_precision = LittleEndian::read_u16(&data[pos + 2..pos + 4]);
485 let exponent_location = data[pos + 4];
486 let exponent_size = data[pos + 5];
487 let mantissa_location = data[pos + 6];
488 let mantissa_size = data[pos + 7];
489 let exponent_bias = LittleEndian::read_u32(&data[pos + 8..pos + 12]);
490 pos += 12;
491 Ok((
492 Datatype::FloatingPoint {
493 size,
494 byte_order,
495 bit_offset,
496 bit_precision,
497 exponent_location,
498 exponent_size,
499 mantissa_location,
500 mantissa_size,
501 exponent_bias,
502 },
503 pos,
504 ))
505 }
506 2 => {
507 ensure_len(data, pos, 2)?;
509 let byte_order = if bf0 & 0x01 == 0 {
510 DatatypeByteOrder::LittleEndian
511 } else {
512 DatatypeByteOrder::BigEndian
513 };
514 let bit_precision = LittleEndian::read_u16(&data[pos..pos + 2]);
515 pos += 2;
516 Ok((
517 Datatype::Time {
518 size,
519 byte_order,
520 bit_precision,
521 },
522 pos,
523 ))
524 }
525 3 => {
526 let padding_val = bf0 & 0x0F;
528 let charset_val = (bf0 >> 4) & 0x0F;
529 let padding = parse_string_padding(padding_val)?;
530 let charset = parse_charset(charset_val)?;
531 Ok((
532 Datatype::String {
533 size,
534 padding,
535 charset,
536 },
537 pos,
538 ))
539 }
540 4 => {
541 ensure_len(data, pos, 4)?;
543 let byte_order = if bf0 & 0x01 == 0 {
544 DatatypeByteOrder::LittleEndian
545 } else {
546 DatatypeByteOrder::BigEndian
547 };
548 let bit_offset = LittleEndian::read_u16(&data[pos..pos + 2]);
549 let bit_precision = LittleEndian::read_u16(&data[pos + 2..pos + 4]);
550 pos += 4;
551 Ok((
552 Datatype::BitField {
553 size,
554 byte_order,
555 bit_offset,
556 bit_precision,
557 },
558 pos,
559 ))
560 }
561 5 => {
562 let tag_len = bf0 as usize;
564 ensure_len(data, pos, tag_len)?;
565 let tag = data[pos..pos + tag_len].to_vec();
566 let padded = (tag_len + 7) & !7;
568 let pos = 8 + padded; Ok((Datatype::Opaque { size, tag }, pos))
570 }
571 6 => {
572 let num_members = (bf0 as u16) | ((bf1 as u16) << 8);
574 let mut members = Vec::with_capacity(num_members as usize);
575
576 if (3..=5).contains(&version) {
581 let ob = offset_bytes_for_size(size);
582 for _ in 0..num_members {
583 let (name, name_len) = read_null_terminated_string(data, pos)?;
584 pos += name_len;
585 let byte_offset = read_uint(data, pos, ob)?;
586 pos += ob;
587 let (member_dt, consumed) = Datatype::parse(&data[pos..])?;
588 pos += consumed;
589 members.push(CompoundMember {
590 name,
591 byte_offset,
592 datatype: member_dt,
593 });
594 }
595 } else if version == 1 || version == 2 {
596 for _ in 0..num_members {
603 let (name, name_len) = read_null_terminated_string(data, pos)?;
604 let padded = (name_len + 7) & !7;
605 pos += padded;
606 ensure_len(data, pos, 4)?;
607 let byte_offset = LittleEndian::read_u32(&data[pos..pos + 4]) as u64;
608 pos += 4;
609 if version == 1 {
610 ensure_len(data, pos, 28)?;
611 pos += 28;
612 }
613 let (member_dt, consumed) = Datatype::parse(&data[pos..])?;
614 pos += consumed;
615 members.push(CompoundMember {
616 name,
617 byte_offset,
618 datatype: member_dt,
619 });
620 }
621 } else {
622 return Err(FormatError::InvalidDatatypeVersion {
623 class: class_id,
624 version,
625 });
626 }
627
628 Ok((Datatype::Compound { size, members }, pos))
629 }
630 7 => {
631 let ref_type_val = bf0 & 0x0F;
633 let ref_type = match ref_type_val {
634 0 => ReferenceType::Object,
635 1 => ReferenceType::DatasetRegion,
636 _ => return Err(FormatError::InvalidReferenceType(ref_type_val)),
637 };
638 Ok((Datatype::Reference { size, ref_type }, pos))
639 }
640 8 => {
641 let num_members = (bf0 as u16) | ((bf1 as u16) << 8);
643 let (base_type, base_consumed) = Datatype::parse(&data[pos..])?;
645 pos += base_consumed;
646 let base_size = base_type.type_size();
647 let mut members = Vec::with_capacity(num_members as usize);
648 let mut member_names = Vec::with_capacity(num_members as usize);
652 for _ in 0..num_members {
653 let (name, name_len) = read_null_terminated_string(data, pos)?;
654 if version < 3 {
655 let padded = (name_len + 7) & !7;
656 pos += padded;
657 } else {
658 pos += name_len;
659 }
660 member_names.push(name);
661 }
662 for name in &member_names {
664 ensure_len(data, pos, base_size as usize)?;
665 let value = data[pos..pos + base_size as usize].to_vec();
666 pos += base_size as usize;
667 members.push(EnumMember {
668 name: name.clone(),
669 value,
670 });
671 }
672 Ok((
673 Datatype::Enumeration {
674 size,
675 base_type: Box::new(base_type),
676 members,
677 },
678 pos,
679 ))
680 }
681 9 => {
682 let vl_type = bf0 & 0x0F;
684 let is_string = vl_type == 1;
685 let padding = if is_string {
686 let pad_val = (bf0 >> 4) & 0x0F;
687 Some(parse_string_padding(pad_val)?)
688 } else {
689 None
690 };
691 let charset = if is_string {
692 let cs_val = bf1 & 0x0F;
693 Some(parse_charset(cs_val)?)
694 } else {
695 None
696 };
697 let (base_type, consumed) = Datatype::parse(&data[pos..])?;
698 pos += consumed;
699 Ok((
700 Datatype::VariableLength {
701 is_string,
702 padding,
703 charset,
704 base_type: Box::new(base_type),
705 },
706 pos,
707 ))
708 }
709 10 => {
710 if version == 2 {
712 ensure_len(data, pos, 4)?;
713 let ndims = data[pos] as usize;
714 pos += 4; ensure_len(data, pos, ndims * 4 + ndims * 4)?;
716 let mut dimensions = Vec::with_capacity(ndims);
717 for _ in 0..ndims {
718 dimensions.push(LittleEndian::read_u32(&data[pos..pos + 4]));
719 pos += 4;
720 }
721 pos += ndims * 4;
723 let (base_type, consumed) = Datatype::parse(&data[pos..])?;
724 pos += consumed;
725 Ok((
726 Datatype::Array {
727 base_type: Box::new(base_type),
728 dimensions,
729 },
730 pos,
731 ))
732 } else if (3..=5).contains(&version) {
733 ensure_len(data, pos, 1)?;
734 let ndims = data[pos] as usize;
735 pos += 1;
736 ensure_len(data, pos, ndims * 4)?;
737 let mut dimensions = Vec::with_capacity(ndims);
738 for _ in 0..ndims {
739 dimensions.push(LittleEndian::read_u32(&data[pos..pos + 4]));
740 pos += 4;
741 }
742 let (base_type, consumed) = Datatype::parse(&data[pos..])?;
743 pos += consumed;
744 Ok((
745 Datatype::Array {
746 base_type: Box::new(base_type),
747 dimensions,
748 },
749 pos,
750 ))
751 } else {
752 Err(FormatError::InvalidDatatypeVersion {
753 class: class_id,
754 version,
755 })
756 }
757 }
758 11 => {
759 let num_members = (bf0 as u16) | ((bf1 as u16) << 8);
765 let mut members = Vec::with_capacity(num_members as usize);
766 let ob = offset_bytes_for_size(size);
767 for _ in 0..num_members {
768 let (name, name_len) = read_null_terminated_string(data, pos)?;
769 pos += name_len;
770 let byte_offset = read_uint(data, pos, ob)?;
771 pos += ob;
772 let (member_dt, consumed) = Datatype::parse(&data[pos..])?;
773 pos += consumed;
774 members.push(CompoundMember {
775 name,
776 byte_offset,
777 datatype: member_dt,
778 });
779 }
780 Ok((Datatype::Compound { size, members }, pos))
781 }
782 _ => Err(FormatError::InvalidDatatypeClass(class_id)),
783 };
784
785 let (datatype, consumed) = parsed?;
790 if datatype.type_size() == 0 {
791 return Err(FormatError::ZeroSizedDatatype { class: class_id });
792 }
793 Ok((datatype, consumed))
794 }
795
796 pub(crate) fn serialize(&self) -> Vec<u8> {
803 match self {
804 Datatype::FixedPoint {
805 size,
806 byte_order,
807 signed,
808 bit_offset,
809 bit_precision,
810 } => {
811 let mut bf0 = 0u8;
812 if matches!(byte_order, DatatypeByteOrder::BigEndian) {
813 bf0 |= 0x01;
814 }
815 if *signed {
816 bf0 |= 0x08;
817 }
818 let mut buf = Self::build_header(0, 1, [bf0, 0, 0], *size);
819 buf.extend_from_slice(&bit_offset.to_le_bytes());
820 buf.extend_from_slice(&bit_precision.to_le_bytes());
821 buf
822 }
823 Datatype::FloatingPoint {
824 size,
825 byte_order,
826 bit_offset,
827 bit_precision,
828 exponent_location,
829 exponent_size,
830 mantissa_location,
831 mantissa_size,
832 exponent_bias,
833 } => {
834 let mut bf0 = 0x20u8; match byte_order {
836 DatatypeByteOrder::BigEndian => {
837 bf0 |= 0x01;
838 }
839 DatatypeByteOrder::Vax => {
840 bf0 |= 0x40;
841 }
842 _ => {}
843 }
844 #[expect(
846 clippy::cast_possible_truncation,
847 reason = "size is an element byte size; *8-1 is a bit index that fits in a u8 (at most 63 for an 8-byte element)"
848 )]
849 let bf1 = (*size * 8 - 1) as u8;
850 let mut buf = Self::build_header(1, 1, [bf0, bf1, 0], *size);
851 buf.extend_from_slice(&bit_offset.to_le_bytes());
852 buf.extend_from_slice(&bit_precision.to_le_bytes());
853 buf.push(*exponent_location);
854 buf.push(*exponent_size);
855 buf.push(*mantissa_location);
856 buf.push(*mantissa_size);
857 buf.extend_from_slice(&exponent_bias.to_le_bytes());
858 buf
859 }
860 Datatype::String {
861 size,
862 padding,
863 charset,
864 } => {
865 let pad_val = match padding {
866 StringPadding::NullTerminate => 0,
867 StringPadding::NullPad => 1,
868 StringPadding::SpacePad => 2,
869 };
870 let cs_val = match charset {
871 CharacterSet::Ascii => 0,
872 CharacterSet::Utf8 => 1,
873 };
874 let bf0 = pad_val | (cs_val << 4);
875 Self::build_header(3, 1, [bf0, 0, 0], *size)
876 }
877 Datatype::VariableLength {
878 is_string,
879 padding,
880 charset,
881 base_type,
882 } => {
883 let mut bf0 = if *is_string { 0x01u8 } else { 0x00 };
884 if *is_string && let Some(p) = padding {
885 let pv = match p {
886 StringPadding::NullTerminate => 0,
887 StringPadding::NullPad => 1,
888 StringPadding::SpacePad => 2,
889 };
890 bf0 |= pv << 4;
891 }
892 let bf1 = if *is_string {
893 charset.as_ref().map_or(0, |c| match c {
894 CharacterSet::Ascii => 0,
895 CharacterSet::Utf8 => 1,
896 })
897 } else {
898 0
899 };
900 let mut buf = Self::build_header(9, 1, [bf0, bf1, 0], 16);
901 buf.extend_from_slice(&base_type.serialize());
902 buf
903 }
904 Datatype::Compound { size, members } => {
905 #[expect(
906 clippy::cast_possible_truncation,
907 reason = "compound member count is written into the 2-byte member-count field of the datatype message"
908 )]
909 let num = members.len() as u16;
910 let bf0 = (num & 0xFF) as u8;
911 let bf1 = ((num >> 8) & 0xFF) as u8;
912 let mut buf = Self::build_header(6, 3, [bf0, bf1, 0], *size);
913 let ob = offset_bytes_for_size(*size);
914 for m in members {
915 buf.extend_from_slice(m.name.as_bytes());
917 buf.push(0);
918 #[expect(
920 clippy::cast_possible_truncation,
921 reason = "ob is the offset-byte width chosen to hold byte_offset, so each arm casts to a width that fits by construction"
922 )]
923 match ob {
924 1 => buf.push(m.byte_offset as u8),
925 2 => buf.extend_from_slice(&(m.byte_offset as u16).to_le_bytes()),
926 _ => buf.extend_from_slice(&(m.byte_offset as u32).to_le_bytes()),
927 }
928 buf.extend_from_slice(&m.datatype.serialize());
930 }
931 buf
932 }
933 Datatype::Enumeration {
934 size,
935 base_type,
936 members,
937 } => {
938 #[expect(
939 clippy::cast_possible_truncation,
940 reason = "enumeration member count is written into the 2-byte member-count field of the datatype message"
941 )]
942 let num = members.len() as u16;
943 let bf0 = (num & 0xFF) as u8;
944 let bf1 = ((num >> 8) & 0xFF) as u8;
945 let mut buf = Self::build_header(8, 3, [bf0, bf1, 0], *size);
946 buf.extend_from_slice(&base_type.serialize());
948 for m in members {
950 buf.extend_from_slice(m.name.as_bytes());
951 buf.push(0);
952 }
953 for m in members {
955 buf.extend_from_slice(&m.value);
956 }
957 buf
958 }
959 Datatype::Array {
960 base_type,
961 dimensions,
962 } => {
963 let mut buf = Self::build_header(10, 3, [0, 0, 0], self.type_size());
964 #[expect(
965 clippy::cast_possible_truncation,
966 reason = "array rank is written into the 1-byte dimensionality field; HDF5 caps array rank well below 255"
967 )]
968 buf.push(dimensions.len() as u8);
969 for &d in dimensions {
970 buf.extend_from_slice(&d.to_le_bytes());
971 }
972 buf.extend_from_slice(&base_type.serialize());
973 buf
974 }
975 Datatype::Reference { size, ref_type } => {
976 let bf0 = match ref_type {
977 ReferenceType::Object => 0,
978 ReferenceType::DatasetRegion => 1,
979 };
980 Self::build_header(7, 1, [bf0, 0, 0], *size)
981 }
982 Datatype::Time {
983 size,
984 byte_order,
985 bit_precision,
986 } => {
987 let bf0 = if matches!(byte_order, DatatypeByteOrder::BigEndian) {
989 0x01u8
990 } else {
991 0
992 };
993 let mut buf = Self::build_header(2, 1, [bf0, 0, 0], *size);
994 buf.extend_from_slice(&bit_precision.to_le_bytes());
995 buf
996 }
997 Datatype::BitField {
998 size,
999 byte_order,
1000 bit_offset,
1001 bit_precision,
1002 } => {
1003 let bf0 = if matches!(byte_order, DatatypeByteOrder::BigEndian) {
1004 0x01u8
1005 } else {
1006 0
1007 };
1008 let mut buf = Self::build_header(4, 1, [bf0, 0, 0], *size);
1009 buf.extend_from_slice(&bit_offset.to_le_bytes());
1010 buf.extend_from_slice(&bit_precision.to_le_bytes());
1011 buf
1012 }
1013 Datatype::Opaque { size, tag } => {
1014 #[expect(
1017 clippy::cast_possible_truncation,
1018 reason = "opaque tag length is written into the 1-byte tag-length bit field (bf0)"
1019 )]
1020 let bf0 = tag.len() as u8;
1021 let mut buf = Self::build_header(5, 1, [bf0, 0, 0], *size);
1022 buf.extend_from_slice(tag);
1023 let padded = (tag.len() + 7) & !7;
1024 buf.resize(buf.len() + (padded - tag.len()), 0);
1025 buf
1026 }
1027 }
1028 }
1029
1030 fn build_header(class: u8, version: u8, bf: [u8; 3], size: u32) -> Vec<u8> {
1031 let mut buf = vec![0u8; 8];
1032 buf[0] = (class & 0x0F) | ((version & 0x0F) << 4);
1033 buf[1] = bf[0];
1034 buf[2] = bf[1];
1035 buf[3] = bf[2];
1036 buf[4..8].copy_from_slice(&size.to_le_bytes());
1037 buf
1038 }
1039
1040 pub fn type_size(&self) -> u32 {
1042 match self {
1043 Datatype::FixedPoint { size, .. } => *size,
1044 Datatype::FloatingPoint { size, .. } => *size,
1045 Datatype::Time { size, .. } => *size,
1046 Datatype::String { size, .. } => *size,
1047 Datatype::BitField { size, .. } => *size,
1048 Datatype::Opaque { size, .. } => *size,
1049 Datatype::Compound { size, .. } => *size,
1050 Datatype::Reference { size, .. } => *size,
1051 Datatype::Enumeration { size, .. } => *size,
1052 Datatype::VariableLength { .. } => 16, Datatype::Array {
1054 base_type,
1055 dimensions,
1056 } => {
1057 let elem_count: u32 = dimensions
1058 .iter()
1059 .copied()
1060 .fold(1u32, |a, b| a.saturating_mul(b));
1061 base_type.type_size().saturating_mul(elem_count)
1062 }
1063 }
1064 }
1065
1066 pub(crate) fn class_code(&self) -> u8 {
1073 match self {
1074 Datatype::FixedPoint { .. } => 0,
1075 Datatype::FloatingPoint { .. } => 1,
1076 Datatype::Time { .. } => 2,
1077 Datatype::String { .. } => 3,
1078 Datatype::BitField { .. } => 4,
1079 Datatype::Opaque { .. } => 5,
1080 Datatype::Compound { .. } => 6,
1081 Datatype::Reference { .. } => 7,
1082 Datatype::Enumeration { .. } => 8,
1083 Datatype::VariableLength { .. } => 9,
1084 Datatype::Array { .. } => 10,
1085 }
1086 }
1087
1088 pub fn element_size(&self) -> Result<NonZeroU32, FormatError> {
1109 NonZeroU32::new(self.type_size()).ok_or(FormatError::ZeroSizedDatatype {
1110 class: self.class_code(),
1111 })
1112 }
1113
1114 pub(crate) fn element_size_usize(&self) -> Result<NonZeroUsize, FormatError> {
1127 crate::convert::nonzero_usize_from(self.element_size()?)
1128 }
1129}
1130pub(crate) fn class_may_hold_object_address(class_and_version: u8) -> bool {
1142 matches!(
1143 class_and_version & 0x0F,
1144 COMPOUND_CLASS | REFERENCE_CLASS | ENUMERATION_CLASS | VARIABLE_LENGTH_CLASS | ARRAY_CLASS
1145 )
1146}
1147
1148const COMPOUND_CLASS: u8 = 6;
1151const REFERENCE_CLASS: u8 = 7;
1152const ENUMERATION_CLASS: u8 = 8;
1153const VARIABLE_LENGTH_CLASS: u8 = 9;
1154const ARRAY_CLASS: u8 = 10;
1155
1156pub(crate) fn datatype_holds_object_address(dt: &Datatype) -> bool {
1176 match dt {
1177 Datatype::Reference { .. } => true,
1183 Datatype::Compound { members, .. } => members
1184 .iter()
1185 .any(|m| datatype_holds_object_address(&m.datatype)),
1186 Datatype::Array { base_type, .. }
1187 | Datatype::Enumeration { base_type, .. }
1188 | Datatype::VariableLength { base_type, .. } => datatype_holds_object_address(base_type),
1189 _ => false,
1190 }
1191}
1192
1193pub(crate) fn datatype_holds_file_address(dt: &Datatype) -> bool {
1217 match dt {
1218 Datatype::VariableLength { .. } | Datatype::Reference { .. } => true,
1219 Datatype::Compound { members, .. } => members
1220 .iter()
1221 .any(|m| datatype_holds_file_address(&m.datatype)),
1222 Datatype::Array { base_type, .. } | Datatype::Enumeration { base_type, .. } => {
1223 datatype_holds_file_address(base_type)
1224 }
1225 _ => false,
1226 }
1227}
1228
1229pub(crate) fn embedded_reference_slots(datatype: &Datatype) -> Option<Vec<usize>> {
1244 fn collect(datatype: &Datatype, base: usize, capacity: usize, out: &mut Vec<usize>) -> bool {
1247 if out.len() > capacity {
1248 return true;
1249 }
1250 match datatype {
1251 Datatype::Reference {
1252 ref_type: ReferenceType::Object,
1253 size: 8,
1254 } => {
1255 out.push(base);
1256 true
1257 }
1258 Datatype::Compound { members, .. } => {
1259 for m in members {
1260 let Some(at) = usize::try_from(m.byte_offset)
1261 .ok()
1262 .and_then(|off| base.checked_add(off))
1263 else {
1264 return false;
1265 };
1266 if !collect(&m.datatype, at, capacity, out) {
1267 return false;
1268 }
1269 }
1270 true
1271 }
1272 Datatype::Array {
1273 base_type,
1274 dimensions,
1275 } => {
1276 let mut probe = Vec::new();
1283 if !collect(base_type, 0, capacity, &mut probe) {
1284 return false;
1285 }
1286 if probe.is_empty() {
1287 return true;
1288 }
1289 let count = dimensions
1290 .iter()
1291 .copied()
1292 .fold(1u64, |a, b| a.saturating_mul(u64::from(b)));
1293 if count > capacity as u64 {
1296 return false;
1297 }
1298 let entries = usize::try_from(count).unwrap_or(usize::MAX);
1299 let stride = base_type.type_size() as usize;
1300 for i in 0..entries {
1301 let Some(at) = i.checked_mul(stride).and_then(|off| base.checked_add(off))
1302 else {
1303 return false;
1304 };
1305 for &slot in &probe {
1306 let Some(off) = at.checked_add(slot) else {
1307 return false;
1308 };
1309 out.push(off);
1310 if out.len() > capacity {
1311 return true;
1312 }
1313 }
1314 }
1315 true
1316 }
1317 _ => !datatype_holds_object_address(datatype),
1325 }
1326 }
1327
1328 let element_size = datatype.type_size() as usize;
1329 let capacity = element_size / 8;
1330 let mut slots = Vec::new();
1331 if !collect(datatype, 0, capacity, &mut slots) {
1332 return None;
1333 }
1334 if slots.len() > capacity
1337 || slots
1338 .iter()
1339 .any(|&s| s.checked_add(8).is_none_or(|end| end > element_size))
1340 {
1341 return None;
1342 }
1343 Some(slots)
1344}
1345
1346pub(crate) fn stored_object_references<'a>(
1360 raw: &'a [u8],
1361 element_size: usize,
1362 slots: &'a [usize],
1363) -> impl Iterator<Item = (usize, u64)> + 'a {
1364 raw.chunks_exact(element_size.max(1))
1365 .enumerate()
1366 .flat_map(move |(i, element)| {
1367 slots.iter().map(move |&at| {
1368 let stored = u64::from_le_bytes(element[at..at + 8].try_into().expect(
1369 "embedded_reference_slots keeps every slot 8 bytes inside the element",
1370 ));
1371 (i * element_size + at, stored)
1372 })
1373 })
1374}
1375
1376#[cfg(test)]
1378fn build_dt_header(class: u8, version: u8, bf: [u8; 3], size: u32) -> Vec<u8> {
1379 let mut buf = vec![0u8; 8];
1380 buf[0] = (class & 0x0F) | ((version & 0x0F) << 4);
1381 buf[1] = bf[0];
1382 buf[2] = bf[1];
1383 buf[3] = bf[2];
1384 LittleEndian::write_u32(&mut buf[4..8], size);
1385 buf
1386}
1387
1388#[cfg(test)]
1389mod tests {
1390
1391 #[test]
1403 fn the_class_gate_admits_every_reference_holding_datatype() {
1404 let object_ref = || Datatype::Reference {
1405 size: 8,
1406 ref_type: ReferenceType::Object,
1407 };
1408 let i32_le = || Datatype::FixedPoint {
1409 size: 4,
1410 byte_order: DatatypeByteOrder::LittleEndian,
1411 signed: true,
1412 bit_offset: 0,
1413 bit_precision: 32,
1414 };
1415 let holds_an_address = [
1416 ("a bare object reference", object_ref()),
1417 (
1418 "a dataset-region reference",
1419 Datatype::Reference {
1420 size: 12,
1421 ref_type: ReferenceType::DatasetRegion,
1422 },
1423 ),
1424 (
1425 "a compound holding one",
1426 Datatype::Compound {
1427 size: 12,
1428 members: vec![
1429 CompoundMember {
1430 name: "r".into(),
1431 byte_offset: 0,
1432 datatype: object_ref(),
1433 },
1434 CompoundMember {
1435 name: "i".into(),
1436 byte_offset: 8,
1437 datatype: i32_le(),
1438 },
1439 ],
1440 },
1441 ),
1442 (
1443 "an array of them",
1444 Datatype::Array {
1445 base_type: Box::new(object_ref()),
1446 dimensions: vec![2],
1447 },
1448 ),
1449 (
1450 "a variable length of them",
1451 Datatype::VariableLength {
1452 is_string: false,
1453 padding: None,
1454 charset: None,
1455 base_type: Box::new(object_ref()),
1456 },
1457 ),
1458 (
1459 "an enumeration over one",
1460 Datatype::Enumeration {
1461 size: 8,
1462 base_type: Box::new(object_ref()),
1463 members: vec![EnumMember {
1464 name: "a".into(),
1465 value: vec![0; 8],
1466 }],
1467 },
1468 ),
1469 (
1470 "one nested two deep",
1471 Datatype::Array {
1472 base_type: Box::new(Datatype::Compound {
1473 size: 8,
1474 members: vec![CompoundMember {
1475 name: "r".into(),
1476 byte_offset: 0,
1477 datatype: object_ref(),
1478 }],
1479 }),
1480 dimensions: vec![3],
1481 },
1482 ),
1483 ];
1484 for (what, dt) in holds_an_address {
1485 assert!(
1486 datatype_holds_object_address(&dt),
1487 "{what} holds an object address"
1488 );
1489 let encoded = dt.serialize();
1490 assert!(
1491 class_may_hold_object_address(encoded[0]),
1492 "{what} encodes as class {}, which the gate rejects — a walk would \
1493 never parse it and would pass over the address inside it",
1494 encoded[0] & 0x0F
1495 );
1496 }
1497 }
1498
1499 #[test]
1503 fn the_class_gate_is_necessary_and_not_sufficient() {
1504 let ints = Datatype::Compound {
1505 size: 8,
1506 members: vec![CompoundMember {
1507 name: "a".into(),
1508 byte_offset: 0,
1509 datatype: Datatype::FixedPoint {
1510 size: 8,
1511 byte_order: DatatypeByteOrder::LittleEndian,
1512 signed: true,
1513 bit_offset: 0,
1514 bit_precision: 64,
1515 },
1516 }],
1517 };
1518 assert!(!datatype_holds_object_address(&ints));
1519 assert!(
1520 class_may_hold_object_address(ints.serialize()[0]),
1521 "a compound of integers is admitted by the class gate and holds no address"
1522 );
1523 }
1524
1525 use super::*;
1526
1527 fn build_fixed_point(
1529 size: u32,
1530 be: bool,
1531 signed: bool,
1532 bit_offset: u16,
1533 bit_precision: u16,
1534 ) -> Vec<u8> {
1535 let bf0 = if be { 0x01 } else { 0x00 } | if signed { 0x08 } else { 0x00 };
1536 let mut buf = build_dt_header(0, 1, [bf0, 0, 0], size);
1537 let mut props = [0u8; 4];
1538 LittleEndian::write_u16(&mut props[0..2], bit_offset);
1539 LittleEndian::write_u16(&mut props[2..4], bit_precision);
1540 buf.extend_from_slice(&props);
1541 buf
1542 }
1543
1544 fn build_float(
1546 size: u32,
1547 exp_loc: u8,
1548 exp_size: u8,
1549 mant_loc: u8,
1550 mant_size: u8,
1551 exp_bias: u32,
1552 ) -> Vec<u8> {
1553 let bf0 = 0x00u8;
1555 let bf1 = 0x00u8;
1556 let bf2 = 0x02u8; let mut buf = build_dt_header(1, 1, [bf0, bf1, bf2], size);
1559 let mut props = [0u8; 12];
1560 LittleEndian::write_u16(&mut props[0..2], 0); LittleEndian::write_u16(&mut props[2..4], (size * 8) as u16); props[4] = exp_loc;
1563 props[5] = exp_size;
1564 props[6] = mant_loc;
1565 props[7] = mant_size;
1566 LittleEndian::write_u32(&mut props[8..12], exp_bias);
1567 buf.extend_from_slice(&props);
1568 buf
1569 }
1570
1571 #[test]
1572 fn test_fixed_point_u8() {
1573 let data = build_fixed_point(1, false, false, 0, 8);
1574 let (dt, consumed) = Datatype::parse(&data).unwrap();
1575 assert_eq!(consumed, 12);
1576 assert_eq!(
1577 dt,
1578 Datatype::FixedPoint {
1579 size: 1,
1580 byte_order: DatatypeByteOrder::LittleEndian,
1581 signed: false,
1582 bit_offset: 0,
1583 bit_precision: 8,
1584 }
1585 );
1586 }
1587
1588 #[test]
1589 fn test_fixed_point_i16_le() {
1590 let data = build_fixed_point(2, false, true, 0, 16);
1591 let (dt, _) = Datatype::parse(&data).unwrap();
1592 assert_eq!(
1593 dt,
1594 Datatype::FixedPoint {
1595 size: 2,
1596 byte_order: DatatypeByteOrder::LittleEndian,
1597 signed: true,
1598 bit_offset: 0,
1599 bit_precision: 16,
1600 }
1601 );
1602 }
1603
1604 #[test]
1605 fn test_fixed_point_u32_be() {
1606 let data = build_fixed_point(4, true, false, 0, 32);
1607 let (dt, _) = Datatype::parse(&data).unwrap();
1608 match &dt {
1609 Datatype::FixedPoint {
1610 byte_order,
1611 signed,
1612 size,
1613 ..
1614 } => {
1615 assert_eq!(*byte_order, DatatypeByteOrder::BigEndian);
1616 assert!(!signed);
1617 assert_eq!(*size, 4);
1618 }
1619 _ => panic!("expected FixedPoint"),
1620 }
1621 }
1622
1623 #[test]
1624 fn test_fixed_point_i64_le() {
1625 let data = build_fixed_point(8, false, true, 0, 64);
1626 let (dt, _) = Datatype::parse(&data).unwrap();
1627 assert_eq!(
1628 dt,
1629 Datatype::FixedPoint {
1630 size: 8,
1631 byte_order: DatatypeByteOrder::LittleEndian,
1632 signed: true,
1633 bit_offset: 0,
1634 bit_precision: 64,
1635 }
1636 );
1637 }
1638
1639 #[test]
1640 fn test_float_f32_le() {
1641 let data = build_float(4, 23, 8, 0, 23, 127);
1643 let (dt, consumed) = Datatype::parse(&data).unwrap();
1644 assert_eq!(consumed, 20);
1645 assert_eq!(
1646 dt,
1647 Datatype::FloatingPoint {
1648 size: 4,
1649 byte_order: DatatypeByteOrder::LittleEndian,
1650 bit_offset: 0,
1651 bit_precision: 32,
1652 exponent_location: 23,
1653 exponent_size: 8,
1654 mantissa_location: 0,
1655 mantissa_size: 23,
1656 exponent_bias: 127,
1657 }
1658 );
1659 }
1660
1661 #[test]
1662 fn test_float_f64_le() {
1663 let data = build_float(8, 52, 11, 0, 52, 1023);
1664 let (dt, _) = Datatype::parse(&data).unwrap();
1665 assert_eq!(
1666 dt,
1667 Datatype::FloatingPoint {
1668 size: 8,
1669 byte_order: DatatypeByteOrder::LittleEndian,
1670 bit_offset: 0,
1671 bit_precision: 64,
1672 exponent_location: 52,
1673 exponent_size: 11,
1674 mantissa_location: 0,
1675 mantissa_size: 52,
1676 exponent_bias: 1023,
1677 }
1678 );
1679 }
1680
1681 #[test]
1682 fn test_string_null_terminated_ascii() {
1683 let buf = build_dt_header(3, 1, [0x00, 0, 0], 10); let (dt, consumed) = Datatype::parse(&buf).unwrap();
1685 assert_eq!(consumed, 8);
1686 assert_eq!(
1687 dt,
1688 Datatype::String {
1689 size: 10,
1690 padding: StringPadding::NullTerminate,
1691 charset: CharacterSet::Ascii,
1692 }
1693 );
1694 }
1695
1696 #[test]
1697 fn test_string_space_padded_utf8() {
1698 let buf = build_dt_header(3, 1, [0x12, 0, 0], 32);
1700 let (dt, _) = Datatype::parse(&buf).unwrap();
1701 assert_eq!(
1702 dt,
1703 Datatype::String {
1704 size: 32,
1705 padding: StringPadding::SpacePad,
1706 charset: CharacterSet::Utf8,
1707 }
1708 );
1709 }
1710
1711 #[test]
1712 fn test_opaque() {
1713 let mut buf = build_dt_header(5, 1, [4, 0, 0], 64);
1715 buf.extend_from_slice(b"BLOB");
1716 buf.extend_from_slice(&[0, 0, 0, 0]);
1718 let (dt, consumed) = Datatype::parse(&buf).unwrap();
1719 assert_eq!(consumed, 16); assert_eq!(
1721 dt,
1722 Datatype::Opaque {
1723 size: 64,
1724 tag: b"BLOB".to_vec(),
1725 }
1726 );
1727 }
1728
1729 #[test]
1730 fn test_compound_v3_two_members() {
1731 let mut buf = build_dt_header(6, 3, [2, 0, 0], 12); buf.extend_from_slice(b"x\0");
1736 buf.push(0); buf.extend_from_slice(&build_fixed_point(4, false, false, 0, 32));
1738 buf.extend_from_slice(b"y\0");
1740 buf.push(4); buf.extend_from_slice(&build_float(8, 52, 11, 0, 52, 1023));
1742
1743 let (dt, _) = Datatype::parse(&buf).unwrap();
1744 match dt {
1745 Datatype::Compound { size, members } => {
1746 assert_eq!(size, 12);
1747 assert_eq!(members.len(), 2);
1748 assert_eq!(members[0].name, "x");
1749 assert_eq!(members[0].byte_offset, 0);
1750 assert_eq!(members[1].name, "y");
1751 assert_eq!(members[1].byte_offset, 4);
1752 match &members[0].datatype {
1753 Datatype::FixedPoint {
1754 size: 4,
1755 signed: false,
1756 ..
1757 } => {}
1758 other => panic!("expected u32, got {other:?}"),
1759 }
1760 match &members[1].datatype {
1761 Datatype::FloatingPoint { size: 8, .. } => {}
1762 other => panic!("expected f64, got {other:?}"),
1763 }
1764 }
1765 _ => panic!("expected Compound"),
1766 }
1767 }
1768
1769 #[test]
1770 fn test_compound_v1_complex_matlab_layout() {
1771 let mut buf = build_dt_header(6, 1, [2, 0, 0], 16); for (name, offset) in [(&b"real\0\0\0\0"[..], 0u32), (&b"imag\0\0\0\0"[..], 8)] {
1781 buf.extend_from_slice(name); let mut off = [0u8; 4];
1783 LittleEndian::write_u32(&mut off, offset);
1784 buf.extend_from_slice(&off);
1785 buf.extend_from_slice(&[0u8; 28]); buf.extend_from_slice(&build_float(8, 52, 11, 0, 52, 1023));
1787 }
1788
1789 let (dt, _) = Datatype::parse(&buf).unwrap();
1790 match dt {
1791 Datatype::Compound { size, members } => {
1792 assert_eq!(size, 16);
1793 assert_eq!(members.len(), 2);
1794 assert_eq!(members[0].name, "real");
1795 assert_eq!(members[0].byte_offset, 0);
1796 assert_eq!(members[1].name, "imag");
1797 assert_eq!(members[1].byte_offset, 8);
1798 for m in &members {
1799 assert!(
1800 matches!(m.datatype, Datatype::FloatingPoint { size: 8, .. }),
1801 "expected f64 member, got {:?}",
1802 m.datatype
1803 );
1804 }
1805 }
1806 _ => panic!("expected Compound"),
1807 }
1808 }
1809
1810 #[test]
1811 fn test_reference_object() {
1812 let buf = build_dt_header(7, 1, [0, 0, 0], 8);
1813 let (dt, _) = Datatype::parse(&buf).unwrap();
1814 assert_eq!(
1815 dt,
1816 Datatype::Reference {
1817 size: 8,
1818 ref_type: ReferenceType::Object,
1819 }
1820 );
1821 }
1822
1823 #[test]
1824 fn test_reference_region() {
1825 let buf = build_dt_header(7, 1, [1, 0, 0], 12);
1826 let (dt, _) = Datatype::parse(&buf).unwrap();
1827 assert_eq!(
1828 dt,
1829 Datatype::Reference {
1830 size: 12,
1831 ref_type: ReferenceType::DatasetRegion,
1832 }
1833 );
1834 }
1835
1836 #[test]
1837 fn test_enumeration() {
1838 let mut buf = build_dt_header(8, 3, [3, 0, 0], 4); buf.extend_from_slice(&build_fixed_point(4, false, true, 0, 32));
1842 buf.extend_from_slice(b"RED\0");
1844 buf.extend_from_slice(b"GREEN\0");
1845 buf.extend_from_slice(b"BLUE\0");
1846 buf.extend_from_slice(&0i32.to_le_bytes());
1848 buf.extend_from_slice(&1i32.to_le_bytes());
1849 buf.extend_from_slice(&2i32.to_le_bytes());
1850
1851 let (dt, _) = Datatype::parse(&buf).unwrap();
1852 match dt {
1853 Datatype::Enumeration {
1854 size,
1855 base_type,
1856 members,
1857 } => {
1858 assert_eq!(size, 4);
1859 assert_eq!(members.len(), 3);
1860 assert_eq!(members[0].name, "RED");
1861 assert_eq!(members[0].value, 0i32.to_le_bytes().to_vec());
1862 assert_eq!(members[1].name, "GREEN");
1863 assert_eq!(members[1].value, 1i32.to_le_bytes().to_vec());
1864 assert_eq!(members[2].name, "BLUE");
1865 assert_eq!(members[2].value, 2i32.to_le_bytes().to_vec());
1866 match *base_type {
1867 Datatype::FixedPoint {
1868 signed: true,
1869 size: 4,
1870 ..
1871 } => {}
1872 other => panic!("expected i32, got {other:?}"),
1873 }
1874 }
1875 _ => panic!("expected Enumeration"),
1876 }
1877 }
1878
1879 #[test]
1880 fn test_variable_length_string_utf8() {
1881 let mut buf = build_dt_header(9, 1, [0x01, 0x01, 0], 16);
1885 buf.extend_from_slice(&build_fixed_point(1, false, false, 0, 8));
1887
1888 let (dt, _) = Datatype::parse(&buf).unwrap();
1889 match dt {
1890 Datatype::VariableLength {
1891 is_string,
1892 padding,
1893 charset,
1894 base_type,
1895 } => {
1896 assert!(is_string);
1897 assert_eq!(padding, Some(StringPadding::NullTerminate));
1898 assert_eq!(charset, Some(CharacterSet::Utf8));
1899 assert_eq!(base_type.type_size(), 1);
1900 }
1901 _ => panic!("expected VariableLength"),
1902 }
1903 }
1904
1905 #[test]
1906 fn test_variable_length_sequence_f32() {
1907 let mut buf = build_dt_header(9, 1, [0x00, 0x00, 0], 16);
1910 buf.extend_from_slice(&build_float(4, 23, 8, 0, 23, 127));
1912
1913 let (dt, _) = Datatype::parse(&buf).unwrap();
1914 match dt {
1915 Datatype::VariableLength {
1916 is_string,
1917 padding,
1918 charset,
1919 base_type,
1920 } => {
1921 assert!(!is_string);
1922 assert_eq!(padding, None);
1923 assert_eq!(charset, None);
1924 assert_eq!(base_type.type_size(), 4);
1925 }
1926 _ => panic!("expected VariableLength"),
1927 }
1928 }
1929
1930 #[test]
1934 fn a_version_5_compound_parses_like_version_3() {
1935 let members = |version: u8| {
1936 let mut buf = build_dt_header(6, version, [2, 0, 0], 16);
1937 buf.extend_from_slice(b"re\0");
1938 buf.push(0);
1939 buf.extend_from_slice(&build_float(8, 52, 11, 0, 52, 1023));
1940 buf.extend_from_slice(b"im\0");
1941 buf.push(8);
1942 buf.extend_from_slice(&build_float(8, 52, 11, 0, 52, 1023));
1943 buf
1944 };
1945 let (expected, expected_len) = Datatype::parse(&members(3)).unwrap();
1946 let (parsed, len) = Datatype::parse(&members(5)).unwrap();
1947 assert_eq!((parsed, len), (expected, expected_len));
1948 }
1949
1950 #[test]
1952 fn a_version_5_array_parses_like_version_3() {
1953 let array = |version: u8| {
1954 let mut buf = build_dt_header(10, version, [0, 0, 0], 48);
1955 buf.push(2);
1956 buf.extend_from_slice(&3u32.to_le_bytes());
1957 buf.extend_from_slice(&4u32.to_le_bytes());
1958 buf.extend_from_slice(&build_fixed_point(4, false, true, 0, 32));
1959 buf
1960 };
1961 let (expected, expected_len) = Datatype::parse(&array(3)).unwrap();
1962 let (parsed, len) = Datatype::parse(&array(5)).unwrap();
1963 assert_eq!((parsed, len), (expected, expected_len));
1964 }
1965
1966 #[test]
1967 fn test_array_2d() {
1968 let mut buf = build_dt_header(10, 3, [0, 0, 0], 48); buf.push(2); buf.extend_from_slice(&3u32.to_le_bytes()); buf.extend_from_slice(&4u32.to_le_bytes()); buf.extend_from_slice(&build_fixed_point(4, false, true, 0, 32));
1975
1976 let (dt, _) = Datatype::parse(&buf).unwrap();
1977 match dt {
1978 Datatype::Array {
1979 base_type,
1980 dimensions,
1981 } => {
1982 assert_eq!(dimensions, vec![3, 4]);
1983 match *base_type {
1984 Datatype::FixedPoint {
1985 size: 4,
1986 signed: true,
1987 ..
1988 } => {}
1989 other => panic!("expected i32, got {other:?}"),
1990 }
1991 }
1992 _ => panic!("expected Array"),
1993 }
1994 }
1995
1996 #[test]
2000 fn a_zero_width_element_type_is_refused() {
2001 let buf = build_dt_header(3, 1, [0x01, 0, 0], 0); assert_eq!(
2003 Datatype::parse(&buf).unwrap_err(),
2004 FormatError::ZeroSizedDatatype { class: 3 }
2005 );
2006 }
2007
2008 #[test]
2013 fn an_array_with_a_zero_dimension_is_refused_despite_its_header_size() {
2014 let mut buf = build_dt_header(10, 3, [0, 0, 0], 48);
2015 buf.push(2); buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&4u32.to_le_bytes()); buf.extend_from_slice(&build_fixed_point(4, false, true, 0, 32));
2019
2020 assert_eq!(
2021 Datatype::parse(&buf).unwrap_err(),
2022 FormatError::ZeroSizedDatatype { class: 10 }
2023 );
2024 }
2025
2026 #[test]
2030 fn a_zero_width_compound_member_is_refused() {
2031 let member = build_dt_header(3, 1, [0x01, 0, 0], 0);
2032 let mut buf = build_dt_header(6, 3, [1, 0, 0], 8); buf.extend_from_slice(b"s\0");
2034 buf.push(0); buf.extend_from_slice(&member);
2036
2037 assert_eq!(
2038 Datatype::parse(&buf).unwrap_err(),
2039 FormatError::ZeroSizedDatatype { class: 3 }
2040 );
2041 }
2042
2043 #[test]
2047 fn element_size_matches_type_size_for_a_type_that_has_one() {
2048 let dt = Datatype::FixedPoint {
2049 size: 4,
2050 byte_order: DatatypeByteOrder::LittleEndian,
2051 signed: true,
2052 bit_offset: 0,
2053 bit_precision: 32,
2054 };
2055 assert_eq!(dt.element_size().unwrap().get(), dt.type_size());
2056 }
2057
2058 #[test]
2063 fn element_size_refuses_a_constructed_array_with_a_zero_dimension() {
2064 let dt = Datatype::Array {
2065 base_type: Box::new(Datatype::FixedPoint {
2066 size: 4,
2067 byte_order: DatatypeByteOrder::LittleEndian,
2068 signed: true,
2069 bit_offset: 0,
2070 bit_precision: 32,
2071 }),
2072 dimensions: vec![0, 4],
2073 };
2074 assert_eq!(dt.type_size(), 0);
2075 assert_eq!(
2076 dt.element_size().unwrap_err(),
2077 FormatError::ZeroSizedDatatype { class: 10 }
2078 );
2079 assert_eq!(
2080 dt.element_size_usize().unwrap_err(),
2081 FormatError::ZeroSizedDatatype { class: 10 }
2082 );
2083 }
2084
2085 #[test]
2088 fn element_size_reports_the_refused_types_own_class() {
2089 let dt = Datatype::Compound {
2090 size: 0,
2091 members: vec![],
2092 };
2093 assert_eq!(
2094 dt.element_size().unwrap_err(),
2095 FormatError::ZeroSizedDatatype { class: 6 }
2096 );
2097 }
2098
2099 #[test]
2100 fn test_bitfield() {
2101 let mut buf = build_dt_header(4, 1, [0, 0, 0], 2); let mut props = [0u8; 4];
2103 LittleEndian::write_u16(&mut props[0..2], 0);
2104 LittleEndian::write_u16(&mut props[2..4], 16);
2105 buf.extend_from_slice(&props);
2106
2107 let (dt, _) = Datatype::parse(&buf).unwrap();
2108 assert_eq!(
2109 dt,
2110 Datatype::BitField {
2111 size: 2,
2112 byte_order: DatatypeByteOrder::LittleEndian,
2113 bit_offset: 0,
2114 bit_precision: 16,
2115 }
2116 );
2117 }
2118
2119 #[test]
2120 fn test_time() {
2121 let mut buf = build_dt_header(2, 1, [0, 0, 0], 8);
2122 let mut props = [0u8; 2];
2123 LittleEndian::write_u16(&mut props[0..2], 64);
2124 buf.extend_from_slice(&props);
2125
2126 let (dt, consumed) = Datatype::parse(&buf).unwrap();
2127 assert_eq!(consumed, 10);
2128 assert_eq!(
2129 dt,
2130 Datatype::Time {
2131 size: 8,
2132 byte_order: DatatypeByteOrder::LittleEndian,
2133 bit_precision: 64,
2134 }
2135 );
2136 }
2137
2138 #[test]
2139 fn test_time_byte_order_roundtrips() {
2140 for (be, order) in [
2143 (0u8, DatatypeByteOrder::LittleEndian),
2144 (1u8, DatatypeByteOrder::BigEndian),
2145 ] {
2146 let mut buf = build_dt_header(2, 1, [be, 0, 0], 4);
2147 buf.extend_from_slice(&32u16.to_le_bytes());
2148 let (dt, _) = Datatype::parse(&buf).unwrap();
2149 assert_eq!(
2150 dt,
2151 Datatype::Time {
2152 size: 4,
2153 byte_order: order.clone(),
2154 bit_precision: 32,
2155 }
2156 );
2157 let (reparsed, _) = Datatype::parse(&dt.serialize()).unwrap();
2159 assert_eq!(reparsed, dt);
2160 }
2161 }
2162
2163 #[test]
2164 fn test_nested_compound_array_enum() {
2165 let mut enum_bytes = build_dt_header(8, 3, [2, 0, 0], 4); enum_bytes.extend_from_slice(&build_fixed_point(4, false, true, 0, 32)); enum_bytes.extend_from_slice(b"A\0");
2170 enum_bytes.extend_from_slice(b"B\0");
2171 enum_bytes.extend_from_slice(&0i32.to_le_bytes());
2172 enum_bytes.extend_from_slice(&1i32.to_le_bytes());
2173
2174 let mut array_bytes = build_dt_header(10, 3, [0, 0, 0], 8); array_bytes.push(1); array_bytes.extend_from_slice(&2u32.to_le_bytes()); array_bytes.extend_from_slice(&enum_bytes);
2179
2180 let mut buf = build_dt_header(6, 3, [1, 0, 0], 8); buf.extend_from_slice(b"data\0");
2183 buf.push(0); buf.extend_from_slice(&array_bytes);
2185
2186 let (dt, _) = Datatype::parse(&buf).unwrap();
2187 match dt {
2188 Datatype::Compound { members, .. } => {
2189 assert_eq!(members.len(), 1);
2190 assert_eq!(members[0].name, "data");
2191 match &members[0].datatype {
2192 Datatype::Array {
2193 dimensions,
2194 base_type,
2195 } => {
2196 assert_eq!(dimensions, &[2]);
2197 match base_type.as_ref() {
2198 Datatype::Enumeration { members, .. } => {
2199 assert_eq!(members.len(), 2);
2200 assert_eq!(members[0].name, "A");
2201 assert_eq!(members[1].name, "B");
2202 }
2203 other => panic!("expected Enum, got {other:?}"),
2204 }
2205 }
2206 other => panic!("expected Array, got {other:?}"),
2207 }
2208 }
2209 _ => panic!("expected Compound"),
2210 }
2211 }
2212
2213 #[test]
2214 fn test_error_invalid_class() {
2215 let buf = build_dt_header(13, 1, [0, 0, 0], 4);
2216 let err = Datatype::parse(&buf).unwrap_err();
2217 assert_eq!(err, FormatError::InvalidDatatypeClass(13));
2218 }
2219
2220 #[test]
2221 fn test_error_truncated_data() {
2222 let buf = [0u8; 4]; let err = Datatype::parse(&buf).unwrap_err();
2224 match err {
2225 FormatError::UnexpectedEof { .. } => {}
2226 other => panic!("expected UnexpectedEof, got {other:?}"),
2227 }
2228 }
2229
2230 #[test]
2231 fn test_error_invalid_string_padding() {
2232 let buf = build_dt_header(3, 1, [0x03, 0, 0], 10); let err = Datatype::parse(&buf).unwrap_err();
2234 assert_eq!(err, FormatError::InvalidStringPadding(3));
2235 }
2236
2237 #[test]
2238 fn test_error_invalid_charset() {
2239 let buf = build_dt_header(3, 1, [0x20, 0, 0], 10); let err = Datatype::parse(&buf).unwrap_err();
2241 assert_eq!(err, FormatError::InvalidCharacterSet(2));
2242 }
2243
2244 #[test]
2245 fn test_error_invalid_reference_type() {
2246 let buf = build_dt_header(7, 1, [5, 0, 0], 8);
2247 let err = Datatype::parse(&buf).unwrap_err();
2248 assert_eq!(err, FormatError::InvalidReferenceType(5));
2249 }
2250
2251 #[test]
2252 fn serialize_parse_compound_roundtrip() {
2253 let dt = Datatype::Compound {
2254 size: 20,
2255 members: vec![
2256 CompoundMember {
2257 name: "x".to_string(),
2258 byte_offset: 0,
2259 datatype: Datatype::FloatingPoint {
2260 size: 8,
2261 byte_order: DatatypeByteOrder::LittleEndian,
2262 bit_offset: 0,
2263 bit_precision: 64,
2264 exponent_location: 52,
2265 exponent_size: 11,
2266 mantissa_location: 0,
2267 mantissa_size: 52,
2268 exponent_bias: 1023,
2269 },
2270 },
2271 CompoundMember {
2272 name: "y".to_string(),
2273 byte_offset: 8,
2274 datatype: Datatype::FloatingPoint {
2275 size: 8,
2276 byte_order: DatatypeByteOrder::LittleEndian,
2277 bit_offset: 0,
2278 bit_precision: 64,
2279 exponent_location: 52,
2280 exponent_size: 11,
2281 mantissa_location: 0,
2282 mantissa_size: 52,
2283 exponent_bias: 1023,
2284 },
2285 },
2286 CompoundMember {
2287 name: "id".to_string(),
2288 byte_offset: 16,
2289 datatype: Datatype::FixedPoint {
2290 size: 4,
2291 byte_order: DatatypeByteOrder::LittleEndian,
2292 signed: true,
2293 bit_offset: 0,
2294 bit_precision: 32,
2295 },
2296 },
2297 ],
2298 };
2299 let bytes = dt.serialize();
2300 let (parsed, _) = Datatype::parse(&bytes).unwrap();
2301 assert_eq!(parsed, dt);
2302 }
2303
2304 #[test]
2305 fn serialize_parse_enum_roundtrip() {
2306 let dt = Datatype::Enumeration {
2307 size: 4,
2308 base_type: Box::new(Datatype::FixedPoint {
2309 size: 4,
2310 byte_order: DatatypeByteOrder::LittleEndian,
2311 signed: true,
2312 bit_offset: 0,
2313 bit_precision: 32,
2314 }),
2315 members: vec![
2316 EnumMember {
2317 name: "RED".to_string(),
2318 value: 0i32.to_le_bytes().to_vec(),
2319 },
2320 EnumMember {
2321 name: "GREEN".to_string(),
2322 value: 1i32.to_le_bytes().to_vec(),
2323 },
2324 EnumMember {
2325 name: "BLUE".to_string(),
2326 value: 2i32.to_le_bytes().to_vec(),
2327 },
2328 ],
2329 };
2330 let bytes = dt.serialize();
2331 let (parsed, _) = Datatype::parse(&bytes).unwrap();
2332 assert_eq!(parsed, dt);
2333 }
2334
2335 fn enum_base_fp(size: u32, be: bool, signed: bool) -> Datatype {
2337 Datatype::FixedPoint {
2338 size,
2339 byte_order: if be {
2340 DatatypeByteOrder::BigEndian
2341 } else {
2342 DatatypeByteOrder::LittleEndian
2343 },
2344 signed,
2345 bit_offset: 0,
2346 #[expect(
2347 clippy::cast_possible_truncation,
2348 reason = "test builds byte-width base types; size*8 is well within u16"
2349 )]
2350 bit_precision: (size * 8) as u16,
2351 }
2352 }
2353
2354 fn make_enum(base: Datatype, members: &[(&str, i64)]) -> Datatype {
2357 let size = base.type_size();
2358 let width = size as usize;
2359 Datatype::Enumeration {
2360 size,
2361 base_type: Box::new(base),
2362 members: members
2363 .iter()
2364 .map(|(name, v)| EnumMember {
2365 name: (*name).to_string(),
2366 value: v.to_le_bytes()[..width].to_vec(),
2367 })
2368 .collect(),
2369 }
2370 }
2371
2372 #[test]
2373 fn serialize_parse_enum_base_type_variety() {
2374 for base in [
2377 enum_base_fp(1, false, false), enum_base_fp(2, true, true), enum_base_fp(8, false, true), ] {
2381 let dt = make_enum(base.clone(), &[("A", 0), ("B", 1), ("NEG", -1)]);
2382 let bytes = dt.serialize();
2383 let (parsed, consumed) = Datatype::parse(&bytes).unwrap();
2384 assert_eq!(parsed, dt, "round-trip failed for base {base:?}");
2385 assert_eq!(consumed, bytes.len());
2386 }
2387 }
2388
2389 #[test]
2390 fn serialize_parse_enum_large_member_count() {
2391 let owned: Vec<(String, i64)> = (0..300).map(|i| (format!("M{i}"), i)).collect();
2394 let members: Vec<(&str, i64)> = owned.iter().map(|(n, v)| (n.as_str(), *v)).collect();
2395 let dt = make_enum(enum_base_fp(4, false, true), &members);
2396 let bytes = dt.serialize();
2397 let (parsed, _) = Datatype::parse(&bytes).unwrap();
2398 assert_eq!(parsed, dt);
2399 match parsed {
2400 Datatype::Enumeration { members, .. } => {
2401 assert_eq!(members.len(), 300);
2402 assert_eq!(members[299].name, "M299");
2403 }
2404 other => panic!("expected Enumeration, got {other:?}"),
2405 }
2406 }
2407
2408 #[test]
2409 fn enum_value_width_is_not_validated_against_base_size() {
2410 let dt = Datatype::Enumeration {
2417 size: 1,
2418 base_type: Box::new(enum_base_fp(1, false, false)),
2419 members: vec![EnumMember {
2420 name: "X".to_string(),
2421 value: 5i32.to_le_bytes().to_vec(), }],
2423 };
2424 let bytes = dt.serialize();
2425 let (parsed, _) = Datatype::parse(&bytes).unwrap();
2426 assert_ne!(
2427 parsed, dt,
2428 "a value wider than the base silently truncates on parse"
2429 );
2430 match parsed {
2431 Datatype::Enumeration { members, .. } => assert_eq!(members[0].value, vec![5]),
2432 other => panic!("expected Enumeration, got {other:?}"),
2433 }
2434 }
2435
2436 #[test]
2437 fn serialize_parse_array_roundtrip() {
2438 let dt = Datatype::Array {
2439 base_type: Box::new(Datatype::FloatingPoint {
2440 size: 8,
2441 byte_order: DatatypeByteOrder::LittleEndian,
2442 bit_offset: 0,
2443 bit_precision: 64,
2444 exponent_location: 52,
2445 exponent_size: 11,
2446 mantissa_location: 0,
2447 mantissa_size: 52,
2448 exponent_bias: 1023,
2449 }),
2450 dimensions: vec![3],
2451 };
2452 let bytes = dt.serialize();
2453 let (parsed, _) = Datatype::parse(&bytes).unwrap();
2454 assert_eq!(parsed, dt);
2455 }
2456
2457 #[test]
2458 fn serialize_parse_time_roundtrip() {
2459 let dt = Datatype::Time {
2460 size: 8,
2461 byte_order: DatatypeByteOrder::LittleEndian,
2462 bit_precision: 64,
2463 };
2464 let bytes = dt.serialize();
2465 let (parsed, consumed) = Datatype::parse(&bytes).unwrap();
2466 assert_eq!(parsed, dt);
2467 assert_eq!(consumed, bytes.len());
2468 }
2469
2470 #[test]
2471 fn serialize_parse_bitfield_roundtrip() {
2472 for byte_order in [
2473 DatatypeByteOrder::LittleEndian,
2474 DatatypeByteOrder::BigEndian,
2475 ] {
2476 let dt = Datatype::BitField {
2477 size: 4,
2478 byte_order,
2479 bit_offset: 3,
2480 bit_precision: 17,
2481 };
2482 let bytes = dt.serialize();
2483 let (parsed, consumed) = Datatype::parse(&bytes).unwrap();
2484 assert_eq!(parsed, dt);
2485 assert_eq!(consumed, bytes.len());
2486 }
2487 }
2488
2489 #[test]
2490 fn serialize_parse_opaque_roundtrip() {
2491 for tag in [
2494 b"abc".to_vec(), b"12345678".to_vec(), b"sensor-id\0".to_vec(), ] {
2498 let dt = Datatype::Opaque { size: 16, tag };
2499 let bytes = dt.serialize();
2500 assert_eq!((bytes.len() - 8) % 8, 0);
2503 let (parsed, consumed) = Datatype::parse(&bytes).unwrap();
2504 assert_eq!(parsed, dt);
2505 assert_eq!(consumed, bytes.len());
2506 }
2507 }
2508
2509 #[test]
2510 fn test_type_size() {
2511 let dt = Datatype::FixedPoint {
2512 size: 4,
2513 byte_order: DatatypeByteOrder::LittleEndian,
2514 signed: true,
2515 bit_offset: 0,
2516 bit_precision: 32,
2517 };
2518 assert_eq!(dt.type_size(), 4);
2519
2520 let dt = Datatype::Array {
2521 base_type: Box::new(Datatype::FixedPoint {
2522 size: 4,
2523 byte_order: DatatypeByteOrder::LittleEndian,
2524 signed: true,
2525 bit_offset: 0,
2526 bit_precision: 32,
2527 }),
2528 dimensions: vec![3, 4],
2529 };
2530 assert_eq!(dt.type_size(), 48);
2531 }
2532}
2533
2534#[cfg(all(test, feature = "std"))]
2535mod display_tests {
2536 use super::*;
2537
2538 #[test]
2539 fn ordinary_numeric_types_read_as_their_rust_names() {
2540 let int = Datatype::FixedPoint {
2541 size: 4,
2542 byte_order: DatatypeByteOrder::LittleEndian,
2543 signed: true,
2544 bit_offset: 0,
2545 bit_precision: 32,
2546 };
2547 assert_eq!(int.to_string(), "i32");
2548
2549 let float = Datatype::FloatingPoint {
2550 size: 8,
2551 byte_order: DatatypeByteOrder::LittleEndian,
2552 bit_offset: 0,
2553 bit_precision: 64,
2554 exponent_location: 52,
2555 exponent_size: 11,
2556 mantissa_location: 0,
2557 mantissa_size: 52,
2558 exponent_bias: 1023,
2559 };
2560 assert_eq!(float.to_string(), "f64");
2561 }
2562
2563 #[test]
2569 fn a_crafted_size_writes_its_width_instead_of_overflowing() {
2570 let bits = u64::from(u32::MAX) * 8;
2571 let cases = [
2572 (
2573 Datatype::FixedPoint {
2574 size: u32::MAX,
2575 byte_order: DatatypeByteOrder::LittleEndian,
2576 signed: true,
2577 bit_offset: 0,
2578 bit_precision: 0,
2579 },
2580 format!("i{bits}(bits 0..0)"),
2581 ),
2582 (
2583 Datatype::FloatingPoint {
2584 size: u32::MAX,
2585 byte_order: DatatypeByteOrder::LittleEndian,
2586 bit_offset: 0,
2587 bit_precision: 0,
2588 exponent_location: 0,
2589 exponent_size: 0,
2590 mantissa_location: 0,
2591 mantissa_size: 0,
2592 exponent_bias: 0,
2593 },
2594 format!("f{bits}(bits 0..0)"),
2595 ),
2596 (
2597 Datatype::Time {
2598 size: u32::MAX,
2599 byte_order: DatatypeByteOrder::LittleEndian,
2600 bit_precision: 0,
2601 },
2602 format!("time{bits}(bits 0..0)"),
2603 ),
2604 (
2605 Datatype::BitField {
2606 size: u32::MAX,
2607 byte_order: DatatypeByteOrder::LittleEndian,
2608 bit_offset: 0,
2609 bit_precision: 0,
2610 },
2611 format!("bitfield{bits}(bits 0..0)"),
2612 ),
2613 ];
2614
2615 for (dtype, expected) in cases {
2616 assert_eq!(dtype.to_string(), expected);
2617 }
2618 }
2619
2620 #[test]
2623 fn a_crafted_bit_span_does_not_wrap() {
2624 let dtype = Datatype::FixedPoint {
2625 size: 1,
2626 byte_order: DatatypeByteOrder::LittleEndian,
2627 signed: false,
2628 bit_offset: u16::MAX,
2629 bit_precision: u16::MAX,
2630 };
2631 assert_eq!(dtype.to_string(), "u8(bits 65535..131070)");
2632 }
2633
2634 #[test]
2637 fn unusual_fields_are_written_and_ordinary_ones_are_not() {
2638 let big_endian = Datatype::FixedPoint {
2639 size: 2,
2640 byte_order: DatatypeByteOrder::BigEndian,
2641 signed: false,
2642 bit_offset: 0,
2643 bit_precision: 16,
2644 };
2645 assert_eq!(big_endian.to_string(), "u16 be");
2646
2647 let narrow = Datatype::FixedPoint {
2648 size: 4,
2649 byte_order: DatatypeByteOrder::LittleEndian,
2650 signed: true,
2651 bit_offset: 0,
2652 bit_precision: 24,
2653 };
2654 assert_eq!(narrow.to_string(), "i32(bits 0..24)");
2655 }
2656
2657 #[test]
2658 fn nested_types_recurse_through_their_members() {
2659 let compound = Datatype::Compound {
2660 size: 12,
2661 members: vec![
2662 CompoundMember {
2663 name: "x".into(),
2664 byte_offset: 0,
2665 datatype: Datatype::FloatingPoint {
2666 size: 4,
2667 byte_order: DatatypeByteOrder::LittleEndian,
2668 bit_offset: 0,
2669 bit_precision: 32,
2670 exponent_location: 23,
2671 exponent_size: 8,
2672 mantissa_location: 0,
2673 mantissa_size: 23,
2674 exponent_bias: 127,
2675 },
2676 },
2677 CompoundMember {
2678 name: "n".into(),
2679 byte_offset: 4,
2680 datatype: Datatype::FixedPoint {
2681 size: 8,
2682 byte_order: DatatypeByteOrder::LittleEndian,
2683 signed: true,
2684 bit_offset: 0,
2685 bit_precision: 64,
2686 },
2687 },
2688 ],
2689 };
2690 assert_eq!(compound.to_string(), "compound{x: f32, n: i64}");
2691
2692 let array = Datatype::Array {
2693 base_type: Box::new(Datatype::FixedPoint {
2694 size: 1,
2695 byte_order: DatatypeByteOrder::LittleEndian,
2696 signed: false,
2697 bit_offset: 0,
2698 bit_precision: 8,
2699 }),
2700 dimensions: vec![2, 3],
2701 };
2702 assert_eq!(
2703 array.to_string(),
2704 "array<u8, 2x3>",
2705 "the shape is spelled `2x3`, never a `Debug` slice"
2706 );
2707 }
2708
2709 #[test]
2713 fn a_leaf_enum_honors_the_width_it_is_given() {
2714 assert_eq!(format!("{:>8}", CharacterSet::Ascii), " ascii");
2715 assert_eq!(format!("{:<8}|", DatatypeByteOrder::BigEndian), "be |");
2716 assert_eq!(format!("{}", StringPadding::NullPad), "null-pad");
2717 }
2718
2719 #[test]
2720 fn a_string_carries_its_width_charset_and_padding() {
2721 let string = Datatype::String {
2722 size: 16,
2723 padding: StringPadding::NullPad,
2724 charset: CharacterSet::Utf8,
2725 };
2726 assert_eq!(string.to_string(), "string[16] utf8 null-pad");
2727 }
2728
2729 #[test]
2731 fn an_opaque_tag_is_quoted_and_escaped() {
2732 let opaque = Datatype::Opaque {
2733 size: 4,
2734 tag: b"a\"b\x00".to_vec(),
2735 };
2736 assert_eq!(opaque.to_string(), "opaque[4] \"a\\\"b\\x00\"");
2737 }
2738
2739 #[test]
2742 fn a_member_name_cannot_carry_a_control_character_into_a_message() {
2743 let compound = Datatype::Compound {
2744 size: 4,
2745 members: vec![CompoundMember {
2746 name: "a\nb\u{1b}[31m".into(),
2747 byte_offset: 0,
2748 datatype: u32_datatype(),
2749 }],
2750 };
2751 let shown = compound.to_string();
2752 assert!(!shown.chars().any(char::is_control), "{shown}");
2753 assert_eq!(shown, "compound{a\\nb\\u{1b}[31m: u32}");
2754
2755 let enumeration = Datatype::Enumeration {
2756 size: 4,
2757 base_type: Box::new(u32_datatype()),
2758 members: vec![EnumMember {
2759 name: "red\u{0}".into(),
2760 value: vec![0, 0, 0, 0],
2761 }],
2762 };
2763 let shown = enumeration.to_string();
2764 assert!(!shown.chars().any(char::is_control), "{shown}");
2765 assert_eq!(shown, "enum<u32>[red\\0]");
2766 }
2767
2768 #[test]
2772 fn a_long_member_list_is_elided_and_reports_the_remainder() {
2773 let over_cap = DISPLAY_MAX_MEMBERS + 3;
2774
2775 let compound = Datatype::Compound {
2776 size: (over_cap * 4) as u32,
2777 members: (0..over_cap)
2778 .map(|i| CompoundMember {
2779 name: format!("m{i}"),
2780 byte_offset: (i * 4) as u64,
2781 datatype: u32_datatype(),
2782 })
2783 .collect(),
2784 };
2785 let enumeration = Datatype::Enumeration {
2786 size: 4,
2787 base_type: Box::new(u32_datatype()),
2788 members: (0..over_cap)
2789 .map(|i| EnumMember {
2790 name: format!("m{i}"),
2791 value: vec![0, 0, 0, 0],
2792 })
2793 .collect(),
2794 };
2795
2796 for (datatype, close) in [(compound, "}"), (enumeration, "]")] {
2797 let shown = datatype.to_string();
2798 assert!(shown.ends_with(&format!(", … 3 more{close}")), "{shown}");
2799 assert!(shown.contains("m0"), "{shown}");
2800 assert!(
2801 !shown.contains(&format!("m{DISPLAY_MAX_MEMBERS}")),
2802 "{shown}"
2803 );
2804 }
2805 }
2806
2807 #[test]
2809 fn a_member_list_at_exactly_the_cap_is_not_elided() {
2810 let members: Vec<_> = (0..DISPLAY_MAX_MEMBERS)
2811 .map(|i| EnumMember {
2812 name: format!("m{i}"),
2813 value: vec![0, 0, 0, 0],
2814 })
2815 .collect();
2816 let shown = Datatype::Enumeration {
2817 size: 4,
2818 base_type: Box::new(u32_datatype()),
2819 members,
2820 }
2821 .to_string();
2822
2823 assert!(!shown.contains('…'), "{shown}");
2824 assert!(
2825 shown.ends_with(&format!("m{}]", DISPLAY_MAX_MEMBERS - 1)),
2826 "{shown}"
2827 );
2828 }
2829
2830 fn u32_datatype() -> Datatype {
2831 Datatype::FixedPoint {
2832 size: 4,
2833 byte_order: DatatypeByteOrder::LittleEndian,
2834 signed: false,
2835 bit_offset: 0,
2836 bit_precision: 32,
2837 }
2838 }
2839}