1#[cfg(not(feature = "std"))]
7use alloc::{boxed::Box, string::String, vec, vec::Vec};
8
9use byteorder::{ByteOrder, LittleEndian};
10
11use crate::error::FormatError;
12
13#[derive(Debug, Clone, PartialEq)]
15pub enum DatatypeByteOrder {
16 LittleEndian,
17 BigEndian,
18 Vax,
19}
20
21#[derive(Debug, Clone, PartialEq)]
23pub enum StringPadding {
24 NullTerminate,
25 NullPad,
26 SpacePad,
27}
28
29#[derive(Debug, Clone, PartialEq)]
31pub enum CharacterSet {
32 Ascii,
33 Utf8,
34}
35
36#[derive(Debug, Clone, PartialEq)]
38pub enum ReferenceType {
39 Object,
40 DatasetRegion,
41}
42
43#[derive(Debug, Clone, PartialEq)]
45pub struct CompoundMember {
46 pub name: String,
48 pub byte_offset: u64,
50 pub datatype: Datatype,
52}
53
54#[derive(Debug, Clone, PartialEq)]
56pub struct EnumMember {
57 pub name: String,
59 pub value: Vec<u8>,
61}
62
63#[derive(Debug, Clone, PartialEq)]
65pub enum Datatype {
66 FixedPoint {
68 size: u32,
69 byte_order: DatatypeByteOrder,
70 signed: bool,
71 bit_offset: u16,
72 bit_precision: u16,
73 },
74 FloatingPoint {
76 size: u32,
77 byte_order: DatatypeByteOrder,
78 bit_offset: u16,
79 bit_precision: u16,
80 exponent_location: u8,
81 exponent_size: u8,
82 mantissa_location: u8,
83 mantissa_size: u8,
84 exponent_bias: u32,
85 },
86 Time {
88 size: u32,
89 byte_order: DatatypeByteOrder,
90 bit_precision: u16,
91 },
92 String {
94 size: u32,
95 padding: StringPadding,
96 charset: CharacterSet,
97 },
98 BitField {
100 size: u32,
101 byte_order: DatatypeByteOrder,
102 bit_offset: u16,
103 bit_precision: u16,
104 },
105 Opaque { size: u32, tag: Vec<u8> },
107 Compound {
109 size: u32,
110 members: Vec<CompoundMember>,
111 },
112 Reference { size: u32, ref_type: ReferenceType },
114 Enumeration {
116 size: u32,
117 base_type: Box<Datatype>,
118 members: Vec<EnumMember>,
119 },
120 VariableLength {
122 is_string: bool,
123 padding: Option<StringPadding>,
124 charset: Option<CharacterSet>,
125 base_type: Box<Datatype>,
126 },
127 Array {
129 base_type: Box<Datatype>,
130 dimensions: Vec<u32>,
131 },
132}
133
134fn ensure_len(data: &[u8], offset: usize, needed: usize) -> Result<(), FormatError> {
135 match offset.checked_add(needed) {
136 Some(end) if end <= data.len() => Ok(()),
137 _ => Err(FormatError::UnexpectedEof {
138 expected: offset.saturating_add(needed),
139 available: data.len(),
140 }),
141 }
142}
143
144fn parse_string_padding(val: u8) -> Result<StringPadding, FormatError> {
145 match val {
146 0 => Ok(StringPadding::NullTerminate),
147 1 => Ok(StringPadding::NullPad),
148 2 => Ok(StringPadding::SpacePad),
149 _ => Err(FormatError::InvalidStringPadding(val)),
150 }
151}
152
153fn parse_charset(val: u8) -> Result<CharacterSet, FormatError> {
154 match val {
155 0 => Ok(CharacterSet::Ascii),
156 1 => Ok(CharacterSet::Utf8),
157 _ => Err(FormatError::InvalidCharacterSet(val)),
158 }
159}
160
161fn read_null_terminated_string(data: &[u8], offset: usize) -> Result<(String, usize), FormatError> {
164 if offset >= data.len() {
165 return Err(FormatError::UnexpectedEof {
166 expected: offset + 1,
167 available: data.len(),
168 });
169 }
170 let remaining = &data[offset..];
171 let null_pos = remaining
172 .iter()
173 .position(|&b| b == 0)
174 .ok_or(FormatError::UnexpectedEof {
175 expected: offset + 1,
176 available: data.len(),
177 })?;
178 let name = String::from_utf8_lossy(&remaining[..null_pos]).into_owned();
179 Ok((name, null_pos + 1))
180}
181
182fn offset_bytes_for_size(compound_size: u32) -> usize {
184 if compound_size <= 0xFF {
185 1
186 } else if compound_size <= 0xFFFF {
187 2
188 } else {
189 4
190 }
191}
192
193fn read_uint(data: &[u8], offset: usize, nbytes: usize) -> Result<u64, FormatError> {
195 ensure_len(data, offset, nbytes)?;
196 let slice = &data[offset..offset + nbytes];
197 Ok(match nbytes {
198 1 => slice[0] as u64,
199 2 => LittleEndian::read_u16(slice) as u64,
200 4 => LittleEndian::read_u32(slice) as u64,
201 8 => LittleEndian::read_u64(slice),
202 _ => {
203 return Err(FormatError::UnexpectedEof {
204 expected: offset + nbytes,
205 available: data.len(),
206 });
207 }
208 })
209}
210
211impl Datatype {
212 pub fn parse(data: &[u8]) -> Result<(Datatype, usize), FormatError> {
216 ensure_len(data, 0, 8)?;
218
219 let class_and_version = data[0];
220 let class_id = class_and_version & 0x0F;
221 let version = (class_and_version >> 4) & 0x0F;
222
223 let bf0 = data[1];
225 let bf1 = data[2];
226 let bf2 = data[3];
227 let _bit_field_24 = (bf0 as u32) | ((bf1 as u32) << 8) | ((bf2 as u32) << 16);
228
229 let size = LittleEndian::read_u32(&data[4..8]);
230 let mut pos = 8;
231
232 match class_id {
233 0 => {
234 ensure_len(data, pos, 4)?;
236 let byte_order = if bf0 & 0x01 == 0 {
237 DatatypeByteOrder::LittleEndian
238 } else {
239 DatatypeByteOrder::BigEndian
240 };
241 let signed = (bf0 >> 3) & 0x01 == 1;
242 let bit_offset = LittleEndian::read_u16(&data[pos..pos + 2]);
243 let bit_precision = LittleEndian::read_u16(&data[pos + 2..pos + 4]);
244 pos += 4;
245 Ok((
246 Datatype::FixedPoint {
247 size,
248 byte_order,
249 signed,
250 bit_offset,
251 bit_precision,
252 },
253 pos,
254 ))
255 }
256 1 => {
257 ensure_len(data, pos, 12)?;
259 let bo_low = bf0 & 0x01;
260 let bo_high = (bf0 >> 6) & 0x01;
261 let byte_order = match (bo_high, bo_low) {
262 (0, 0) => DatatypeByteOrder::LittleEndian,
263 (0, 1) => DatatypeByteOrder::BigEndian,
264 (1, 0) => DatatypeByteOrder::Vax,
265 (1, 1) => DatatypeByteOrder::Vax,
266 _ => unreachable!(),
267 };
268 let bit_offset = LittleEndian::read_u16(&data[pos..pos + 2]);
269 let bit_precision = LittleEndian::read_u16(&data[pos + 2..pos + 4]);
270 let exponent_location = data[pos + 4];
271 let exponent_size = data[pos + 5];
272 let mantissa_location = data[pos + 6];
273 let mantissa_size = data[pos + 7];
274 let exponent_bias = LittleEndian::read_u32(&data[pos + 8..pos + 12]);
275 pos += 12;
276 Ok((
277 Datatype::FloatingPoint {
278 size,
279 byte_order,
280 bit_offset,
281 bit_precision,
282 exponent_location,
283 exponent_size,
284 mantissa_location,
285 mantissa_size,
286 exponent_bias,
287 },
288 pos,
289 ))
290 }
291 2 => {
292 ensure_len(data, pos, 2)?;
294 let byte_order = if bf0 & 0x01 == 0 {
295 DatatypeByteOrder::LittleEndian
296 } else {
297 DatatypeByteOrder::BigEndian
298 };
299 let bit_precision = LittleEndian::read_u16(&data[pos..pos + 2]);
300 pos += 2;
301 Ok((
302 Datatype::Time {
303 size,
304 byte_order,
305 bit_precision,
306 },
307 pos,
308 ))
309 }
310 3 => {
311 let padding_val = bf0 & 0x0F;
313 let charset_val = (bf0 >> 4) & 0x0F;
314 let padding = parse_string_padding(padding_val)?;
315 let charset = parse_charset(charset_val)?;
316 Ok((
317 Datatype::String {
318 size,
319 padding,
320 charset,
321 },
322 pos,
323 ))
324 }
325 4 => {
326 ensure_len(data, pos, 4)?;
328 let byte_order = if bf0 & 0x01 == 0 {
329 DatatypeByteOrder::LittleEndian
330 } else {
331 DatatypeByteOrder::BigEndian
332 };
333 let bit_offset = LittleEndian::read_u16(&data[pos..pos + 2]);
334 let bit_precision = LittleEndian::read_u16(&data[pos + 2..pos + 4]);
335 pos += 4;
336 Ok((
337 Datatype::BitField {
338 size,
339 byte_order,
340 bit_offset,
341 bit_precision,
342 },
343 pos,
344 ))
345 }
346 5 => {
347 let tag_len = bf0 as usize;
349 ensure_len(data, pos, tag_len)?;
350 let tag = data[pos..pos + tag_len].to_vec();
351 let padded = (tag_len + 7) & !7;
353 let pos = 8 + padded; Ok((Datatype::Opaque { size, tag }, pos))
355 }
356 6 => {
357 let num_members = (bf0 as u16) | ((bf1 as u16) << 8);
359 let mut members = Vec::with_capacity(num_members as usize);
360
361 if version == 3 || version == 4 {
362 let ob = offset_bytes_for_size(size);
363 for _ in 0..num_members {
364 let (name, name_len) = read_null_terminated_string(data, pos)?;
365 pos += name_len;
366 let byte_offset = read_uint(data, pos, ob)?;
367 pos += ob;
368 let (member_dt, consumed) = Datatype::parse(&data[pos..])?;
369 pos += consumed;
370 members.push(CompoundMember {
371 name,
372 byte_offset,
373 datatype: member_dt,
374 });
375 }
376 } else if version == 1 || version == 2 {
377 for _ in 0..num_members {
384 let (name, name_len) = read_null_terminated_string(data, pos)?;
385 let padded = (name_len + 7) & !7;
386 pos += padded;
387 ensure_len(data, pos, 4)?;
388 let byte_offset = LittleEndian::read_u32(&data[pos..pos + 4]) as u64;
389 pos += 4;
390 if version == 1 {
391 ensure_len(data, pos, 28)?;
392 pos += 28;
393 }
394 let (member_dt, consumed) = Datatype::parse(&data[pos..])?;
395 pos += consumed;
396 members.push(CompoundMember {
397 name,
398 byte_offset,
399 datatype: member_dt,
400 });
401 }
402 } else {
403 return Err(FormatError::InvalidDatatypeVersion {
404 class: class_id,
405 version,
406 });
407 }
408
409 Ok((Datatype::Compound { size, members }, pos))
410 }
411 7 => {
412 let ref_type_val = bf0 & 0x0F;
414 let ref_type = match ref_type_val {
415 0 => ReferenceType::Object,
416 1 => ReferenceType::DatasetRegion,
417 _ => return Err(FormatError::InvalidReferenceType(ref_type_val)),
418 };
419 Ok((Datatype::Reference { size, ref_type }, pos))
420 }
421 8 => {
422 let num_members = (bf0 as u16) | ((bf1 as u16) << 8);
424 let (base_type, base_consumed) = Datatype::parse(&data[pos..])?;
426 pos += base_consumed;
427 let base_size = base_type.type_size();
428 let mut members = Vec::with_capacity(num_members as usize);
429 let mut member_names = Vec::with_capacity(num_members as usize);
433 for _ in 0..num_members {
434 let (name, name_len) = read_null_terminated_string(data, pos)?;
435 if version < 3 {
436 let padded = (name_len + 7) & !7;
437 pos += padded;
438 } else {
439 pos += name_len;
440 }
441 member_names.push(name);
442 }
443 for name in &member_names {
445 ensure_len(data, pos, base_size as usize)?;
446 let value = data[pos..pos + base_size as usize].to_vec();
447 pos += base_size as usize;
448 members.push(EnumMember {
449 name: name.clone(),
450 value,
451 });
452 }
453 Ok((
454 Datatype::Enumeration {
455 size,
456 base_type: Box::new(base_type),
457 members,
458 },
459 pos,
460 ))
461 }
462 9 => {
463 let vl_type = bf0 & 0x0F;
465 let is_string = vl_type == 1;
466 let padding = if is_string {
467 let pad_val = (bf0 >> 4) & 0x0F;
468 Some(parse_string_padding(pad_val)?)
469 } else {
470 None
471 };
472 let charset = if is_string {
473 let cs_val = bf1 & 0x0F;
474 Some(parse_charset(cs_val)?)
475 } else {
476 None
477 };
478 let (base_type, consumed) = Datatype::parse(&data[pos..])?;
479 pos += consumed;
480 Ok((
481 Datatype::VariableLength {
482 is_string,
483 padding,
484 charset,
485 base_type: Box::new(base_type),
486 },
487 pos,
488 ))
489 }
490 10 => {
491 if version == 2 {
493 ensure_len(data, pos, 4)?;
494 let ndims = data[pos] as usize;
495 pos += 4; ensure_len(data, pos, ndims * 4 + ndims * 4)?;
497 let mut dimensions = Vec::with_capacity(ndims);
498 for _ in 0..ndims {
499 dimensions.push(LittleEndian::read_u32(&data[pos..pos + 4]));
500 pos += 4;
501 }
502 pos += ndims * 4;
504 let (base_type, consumed) = Datatype::parse(&data[pos..])?;
505 pos += consumed;
506 Ok((
507 Datatype::Array {
508 base_type: Box::new(base_type),
509 dimensions,
510 },
511 pos,
512 ))
513 } else if version == 3 {
514 ensure_len(data, pos, 1)?;
515 let ndims = data[pos] as usize;
516 pos += 1;
517 ensure_len(data, pos, ndims * 4)?;
518 let mut dimensions = Vec::with_capacity(ndims);
519 for _ in 0..ndims {
520 dimensions.push(LittleEndian::read_u32(&data[pos..pos + 4]));
521 pos += 4;
522 }
523 let (base_type, consumed) = Datatype::parse(&data[pos..])?;
524 pos += consumed;
525 Ok((
526 Datatype::Array {
527 base_type: Box::new(base_type),
528 dimensions,
529 },
530 pos,
531 ))
532 } else {
533 Err(FormatError::InvalidDatatypeVersion {
534 class: class_id,
535 version,
536 })
537 }
538 }
539 11 => {
540 let num_members = (bf0 as u16) | ((bf1 as u16) << 8);
546 let mut members = Vec::with_capacity(num_members as usize);
547 let ob = offset_bytes_for_size(size);
548 for _ in 0..num_members {
549 let (name, name_len) = read_null_terminated_string(data, pos)?;
550 pos += name_len;
551 let byte_offset = read_uint(data, pos, ob)?;
552 pos += ob;
553 let (member_dt, consumed) = Datatype::parse(&data[pos..])?;
554 pos += consumed;
555 members.push(CompoundMember {
556 name,
557 byte_offset,
558 datatype: member_dt,
559 });
560 }
561 Ok((Datatype::Compound { size, members }, pos))
562 }
563 _ => Err(FormatError::InvalidDatatypeClass(class_id)),
564 }
565 }
566
567 pub fn serialize(&self) -> Vec<u8> {
569 match self {
570 Datatype::FixedPoint {
571 size,
572 byte_order,
573 signed,
574 bit_offset,
575 bit_precision,
576 } => {
577 let mut bf0 = 0u8;
578 if matches!(byte_order, DatatypeByteOrder::BigEndian) {
579 bf0 |= 0x01;
580 }
581 if *signed {
582 bf0 |= 0x08;
583 }
584 let mut buf = Self::build_header(0, 1, [bf0, 0, 0], *size);
585 buf.extend_from_slice(&bit_offset.to_le_bytes());
586 buf.extend_from_slice(&bit_precision.to_le_bytes());
587 buf
588 }
589 Datatype::FloatingPoint {
590 size,
591 byte_order,
592 bit_offset,
593 bit_precision,
594 exponent_location,
595 exponent_size,
596 mantissa_location,
597 mantissa_size,
598 exponent_bias,
599 } => {
600 let mut bf0 = 0x20u8; match byte_order {
602 DatatypeByteOrder::BigEndian => {
603 bf0 |= 0x01;
604 }
605 DatatypeByteOrder::Vax => {
606 bf0 |= 0x40;
607 }
608 _ => {}
609 }
610 #[expect(
612 clippy::cast_possible_truncation,
613 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)"
614 )]
615 let bf1 = (*size * 8 - 1) as u8;
616 let mut buf = Self::build_header(1, 1, [bf0, bf1, 0], *size);
617 buf.extend_from_slice(&bit_offset.to_le_bytes());
618 buf.extend_from_slice(&bit_precision.to_le_bytes());
619 buf.push(*exponent_location);
620 buf.push(*exponent_size);
621 buf.push(*mantissa_location);
622 buf.push(*mantissa_size);
623 buf.extend_from_slice(&exponent_bias.to_le_bytes());
624 buf
625 }
626 Datatype::String {
627 size,
628 padding,
629 charset,
630 } => {
631 let pad_val = match padding {
632 StringPadding::NullTerminate => 0,
633 StringPadding::NullPad => 1,
634 StringPadding::SpacePad => 2,
635 };
636 let cs_val = match charset {
637 CharacterSet::Ascii => 0,
638 CharacterSet::Utf8 => 1,
639 };
640 let bf0 = pad_val | (cs_val << 4);
641 Self::build_header(3, 1, [bf0, 0, 0], *size)
642 }
643 Datatype::VariableLength {
644 is_string,
645 padding,
646 charset,
647 base_type,
648 } => {
649 let mut bf0 = if *is_string { 0x01u8 } else { 0x00 };
650 if *is_string && let Some(p) = padding {
651 let pv = match p {
652 StringPadding::NullTerminate => 0,
653 StringPadding::NullPad => 1,
654 StringPadding::SpacePad => 2,
655 };
656 bf0 |= pv << 4;
657 }
658 let bf1 = if *is_string {
659 charset.as_ref().map_or(0, |c| match c {
660 CharacterSet::Ascii => 0,
661 CharacterSet::Utf8 => 1,
662 })
663 } else {
664 0
665 };
666 let mut buf = Self::build_header(9, 1, [bf0, bf1, 0], 16);
667 buf.extend_from_slice(&base_type.serialize());
668 buf
669 }
670 Datatype::Compound { size, members } => {
671 #[expect(
672 clippy::cast_possible_truncation,
673 reason = "compound member count is written into the 2-byte member-count field of the datatype message"
674 )]
675 let num = members.len() as u16;
676 let bf0 = (num & 0xFF) as u8;
677 let bf1 = ((num >> 8) & 0xFF) as u8;
678 let mut buf = Self::build_header(6, 3, [bf0, bf1, 0], *size);
679 let ob = offset_bytes_for_size(*size);
680 for m in members {
681 buf.extend_from_slice(m.name.as_bytes());
683 buf.push(0);
684 #[expect(
686 clippy::cast_possible_truncation,
687 reason = "ob is the offset-byte width chosen to hold byte_offset, so each arm casts to a width that fits by construction"
688 )]
689 match ob {
690 1 => buf.push(m.byte_offset as u8),
691 2 => buf.extend_from_slice(&(m.byte_offset as u16).to_le_bytes()),
692 _ => buf.extend_from_slice(&(m.byte_offset as u32).to_le_bytes()),
693 }
694 buf.extend_from_slice(&m.datatype.serialize());
696 }
697 buf
698 }
699 Datatype::Enumeration {
700 size,
701 base_type,
702 members,
703 } => {
704 #[expect(
705 clippy::cast_possible_truncation,
706 reason = "enumeration member count is written into the 2-byte member-count field of the datatype message"
707 )]
708 let num = members.len() as u16;
709 let bf0 = (num & 0xFF) as u8;
710 let bf1 = ((num >> 8) & 0xFF) as u8;
711 let mut buf = Self::build_header(8, 3, [bf0, bf1, 0], *size);
712 buf.extend_from_slice(&base_type.serialize());
714 for m in members {
716 buf.extend_from_slice(m.name.as_bytes());
717 buf.push(0);
718 }
719 for m in members {
721 buf.extend_from_slice(&m.value);
722 }
723 buf
724 }
725 Datatype::Array {
726 base_type,
727 dimensions,
728 } => {
729 let mut buf = Self::build_header(10, 3, [0, 0, 0], self.type_size());
730 #[expect(
731 clippy::cast_possible_truncation,
732 reason = "array rank is written into the 1-byte dimensionality field; HDF5 caps array rank well below 255"
733 )]
734 buf.push(dimensions.len() as u8);
735 for &d in dimensions {
736 buf.extend_from_slice(&d.to_le_bytes());
737 }
738 buf.extend_from_slice(&base_type.serialize());
739 buf
740 }
741 Datatype::Reference { size, ref_type } => {
742 let bf0 = match ref_type {
743 ReferenceType::Object => 0,
744 ReferenceType::DatasetRegion => 1,
745 };
746 Self::build_header(7, 1, [bf0, 0, 0], *size)
747 }
748 Datatype::Time {
749 size,
750 byte_order,
751 bit_precision,
752 } => {
753 let bf0 = if matches!(byte_order, DatatypeByteOrder::BigEndian) {
755 0x01u8
756 } else {
757 0
758 };
759 let mut buf = Self::build_header(2, 1, [bf0, 0, 0], *size);
760 buf.extend_from_slice(&bit_precision.to_le_bytes());
761 buf
762 }
763 Datatype::BitField {
764 size,
765 byte_order,
766 bit_offset,
767 bit_precision,
768 } => {
769 let bf0 = if matches!(byte_order, DatatypeByteOrder::BigEndian) {
770 0x01u8
771 } else {
772 0
773 };
774 let mut buf = Self::build_header(4, 1, [bf0, 0, 0], *size);
775 buf.extend_from_slice(&bit_offset.to_le_bytes());
776 buf.extend_from_slice(&bit_precision.to_le_bytes());
777 buf
778 }
779 Datatype::Opaque { size, tag } => {
780 #[expect(
783 clippy::cast_possible_truncation,
784 reason = "opaque tag length is written into the 1-byte tag-length bit field (bf0)"
785 )]
786 let bf0 = tag.len() as u8;
787 let mut buf = Self::build_header(5, 1, [bf0, 0, 0], *size);
788 buf.extend_from_slice(tag);
789 let padded = (tag.len() + 7) & !7;
790 buf.resize(buf.len() + (padded - tag.len()), 0);
791 buf
792 }
793 }
794 }
795
796 fn build_header(class: u8, version: u8, bf: [u8; 3], size: u32) -> Vec<u8> {
797 let mut buf = vec![0u8; 8];
798 buf[0] = (class & 0x0F) | ((version & 0x0F) << 4);
799 buf[1] = bf[0];
800 buf[2] = bf[1];
801 buf[3] = bf[2];
802 buf[4..8].copy_from_slice(&size.to_le_bytes());
803 buf
804 }
805
806 pub fn type_size(&self) -> u32 {
808 match self {
809 Datatype::FixedPoint { size, .. } => *size,
810 Datatype::FloatingPoint { size, .. } => *size,
811 Datatype::Time { size, .. } => *size,
812 Datatype::String { size, .. } => *size,
813 Datatype::BitField { size, .. } => *size,
814 Datatype::Opaque { size, .. } => *size,
815 Datatype::Compound { size, .. } => *size,
816 Datatype::Reference { size, .. } => *size,
817 Datatype::Enumeration { size, .. } => *size,
818 Datatype::VariableLength { .. } => 16, Datatype::Array {
820 base_type,
821 dimensions,
822 } => {
823 let elem_count: u32 = dimensions
824 .iter()
825 .copied()
826 .fold(1u32, |a, b| a.saturating_mul(b));
827 base_type.type_size().saturating_mul(elem_count)
828 }
829 }
830 }
831}
832
833#[cfg(test)]
835fn build_dt_header(class: u8, version: u8, bf: [u8; 3], size: u32) -> Vec<u8> {
836 let mut buf = vec![0u8; 8];
837 buf[0] = (class & 0x0F) | ((version & 0x0F) << 4);
838 buf[1] = bf[0];
839 buf[2] = bf[1];
840 buf[3] = bf[2];
841 LittleEndian::write_u32(&mut buf[4..8], size);
842 buf
843}
844
845#[cfg(test)]
846mod tests {
847 use super::*;
848
849 fn build_fixed_point(
851 size: u32,
852 be: bool,
853 signed: bool,
854 bit_offset: u16,
855 bit_precision: u16,
856 ) -> Vec<u8> {
857 let bf0 = if be { 0x01 } else { 0x00 } | if signed { 0x08 } else { 0x00 };
858 let mut buf = build_dt_header(0, 1, [bf0, 0, 0], size);
859 let mut props = [0u8; 4];
860 LittleEndian::write_u16(&mut props[0..2], bit_offset);
861 LittleEndian::write_u16(&mut props[2..4], bit_precision);
862 buf.extend_from_slice(&props);
863 buf
864 }
865
866 fn build_float(
868 size: u32,
869 exp_loc: u8,
870 exp_size: u8,
871 mant_loc: u8,
872 mant_size: u8,
873 exp_bias: u32,
874 ) -> Vec<u8> {
875 let bf0 = 0x00u8;
877 let bf1 = 0x00u8;
878 let bf2 = 0x02u8; let mut buf = build_dt_header(1, 1, [bf0, bf1, bf2], size);
881 let mut props = [0u8; 12];
882 LittleEndian::write_u16(&mut props[0..2], 0); LittleEndian::write_u16(&mut props[2..4], (size * 8) as u16); props[4] = exp_loc;
885 props[5] = exp_size;
886 props[6] = mant_loc;
887 props[7] = mant_size;
888 LittleEndian::write_u32(&mut props[8..12], exp_bias);
889 buf.extend_from_slice(&props);
890 buf
891 }
892
893 #[test]
894 fn test_fixed_point_u8() {
895 let data = build_fixed_point(1, false, false, 0, 8);
896 let (dt, consumed) = Datatype::parse(&data).unwrap();
897 assert_eq!(consumed, 12);
898 assert_eq!(
899 dt,
900 Datatype::FixedPoint {
901 size: 1,
902 byte_order: DatatypeByteOrder::LittleEndian,
903 signed: false,
904 bit_offset: 0,
905 bit_precision: 8,
906 }
907 );
908 }
909
910 #[test]
911 fn test_fixed_point_i16_le() {
912 let data = build_fixed_point(2, false, true, 0, 16);
913 let (dt, _) = Datatype::parse(&data).unwrap();
914 assert_eq!(
915 dt,
916 Datatype::FixedPoint {
917 size: 2,
918 byte_order: DatatypeByteOrder::LittleEndian,
919 signed: true,
920 bit_offset: 0,
921 bit_precision: 16,
922 }
923 );
924 }
925
926 #[test]
927 fn test_fixed_point_u32_be() {
928 let data = build_fixed_point(4, true, false, 0, 32);
929 let (dt, _) = Datatype::parse(&data).unwrap();
930 match &dt {
931 Datatype::FixedPoint {
932 byte_order,
933 signed,
934 size,
935 ..
936 } => {
937 assert_eq!(*byte_order, DatatypeByteOrder::BigEndian);
938 assert!(!signed);
939 assert_eq!(*size, 4);
940 }
941 _ => panic!("expected FixedPoint"),
942 }
943 }
944
945 #[test]
946 fn test_fixed_point_i64_le() {
947 let data = build_fixed_point(8, false, true, 0, 64);
948 let (dt, _) = Datatype::parse(&data).unwrap();
949 assert_eq!(
950 dt,
951 Datatype::FixedPoint {
952 size: 8,
953 byte_order: DatatypeByteOrder::LittleEndian,
954 signed: true,
955 bit_offset: 0,
956 bit_precision: 64,
957 }
958 );
959 }
960
961 #[test]
962 fn test_float_f32_le() {
963 let data = build_float(4, 23, 8, 0, 23, 127);
965 let (dt, consumed) = Datatype::parse(&data).unwrap();
966 assert_eq!(consumed, 20);
967 assert_eq!(
968 dt,
969 Datatype::FloatingPoint {
970 size: 4,
971 byte_order: DatatypeByteOrder::LittleEndian,
972 bit_offset: 0,
973 bit_precision: 32,
974 exponent_location: 23,
975 exponent_size: 8,
976 mantissa_location: 0,
977 mantissa_size: 23,
978 exponent_bias: 127,
979 }
980 );
981 }
982
983 #[test]
984 fn test_float_f64_le() {
985 let data = build_float(8, 52, 11, 0, 52, 1023);
986 let (dt, _) = Datatype::parse(&data).unwrap();
987 assert_eq!(
988 dt,
989 Datatype::FloatingPoint {
990 size: 8,
991 byte_order: DatatypeByteOrder::LittleEndian,
992 bit_offset: 0,
993 bit_precision: 64,
994 exponent_location: 52,
995 exponent_size: 11,
996 mantissa_location: 0,
997 mantissa_size: 52,
998 exponent_bias: 1023,
999 }
1000 );
1001 }
1002
1003 #[test]
1004 fn test_string_null_terminated_ascii() {
1005 let buf = build_dt_header(3, 1, [0x00, 0, 0], 10); let (dt, consumed) = Datatype::parse(&buf).unwrap();
1007 assert_eq!(consumed, 8);
1008 assert_eq!(
1009 dt,
1010 Datatype::String {
1011 size: 10,
1012 padding: StringPadding::NullTerminate,
1013 charset: CharacterSet::Ascii,
1014 }
1015 );
1016 }
1017
1018 #[test]
1019 fn test_string_space_padded_utf8() {
1020 let buf = build_dt_header(3, 1, [0x12, 0, 0], 32);
1022 let (dt, _) = Datatype::parse(&buf).unwrap();
1023 assert_eq!(
1024 dt,
1025 Datatype::String {
1026 size: 32,
1027 padding: StringPadding::SpacePad,
1028 charset: CharacterSet::Utf8,
1029 }
1030 );
1031 }
1032
1033 #[test]
1034 fn test_opaque() {
1035 let mut buf = build_dt_header(5, 1, [4, 0, 0], 64);
1037 buf.extend_from_slice(b"BLOB");
1038 buf.extend_from_slice(&[0, 0, 0, 0]);
1040 let (dt, consumed) = Datatype::parse(&buf).unwrap();
1041 assert_eq!(consumed, 16); assert_eq!(
1043 dt,
1044 Datatype::Opaque {
1045 size: 64,
1046 tag: b"BLOB".to_vec(),
1047 }
1048 );
1049 }
1050
1051 #[test]
1052 fn test_compound_v3_two_members() {
1053 let mut buf = build_dt_header(6, 3, [2, 0, 0], 12); buf.extend_from_slice(b"x\0");
1058 buf.push(0); buf.extend_from_slice(&build_fixed_point(4, false, false, 0, 32));
1060 buf.extend_from_slice(b"y\0");
1062 buf.push(4); buf.extend_from_slice(&build_float(8, 52, 11, 0, 52, 1023));
1064
1065 let (dt, _) = Datatype::parse(&buf).unwrap();
1066 match dt {
1067 Datatype::Compound { size, members } => {
1068 assert_eq!(size, 12);
1069 assert_eq!(members.len(), 2);
1070 assert_eq!(members[0].name, "x");
1071 assert_eq!(members[0].byte_offset, 0);
1072 assert_eq!(members[1].name, "y");
1073 assert_eq!(members[1].byte_offset, 4);
1074 match &members[0].datatype {
1075 Datatype::FixedPoint {
1076 size: 4,
1077 signed: false,
1078 ..
1079 } => {}
1080 other => panic!("expected u32, got {other:?}"),
1081 }
1082 match &members[1].datatype {
1083 Datatype::FloatingPoint { size: 8, .. } => {}
1084 other => panic!("expected f64, got {other:?}"),
1085 }
1086 }
1087 _ => panic!("expected Compound"),
1088 }
1089 }
1090
1091 #[test]
1092 fn test_compound_v1_complex_matlab_layout() {
1093 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)] {
1103 buf.extend_from_slice(name); let mut off = [0u8; 4];
1105 LittleEndian::write_u32(&mut off, offset);
1106 buf.extend_from_slice(&off);
1107 buf.extend_from_slice(&[0u8; 28]); buf.extend_from_slice(&build_float(8, 52, 11, 0, 52, 1023));
1109 }
1110
1111 let (dt, _) = Datatype::parse(&buf).unwrap();
1112 match dt {
1113 Datatype::Compound { size, members } => {
1114 assert_eq!(size, 16);
1115 assert_eq!(members.len(), 2);
1116 assert_eq!(members[0].name, "real");
1117 assert_eq!(members[0].byte_offset, 0);
1118 assert_eq!(members[1].name, "imag");
1119 assert_eq!(members[1].byte_offset, 8);
1120 for m in &members {
1121 assert!(
1122 matches!(m.datatype, Datatype::FloatingPoint { size: 8, .. }),
1123 "expected f64 member, got {:?}",
1124 m.datatype
1125 );
1126 }
1127 }
1128 _ => panic!("expected Compound"),
1129 }
1130 }
1131
1132 #[test]
1133 fn test_reference_object() {
1134 let buf = build_dt_header(7, 1, [0, 0, 0], 8);
1135 let (dt, _) = Datatype::parse(&buf).unwrap();
1136 assert_eq!(
1137 dt,
1138 Datatype::Reference {
1139 size: 8,
1140 ref_type: ReferenceType::Object,
1141 }
1142 );
1143 }
1144
1145 #[test]
1146 fn test_reference_region() {
1147 let buf = build_dt_header(7, 1, [1, 0, 0], 12);
1148 let (dt, _) = Datatype::parse(&buf).unwrap();
1149 assert_eq!(
1150 dt,
1151 Datatype::Reference {
1152 size: 12,
1153 ref_type: ReferenceType::DatasetRegion,
1154 }
1155 );
1156 }
1157
1158 #[test]
1159 fn test_enumeration() {
1160 let mut buf = build_dt_header(8, 3, [3, 0, 0], 4); buf.extend_from_slice(&build_fixed_point(4, false, true, 0, 32));
1164 buf.extend_from_slice(b"RED\0");
1166 buf.extend_from_slice(b"GREEN\0");
1167 buf.extend_from_slice(b"BLUE\0");
1168 buf.extend_from_slice(&0i32.to_le_bytes());
1170 buf.extend_from_slice(&1i32.to_le_bytes());
1171 buf.extend_from_slice(&2i32.to_le_bytes());
1172
1173 let (dt, _) = Datatype::parse(&buf).unwrap();
1174 match dt {
1175 Datatype::Enumeration {
1176 size,
1177 base_type,
1178 members,
1179 } => {
1180 assert_eq!(size, 4);
1181 assert_eq!(members.len(), 3);
1182 assert_eq!(members[0].name, "RED");
1183 assert_eq!(members[0].value, 0i32.to_le_bytes().to_vec());
1184 assert_eq!(members[1].name, "GREEN");
1185 assert_eq!(members[1].value, 1i32.to_le_bytes().to_vec());
1186 assert_eq!(members[2].name, "BLUE");
1187 assert_eq!(members[2].value, 2i32.to_le_bytes().to_vec());
1188 match *base_type {
1189 Datatype::FixedPoint {
1190 signed: true,
1191 size: 4,
1192 ..
1193 } => {}
1194 other => panic!("expected i32, got {other:?}"),
1195 }
1196 }
1197 _ => panic!("expected Enumeration"),
1198 }
1199 }
1200
1201 #[test]
1202 fn test_variable_length_string_utf8() {
1203 let mut buf = build_dt_header(9, 1, [0x01, 0x01, 0], 16);
1207 buf.extend_from_slice(&build_fixed_point(1, false, false, 0, 8));
1209
1210 let (dt, _) = Datatype::parse(&buf).unwrap();
1211 match dt {
1212 Datatype::VariableLength {
1213 is_string,
1214 padding,
1215 charset,
1216 base_type,
1217 } => {
1218 assert!(is_string);
1219 assert_eq!(padding, Some(StringPadding::NullTerminate));
1220 assert_eq!(charset, Some(CharacterSet::Utf8));
1221 assert_eq!(base_type.type_size(), 1);
1222 }
1223 _ => panic!("expected VariableLength"),
1224 }
1225 }
1226
1227 #[test]
1228 fn test_variable_length_sequence_f32() {
1229 let mut buf = build_dt_header(9, 1, [0x00, 0x00, 0], 16);
1232 buf.extend_from_slice(&build_float(4, 23, 8, 0, 23, 127));
1234
1235 let (dt, _) = Datatype::parse(&buf).unwrap();
1236 match dt {
1237 Datatype::VariableLength {
1238 is_string,
1239 padding,
1240 charset,
1241 base_type,
1242 } => {
1243 assert!(!is_string);
1244 assert_eq!(padding, None);
1245 assert_eq!(charset, None);
1246 assert_eq!(base_type.type_size(), 4);
1247 }
1248 _ => panic!("expected VariableLength"),
1249 }
1250 }
1251
1252 #[test]
1253 fn test_array_2d() {
1254 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));
1261
1262 let (dt, _) = Datatype::parse(&buf).unwrap();
1263 match dt {
1264 Datatype::Array {
1265 base_type,
1266 dimensions,
1267 } => {
1268 assert_eq!(dimensions, vec![3, 4]);
1269 match *base_type {
1270 Datatype::FixedPoint {
1271 size: 4,
1272 signed: true,
1273 ..
1274 } => {}
1275 other => panic!("expected i32, got {other:?}"),
1276 }
1277 }
1278 _ => panic!("expected Array"),
1279 }
1280 }
1281
1282 #[test]
1283 fn test_bitfield() {
1284 let mut buf = build_dt_header(4, 1, [0, 0, 0], 2); let mut props = [0u8; 4];
1286 LittleEndian::write_u16(&mut props[0..2], 0);
1287 LittleEndian::write_u16(&mut props[2..4], 16);
1288 buf.extend_from_slice(&props);
1289
1290 let (dt, _) = Datatype::parse(&buf).unwrap();
1291 assert_eq!(
1292 dt,
1293 Datatype::BitField {
1294 size: 2,
1295 byte_order: DatatypeByteOrder::LittleEndian,
1296 bit_offset: 0,
1297 bit_precision: 16,
1298 }
1299 );
1300 }
1301
1302 #[test]
1303 fn test_time() {
1304 let mut buf = build_dt_header(2, 1, [0, 0, 0], 8);
1305 let mut props = [0u8; 2];
1306 LittleEndian::write_u16(&mut props[0..2], 64);
1307 buf.extend_from_slice(&props);
1308
1309 let (dt, consumed) = Datatype::parse(&buf).unwrap();
1310 assert_eq!(consumed, 10);
1311 assert_eq!(
1312 dt,
1313 Datatype::Time {
1314 size: 8,
1315 byte_order: DatatypeByteOrder::LittleEndian,
1316 bit_precision: 64,
1317 }
1318 );
1319 }
1320
1321 #[test]
1322 fn test_time_byte_order_roundtrips() {
1323 for (be, order) in [
1326 (0u8, DatatypeByteOrder::LittleEndian),
1327 (1u8, DatatypeByteOrder::BigEndian),
1328 ] {
1329 let mut buf = build_dt_header(2, 1, [be, 0, 0], 4);
1330 buf.extend_from_slice(&32u16.to_le_bytes());
1331 let (dt, _) = Datatype::parse(&buf).unwrap();
1332 assert_eq!(
1333 dt,
1334 Datatype::Time {
1335 size: 4,
1336 byte_order: order.clone(),
1337 bit_precision: 32,
1338 }
1339 );
1340 let (reparsed, _) = Datatype::parse(&dt.serialize()).unwrap();
1342 assert_eq!(reparsed, dt);
1343 }
1344 }
1345
1346 #[test]
1347 fn test_nested_compound_array_enum() {
1348 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");
1353 enum_bytes.extend_from_slice(b"B\0");
1354 enum_bytes.extend_from_slice(&0i32.to_le_bytes());
1355 enum_bytes.extend_from_slice(&1i32.to_le_bytes());
1356
1357 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);
1362
1363 let mut buf = build_dt_header(6, 3, [1, 0, 0], 8); buf.extend_from_slice(b"data\0");
1366 buf.push(0); buf.extend_from_slice(&array_bytes);
1368
1369 let (dt, _) = Datatype::parse(&buf).unwrap();
1370 match dt {
1371 Datatype::Compound { members, .. } => {
1372 assert_eq!(members.len(), 1);
1373 assert_eq!(members[0].name, "data");
1374 match &members[0].datatype {
1375 Datatype::Array {
1376 dimensions,
1377 base_type,
1378 } => {
1379 assert_eq!(dimensions, &[2]);
1380 match base_type.as_ref() {
1381 Datatype::Enumeration { members, .. } => {
1382 assert_eq!(members.len(), 2);
1383 assert_eq!(members[0].name, "A");
1384 assert_eq!(members[1].name, "B");
1385 }
1386 other => panic!("expected Enum, got {other:?}"),
1387 }
1388 }
1389 other => panic!("expected Array, got {other:?}"),
1390 }
1391 }
1392 _ => panic!("expected Compound"),
1393 }
1394 }
1395
1396 #[test]
1397 fn test_error_invalid_class() {
1398 let buf = build_dt_header(13, 1, [0, 0, 0], 4);
1399 let err = Datatype::parse(&buf).unwrap_err();
1400 assert_eq!(err, FormatError::InvalidDatatypeClass(13));
1401 }
1402
1403 #[test]
1404 fn test_error_truncated_data() {
1405 let buf = [0u8; 4]; let err = Datatype::parse(&buf).unwrap_err();
1407 match err {
1408 FormatError::UnexpectedEof { .. } => {}
1409 other => panic!("expected UnexpectedEof, got {other:?}"),
1410 }
1411 }
1412
1413 #[test]
1414 fn test_error_invalid_string_padding() {
1415 let buf = build_dt_header(3, 1, [0x03, 0, 0], 10); let err = Datatype::parse(&buf).unwrap_err();
1417 assert_eq!(err, FormatError::InvalidStringPadding(3));
1418 }
1419
1420 #[test]
1421 fn test_error_invalid_charset() {
1422 let buf = build_dt_header(3, 1, [0x20, 0, 0], 10); let err = Datatype::parse(&buf).unwrap_err();
1424 assert_eq!(err, FormatError::InvalidCharacterSet(2));
1425 }
1426
1427 #[test]
1428 fn test_error_invalid_reference_type() {
1429 let buf = build_dt_header(7, 1, [5, 0, 0], 8);
1430 let err = Datatype::parse(&buf).unwrap_err();
1431 assert_eq!(err, FormatError::InvalidReferenceType(5));
1432 }
1433
1434 #[test]
1435 fn serialize_parse_compound_roundtrip() {
1436 let dt = Datatype::Compound {
1437 size: 20,
1438 members: vec![
1439 CompoundMember {
1440 name: "x".to_string(),
1441 byte_offset: 0,
1442 datatype: Datatype::FloatingPoint {
1443 size: 8,
1444 byte_order: DatatypeByteOrder::LittleEndian,
1445 bit_offset: 0,
1446 bit_precision: 64,
1447 exponent_location: 52,
1448 exponent_size: 11,
1449 mantissa_location: 0,
1450 mantissa_size: 52,
1451 exponent_bias: 1023,
1452 },
1453 },
1454 CompoundMember {
1455 name: "y".to_string(),
1456 byte_offset: 8,
1457 datatype: Datatype::FloatingPoint {
1458 size: 8,
1459 byte_order: DatatypeByteOrder::LittleEndian,
1460 bit_offset: 0,
1461 bit_precision: 64,
1462 exponent_location: 52,
1463 exponent_size: 11,
1464 mantissa_location: 0,
1465 mantissa_size: 52,
1466 exponent_bias: 1023,
1467 },
1468 },
1469 CompoundMember {
1470 name: "id".to_string(),
1471 byte_offset: 16,
1472 datatype: Datatype::FixedPoint {
1473 size: 4,
1474 byte_order: DatatypeByteOrder::LittleEndian,
1475 signed: true,
1476 bit_offset: 0,
1477 bit_precision: 32,
1478 },
1479 },
1480 ],
1481 };
1482 let bytes = dt.serialize();
1483 let (parsed, _) = Datatype::parse(&bytes).unwrap();
1484 assert_eq!(parsed, dt);
1485 }
1486
1487 #[test]
1488 fn serialize_parse_enum_roundtrip() {
1489 let dt = Datatype::Enumeration {
1490 size: 4,
1491 base_type: Box::new(Datatype::FixedPoint {
1492 size: 4,
1493 byte_order: DatatypeByteOrder::LittleEndian,
1494 signed: true,
1495 bit_offset: 0,
1496 bit_precision: 32,
1497 }),
1498 members: vec![
1499 EnumMember {
1500 name: "RED".to_string(),
1501 value: 0i32.to_le_bytes().to_vec(),
1502 },
1503 EnumMember {
1504 name: "GREEN".to_string(),
1505 value: 1i32.to_le_bytes().to_vec(),
1506 },
1507 EnumMember {
1508 name: "BLUE".to_string(),
1509 value: 2i32.to_le_bytes().to_vec(),
1510 },
1511 ],
1512 };
1513 let bytes = dt.serialize();
1514 let (parsed, _) = Datatype::parse(&bytes).unwrap();
1515 assert_eq!(parsed, dt);
1516 }
1517
1518 fn enum_base_fp(size: u32, be: bool, signed: bool) -> Datatype {
1520 Datatype::FixedPoint {
1521 size,
1522 byte_order: if be {
1523 DatatypeByteOrder::BigEndian
1524 } else {
1525 DatatypeByteOrder::LittleEndian
1526 },
1527 signed,
1528 bit_offset: 0,
1529 #[expect(
1530 clippy::cast_possible_truncation,
1531 reason = "test builds byte-width base types; size*8 is well within u16"
1532 )]
1533 bit_precision: (size * 8) as u16,
1534 }
1535 }
1536
1537 fn make_enum(base: Datatype, members: &[(&str, i64)]) -> Datatype {
1540 let size = base.type_size();
1541 let width = size as usize;
1542 Datatype::Enumeration {
1543 size,
1544 base_type: Box::new(base),
1545 members: members
1546 .iter()
1547 .map(|(name, v)| EnumMember {
1548 name: (*name).to_string(),
1549 value: v.to_le_bytes()[..width].to_vec(),
1550 })
1551 .collect(),
1552 }
1553 }
1554
1555 #[test]
1556 fn serialize_parse_enum_base_type_variety() {
1557 for base in [
1560 enum_base_fp(1, false, false), enum_base_fp(2, true, true), enum_base_fp(8, false, true), ] {
1564 let dt = make_enum(base.clone(), &[("A", 0), ("B", 1), ("NEG", -1)]);
1565 let bytes = dt.serialize();
1566 let (parsed, consumed) = Datatype::parse(&bytes).unwrap();
1567 assert_eq!(parsed, dt, "round-trip failed for base {base:?}");
1568 assert_eq!(consumed, bytes.len());
1569 }
1570 }
1571
1572 #[test]
1573 fn serialize_parse_enum_large_member_count() {
1574 let owned: Vec<(String, i64)> = (0..300).map(|i| (format!("M{i}"), i)).collect();
1577 let members: Vec<(&str, i64)> = owned.iter().map(|(n, v)| (n.as_str(), *v)).collect();
1578 let dt = make_enum(enum_base_fp(4, false, true), &members);
1579 let bytes = dt.serialize();
1580 let (parsed, _) = Datatype::parse(&bytes).unwrap();
1581 assert_eq!(parsed, dt);
1582 match parsed {
1583 Datatype::Enumeration { members, .. } => {
1584 assert_eq!(members.len(), 300);
1585 assert_eq!(members[299].name, "M299");
1586 }
1587 other => panic!("expected Enumeration, got {other:?}"),
1588 }
1589 }
1590
1591 #[test]
1592 fn enum_value_width_is_not_validated_against_base_size() {
1593 let dt = Datatype::Enumeration {
1600 size: 1,
1601 base_type: Box::new(enum_base_fp(1, false, false)),
1602 members: vec![EnumMember {
1603 name: "X".to_string(),
1604 value: 5i32.to_le_bytes().to_vec(), }],
1606 };
1607 let bytes = dt.serialize();
1608 let (parsed, _) = Datatype::parse(&bytes).unwrap();
1609 assert_ne!(
1610 parsed, dt,
1611 "a value wider than the base silently truncates on parse"
1612 );
1613 match parsed {
1614 Datatype::Enumeration { members, .. } => assert_eq!(members[0].value, vec![5]),
1615 other => panic!("expected Enumeration, got {other:?}"),
1616 }
1617 }
1618
1619 #[test]
1620 fn serialize_parse_array_roundtrip() {
1621 let dt = Datatype::Array {
1622 base_type: Box::new(Datatype::FloatingPoint {
1623 size: 8,
1624 byte_order: DatatypeByteOrder::LittleEndian,
1625 bit_offset: 0,
1626 bit_precision: 64,
1627 exponent_location: 52,
1628 exponent_size: 11,
1629 mantissa_location: 0,
1630 mantissa_size: 52,
1631 exponent_bias: 1023,
1632 }),
1633 dimensions: vec![3],
1634 };
1635 let bytes = dt.serialize();
1636 let (parsed, _) = Datatype::parse(&bytes).unwrap();
1637 assert_eq!(parsed, dt);
1638 }
1639
1640 #[test]
1641 fn serialize_parse_time_roundtrip() {
1642 let dt = Datatype::Time {
1643 size: 8,
1644 byte_order: DatatypeByteOrder::LittleEndian,
1645 bit_precision: 64,
1646 };
1647 let bytes = dt.serialize();
1648 let (parsed, consumed) = Datatype::parse(&bytes).unwrap();
1649 assert_eq!(parsed, dt);
1650 assert_eq!(consumed, bytes.len());
1651 }
1652
1653 #[test]
1654 fn serialize_parse_bitfield_roundtrip() {
1655 for byte_order in [
1656 DatatypeByteOrder::LittleEndian,
1657 DatatypeByteOrder::BigEndian,
1658 ] {
1659 let dt = Datatype::BitField {
1660 size: 4,
1661 byte_order,
1662 bit_offset: 3,
1663 bit_precision: 17,
1664 };
1665 let bytes = dt.serialize();
1666 let (parsed, consumed) = Datatype::parse(&bytes).unwrap();
1667 assert_eq!(parsed, dt);
1668 assert_eq!(consumed, bytes.len());
1669 }
1670 }
1671
1672 #[test]
1673 fn serialize_parse_opaque_roundtrip() {
1674 for tag in [
1677 b"abc".to_vec(), b"12345678".to_vec(), b"sensor-id\0".to_vec(), ] {
1681 let dt = Datatype::Opaque { size: 16, tag };
1682 let bytes = dt.serialize();
1683 assert_eq!((bytes.len() - 8) % 8, 0);
1686 let (parsed, consumed) = Datatype::parse(&bytes).unwrap();
1687 assert_eq!(parsed, dt);
1688 assert_eq!(consumed, bytes.len());
1689 }
1690 }
1691
1692 #[test]
1693 fn test_type_size() {
1694 let dt = Datatype::FixedPoint {
1695 size: 4,
1696 byte_order: DatatypeByteOrder::LittleEndian,
1697 signed: true,
1698 bit_offset: 0,
1699 bit_precision: 32,
1700 };
1701 assert_eq!(dt.type_size(), 4);
1702
1703 let dt = Datatype::Array {
1704 base_type: Box::new(Datatype::FixedPoint {
1705 size: 4,
1706 byte_order: DatatypeByteOrder::LittleEndian,
1707 signed: true,
1708 bit_offset: 0,
1709 bit_precision: 32,
1710 }),
1711 dimensions: vec![3, 4],
1712 };
1713 assert_eq!(dt.type_size(), 48);
1714 }
1715}