1pub mod kinds;
34
35use alloc::boxed::Box;
36use alloc::vec::Vec;
37
38use zerodds_cdr::{BufferReader, BufferWriter, DecodeError, EncodeError, Endianness};
39
40use self::kinds::{
41 EK_COMPLETE, EK_MINIMAL, EQUIVALENCE_HASH_LEN, TI_PLAIN_ARRAY_LARGE, TI_PLAIN_ARRAY_SMALL,
42 TI_PLAIN_MAP_LARGE, TI_PLAIN_MAP_SMALL, TI_PLAIN_SEQUENCE_LARGE, TI_PLAIN_SEQUENCE_SMALL,
43 TI_STRING8_LARGE, TI_STRING8_SMALL, TI_STRING16_LARGE, TI_STRING16_SMALL,
44 TI_STRONGLY_CONNECTED_COMPONENT, TK_BOOLEAN, TK_BYTE, TK_CHAR8, TK_CHAR16, TK_FLOAT32,
45 TK_FLOAT64, TK_FLOAT128, TK_INT8, TK_INT16, TK_INT32, TK_INT64, TK_NONE, TK_UINT8, TK_UINT16,
46 TK_UINT32, TK_UINT64,
47};
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
54pub struct EquivalenceHash(pub [u8; EQUIVALENCE_HASH_LEN]);
55
56impl EquivalenceHash {
57 pub const ZERO: Self = Self([0; EQUIVALENCE_HASH_LEN]);
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
64pub struct CollectionElementFlag(pub u16);
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub enum EquivalenceKind {
73 None,
75 Minimal,
77 Complete,
79 Both,
81}
82
83impl EquivalenceKind {
84 #[must_use]
86 pub const fn to_u8(self) -> u8 {
87 match self {
88 Self::None => 0,
89 Self::Minimal => EK_MINIMAL,
90 Self::Complete => EK_COMPLETE,
91 Self::Both => 0xF3,
92 }
93 }
94
95 #[must_use]
97 pub const fn from_u8(v: u8) -> Self {
98 match v {
99 EK_MINIMAL => Self::Minimal,
100 EK_COMPLETE => Self::Complete,
101 0xF3 => Self::Both,
102 _ => Self::None,
103 }
104 }
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
109pub struct PlainCollectionHeader {
110 pub equiv_kind: u8,
112 pub element_flags: CollectionElementFlag,
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118pub struct StronglyConnectedComponentId {
119 pub hash: EquivalenceHash,
121 pub scc_length: i32,
123 pub scc_index: i32,
125}
126
127#[derive(Debug, Clone, PartialEq, Eq, Default)]
134#[non_exhaustive]
135pub enum TypeIdentifier {
136 #[default]
138 None,
139 Primitive(PrimitiveKind),
141 String8Small {
143 bound: u8,
145 },
146 String8Large {
148 bound: u32,
150 },
151 String16Small {
153 bound: u8,
155 },
156 String16Large {
158 bound: u32,
160 },
161 PlainSequenceSmall {
163 header: PlainCollectionHeader,
165 bound: u8,
167 element: Box<TypeIdentifier>,
169 },
170 PlainSequenceLarge {
172 header: PlainCollectionHeader,
174 bound: u32,
176 element: Box<TypeIdentifier>,
178 },
179 PlainArraySmall {
181 header: PlainCollectionHeader,
183 array_bounds: Vec<u8>,
185 element: Box<TypeIdentifier>,
187 },
188 PlainArrayLarge {
190 header: PlainCollectionHeader,
192 array_bounds: Vec<u32>,
194 element: Box<TypeIdentifier>,
196 },
197 PlainMapSmall {
199 header: PlainCollectionHeader,
201 bound: u8,
203 element: Box<TypeIdentifier>,
205 key_flags: CollectionElementFlag,
207 key: Box<TypeIdentifier>,
209 },
210 PlainMapLarge {
212 header: PlainCollectionHeader,
214 bound: u32,
216 element: Box<TypeIdentifier>,
218 key_flags: CollectionElementFlag,
220 key: Box<TypeIdentifier>,
222 },
223 StronglyConnectedComponent(StronglyConnectedComponentId),
225 EquivalenceHashMinimal(EquivalenceHash),
227 EquivalenceHashComplete(EquivalenceHash),
229 Unknown(u8),
231}
232
233#[derive(Debug, Clone, Copy, PartialEq, Eq)]
235pub enum PrimitiveKind {
236 Boolean,
238 Byte,
240 Int8,
242 Int16,
244 Int32,
246 Int64,
248 UInt8,
250 UInt16,
252 UInt32,
254 UInt64,
256 Float32,
258 Float64,
260 Float128,
262 Char8,
264 Char16,
266}
267
268impl PrimitiveKind {
269 #[must_use]
271 pub const fn to_u8(self) -> u8 {
272 match self {
273 Self::Boolean => TK_BOOLEAN,
274 Self::Byte => TK_BYTE,
275 Self::Int8 => TK_INT8,
276 Self::Int16 => TK_INT16,
277 Self::Int32 => TK_INT32,
278 Self::Int64 => TK_INT64,
279 Self::UInt8 => TK_UINT8,
280 Self::UInt16 => TK_UINT16,
281 Self::UInt32 => TK_UINT32,
282 Self::UInt64 => TK_UINT64,
283 Self::Float32 => TK_FLOAT32,
284 Self::Float64 => TK_FLOAT64,
285 Self::Float128 => TK_FLOAT128,
286 Self::Char8 => TK_CHAR8,
287 Self::Char16 => TK_CHAR16,
288 }
289 }
290
291 #[must_use]
293 pub const fn from_u8(v: u8) -> Option<Self> {
294 Some(match v {
295 TK_BOOLEAN => Self::Boolean,
296 TK_BYTE => Self::Byte,
297 TK_INT8 => Self::Int8,
298 TK_INT16 => Self::Int16,
299 TK_INT32 => Self::Int32,
300 TK_INT64 => Self::Int64,
301 TK_UINT8 => Self::UInt8,
302 TK_UINT16 => Self::UInt16,
303 TK_UINT32 => Self::UInt32,
304 TK_UINT64 => Self::UInt64,
305 TK_FLOAT32 => Self::Float32,
306 TK_FLOAT64 => Self::Float64,
307 TK_FLOAT128 => Self::Float128,
308 TK_CHAR8 => Self::Char8,
309 TK_CHAR16 => Self::Char16,
310 _ => return None,
311 })
312 }
313}
314
315impl TypeIdentifier {
320 pub fn encode_into(&self, w: &mut BufferWriter) -> Result<(), EncodeError> {
328 let d = self.discriminator();
329 w.write_u8(d)?;
330 match self {
331 Self::None | Self::Primitive(_) | Self::Unknown(_) => Ok(()),
332 Self::String8Small { bound } | Self::String16Small { bound } => w.write_u8(*bound),
333 Self::String8Large { bound } | Self::String16Large { bound } => w.write_u32(*bound),
334 Self::PlainSequenceSmall {
335 header,
336 bound,
337 element,
338 } => {
339 encode_collection_header(w, *header)?;
340 w.write_u8(*bound)?;
341 element.encode_into(w)
342 }
343 Self::PlainSequenceLarge {
344 header,
345 bound,
346 element,
347 } => {
348 encode_collection_header(w, *header)?;
349 w.write_u32(*bound)?;
350 element.encode_into(w)
351 }
352 Self::PlainArraySmall {
353 header,
354 array_bounds,
355 element,
356 } => {
357 encode_collection_header(w, *header)?;
358 let len = u32::try_from(array_bounds.len()).map_err(|_| {
360 EncodeError::ValueOutOfRange {
361 message: "plain array dimensions length exceeds u32::MAX",
362 }
363 })?;
364 w.write_u32(len)?;
365 w.write_bytes(array_bounds)?;
366 element.encode_into(w)
367 }
368 Self::PlainArrayLarge {
369 header,
370 array_bounds,
371 element,
372 } => {
373 encode_collection_header(w, *header)?;
374 let len = u32::try_from(array_bounds.len()).map_err(|_| {
375 EncodeError::ValueOutOfRange {
376 message: "plain array dimensions length exceeds u32::MAX",
377 }
378 })?;
379 w.write_u32(len)?;
380 for dim in array_bounds {
381 w.write_u32(*dim)?;
382 }
383 element.encode_into(w)
384 }
385 Self::PlainMapSmall {
386 header,
387 bound,
388 element,
389 key_flags,
390 key,
391 } => {
392 encode_collection_header(w, *header)?;
393 w.write_u8(*bound)?;
394 element.encode_into(w)?;
395 w.write_u16(key_flags.0)?;
396 key.encode_into(w)
397 }
398 Self::PlainMapLarge {
399 header,
400 bound,
401 element,
402 key_flags,
403 key,
404 } => {
405 encode_collection_header(w, *header)?;
406 w.write_u32(*bound)?;
407 element.encode_into(w)?;
408 w.write_u16(key_flags.0)?;
409 key.encode_into(w)
410 }
411 Self::StronglyConnectedComponent(scc) => {
412 if scc.scc_length < 0 || scc.scc_index < 0 || scc.scc_index >= scc.scc_length {
416 return Err(EncodeError::ValueOutOfRange {
417 message: "SCC scc_length/scc_index invalid",
418 });
419 }
420 w.write_bytes(&scc.hash.0)?;
421 w.write_u32(scc.scc_length as u32)?;
422 w.write_u32(scc.scc_index as u32)
423 }
424 Self::EquivalenceHashMinimal(h) | Self::EquivalenceHashComplete(h) => {
425 w.write_bytes(&h.0)
426 }
427 }
428 }
429
430 pub const MAX_DECODE_DEPTH: usize = 16;
434
435 pub fn decode_from(r: &mut BufferReader<'_>) -> Result<Self, DecodeError> {
443 Self::decode_with_depth(r, 0)
444 }
445
446 fn decode_with_depth(r: &mut BufferReader<'_>, depth: usize) -> Result<Self, DecodeError> {
447 if depth >= Self::MAX_DECODE_DEPTH {
448 return Err(DecodeError::LengthExceeded {
449 announced: Self::MAX_DECODE_DEPTH,
450 remaining: 0,
451 offset: r.position(),
452 });
453 }
454 let d = r.read_u8()?;
455 Ok(match d {
456 TK_NONE => Self::None,
457 TK_BOOLEAN | TK_BYTE | TK_INT8 | TK_INT16 | TK_INT32 | TK_INT64 | TK_UINT8
458 | TK_UINT16 | TK_UINT32 | TK_UINT64 | TK_FLOAT32 | TK_FLOAT64 | TK_FLOAT128
459 | TK_CHAR8 | TK_CHAR16 => {
460 match PrimitiveKind::from_u8(d) {
465 Some(p) => Self::Primitive(p),
466 None => Self::Unknown(d),
467 }
468 }
469 TI_STRING8_SMALL => Self::String8Small {
470 bound: r.read_u8()?,
471 },
472 TI_STRING8_LARGE => Self::String8Large {
473 bound: r.read_u32()?,
474 },
475 TI_STRING16_SMALL => Self::String16Small {
476 bound: r.read_u8()?,
477 },
478 TI_STRING16_LARGE => Self::String16Large {
479 bound: r.read_u32()?,
480 },
481 TI_PLAIN_SEQUENCE_SMALL => {
482 let header = decode_collection_header(r)?;
483 let bound = r.read_u8()?;
484 let element = Box::new(Self::decode_with_depth(r, depth + 1)?);
485 Self::PlainSequenceSmall {
486 header,
487 bound,
488 element,
489 }
490 }
491 TI_PLAIN_SEQUENCE_LARGE => {
492 let header = decode_collection_header(r)?;
493 let bound = r.read_u32()?;
494 let element = Box::new(Self::decode_with_depth(r, depth + 1)?);
495 Self::PlainSequenceLarge {
496 header,
497 bound,
498 element,
499 }
500 }
501 TI_PLAIN_ARRAY_SMALL => {
502 let header = decode_collection_header(r)?;
503 let n = r.read_u32()? as usize;
504 let array_bounds = r.read_bytes(n)?.to_vec();
505 let element = Box::new(Self::decode_with_depth(r, depth + 1)?);
506 Self::PlainArraySmall {
507 header,
508 array_bounds,
509 element,
510 }
511 }
512 TI_PLAIN_ARRAY_LARGE => {
513 let header = decode_collection_header(r)?;
514 let n = r.read_u32()? as usize;
515 let cap = crate::type_object::common::safe_capacity(n, 4, r.remaining());
516 let mut array_bounds = Vec::with_capacity(cap);
517 for _ in 0..n {
518 array_bounds.push(r.read_u32()?);
519 }
520 let element = Box::new(Self::decode_with_depth(r, depth + 1)?);
521 Self::PlainArrayLarge {
522 header,
523 array_bounds,
524 element,
525 }
526 }
527 TI_PLAIN_MAP_SMALL => {
528 let header = decode_collection_header(r)?;
529 let bound = r.read_u8()?;
530 let element = Box::new(Self::decode_with_depth(r, depth + 1)?);
531 let key_flags = CollectionElementFlag(r.read_u16()?);
532 let key = Box::new(Self::decode_with_depth(r, depth + 1)?);
533 Self::PlainMapSmall {
534 header,
535 bound,
536 element,
537 key_flags,
538 key,
539 }
540 }
541 TI_PLAIN_MAP_LARGE => {
542 let header = decode_collection_header(r)?;
543 let bound = r.read_u32()?;
544 let element = Box::new(Self::decode_with_depth(r, depth + 1)?);
545 let key_flags = CollectionElementFlag(r.read_u16()?);
546 let key = Box::new(Self::decode_with_depth(r, depth + 1)?);
547 Self::PlainMapLarge {
548 header,
549 bound,
550 element,
551 key_flags,
552 key,
553 }
554 }
555 TI_STRONGLY_CONNECTED_COMPONENT => {
556 let hash_bytes = r.read_bytes(EQUIVALENCE_HASH_LEN)?;
557 let Ok(h): Result<[u8; EQUIVALENCE_HASH_LEN], _> = hash_bytes.try_into() else {
558 return Err(DecodeError::UnexpectedEof {
559 needed: EQUIVALENCE_HASH_LEN,
560 offset: 0,
561 });
562 };
563 let scc_length = r.read_u32()? as i32;
564 let scc_index = r.read_u32()? as i32;
565 Self::StronglyConnectedComponent(StronglyConnectedComponentId {
566 hash: EquivalenceHash(h),
567 scc_length,
568 scc_index,
569 })
570 }
571 EK_MINIMAL | EK_COMPLETE => {
572 let hash_bytes = r.read_bytes(EQUIVALENCE_HASH_LEN)?;
573 let Ok(h): Result<[u8; EQUIVALENCE_HASH_LEN], _> = hash_bytes.try_into() else {
574 return Err(DecodeError::UnexpectedEof {
575 needed: EQUIVALENCE_HASH_LEN,
576 offset: 0,
577 });
578 };
579 if d == EK_MINIMAL {
580 Self::EquivalenceHashMinimal(EquivalenceHash(h))
581 } else {
582 Self::EquivalenceHashComplete(EquivalenceHash(h))
583 }
584 }
585 other => Self::Unknown(other),
586 })
587 }
588
589 #[must_use]
591 pub const fn discriminator(&self) -> u8 {
592 match self {
593 Self::None => TK_NONE,
594 Self::Primitive(p) => p.to_u8(),
595 Self::String8Small { .. } => TI_STRING8_SMALL,
596 Self::String8Large { .. } => TI_STRING8_LARGE,
597 Self::String16Small { .. } => TI_STRING16_SMALL,
598 Self::String16Large { .. } => TI_STRING16_LARGE,
599 Self::PlainSequenceSmall { .. } => TI_PLAIN_SEQUENCE_SMALL,
600 Self::PlainSequenceLarge { .. } => TI_PLAIN_SEQUENCE_LARGE,
601 Self::PlainArraySmall { .. } => TI_PLAIN_ARRAY_SMALL,
602 Self::PlainArrayLarge { .. } => TI_PLAIN_ARRAY_LARGE,
603 Self::PlainMapSmall { .. } => TI_PLAIN_MAP_SMALL,
604 Self::PlainMapLarge { .. } => TI_PLAIN_MAP_LARGE,
605 Self::StronglyConnectedComponent(_) => TI_STRONGLY_CONNECTED_COMPONENT,
606 Self::EquivalenceHashMinimal(_) => EK_MINIMAL,
607 Self::EquivalenceHashComplete(_) => EK_COMPLETE,
608 Self::Unknown(d) => *d,
609 }
610 }
611
612 pub fn to_bytes_le(&self) -> Result<Vec<u8>, EncodeError> {
617 let mut w = BufferWriter::new(Endianness::Little);
618 self.encode_into(&mut w)?;
619 Ok(w.into_bytes())
620 }
621
622 pub fn from_bytes_le(bytes: &[u8]) -> Result<Self, DecodeError> {
627 let mut r = BufferReader::new(bytes, Endianness::Little);
628 Self::decode_from(&mut r)
629 }
630}
631
632fn encode_collection_header(
633 w: &mut BufferWriter,
634 header: PlainCollectionHeader,
635) -> Result<(), EncodeError> {
636 w.write_u8(header.equiv_kind)?;
637 w.write_u16(header.element_flags.0)
638}
639
640fn decode_collection_header(
641 r: &mut BufferReader<'_>,
642) -> Result<PlainCollectionHeader, DecodeError> {
643 let equiv_kind = r.read_u8()?;
644 let element_flags = CollectionElementFlag(r.read_u16()?);
645 Ok(PlainCollectionHeader {
646 equiv_kind,
647 element_flags,
648 })
649}
650
651#[cfg(test)]
656#[allow(clippy::unwrap_used, clippy::panic)]
657mod tests {
658 use super::*;
659
660 fn roundtrip(ti: TypeIdentifier) {
661 let bytes = ti.to_bytes_le().unwrap();
662 let decoded = TypeIdentifier::from_bytes_le(&bytes).unwrap();
663 assert_eq!(ti, decoded);
664 }
665
666 #[test]
667 fn primitive_none_roundtrips() {
668 roundtrip(TypeIdentifier::None);
669 }
670
671 #[test]
672 fn all_primitives_roundtrip() {
673 for p in [
674 PrimitiveKind::Boolean,
675 PrimitiveKind::Byte,
676 PrimitiveKind::Int8,
677 PrimitiveKind::Int16,
678 PrimitiveKind::Int32,
679 PrimitiveKind::Int64,
680 PrimitiveKind::UInt8,
681 PrimitiveKind::UInt16,
682 PrimitiveKind::UInt32,
683 PrimitiveKind::UInt64,
684 PrimitiveKind::Float32,
685 PrimitiveKind::Float64,
686 PrimitiveKind::Float128,
687 PrimitiveKind::Char8,
688 PrimitiveKind::Char16,
689 ] {
690 roundtrip(TypeIdentifier::Primitive(p));
691 }
692 }
693
694 #[test]
695 fn primitive_int32_discriminator_is_spec_value() {
696 let ti = TypeIdentifier::Primitive(PrimitiveKind::Int32);
697 let bytes = ti.to_bytes_le().unwrap();
698 assert_eq!(bytes, [TK_INT32]);
699 }
700
701 #[test]
702 fn string8_small_roundtrips() {
703 roundtrip(TypeIdentifier::String8Small { bound: 64 });
704 roundtrip(TypeIdentifier::String8Small { bound: 0 }); }
706
707 #[test]
708 fn string8_large_roundtrips() {
709 roundtrip(TypeIdentifier::String8Large { bound: 65_536 });
710 }
711
712 #[test]
713 fn string16_small_and_large_roundtrip() {
714 roundtrip(TypeIdentifier::String16Small { bound: 32 });
715 roundtrip(TypeIdentifier::String16Large { bound: 100_000 });
716 }
717
718 #[test]
719 fn plain_sequence_of_int32_roundtrips() {
720 let ti = TypeIdentifier::PlainSequenceSmall {
721 header: PlainCollectionHeader {
722 equiv_kind: 0,
723 element_flags: CollectionElementFlag(0),
724 },
725 bound: 10,
726 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
727 };
728 roundtrip(ti);
729 }
730
731 #[test]
732 fn plain_sequence_large_of_string_roundtrips() {
733 let ti = TypeIdentifier::PlainSequenceLarge {
734 header: PlainCollectionHeader {
735 equiv_kind: 0,
736 element_flags: CollectionElementFlag(0),
737 },
738 bound: 1_000_000,
739 element: Box::new(TypeIdentifier::String8Small { bound: 255 }),
740 };
741 roundtrip(ti);
742 }
743
744 #[test]
745 fn plain_array_small_3d_roundtrips() {
746 let ti = TypeIdentifier::PlainArraySmall {
747 header: PlainCollectionHeader::default(),
748 array_bounds: alloc::vec![3, 4, 5],
749 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Float64)),
750 };
751 roundtrip(ti);
752 }
753
754 #[test]
755 fn plain_array_large_roundtrips() {
756 let ti = TypeIdentifier::PlainArrayLarge {
757 header: PlainCollectionHeader::default(),
758 array_bounds: alloc::vec![1_000, 2_000],
759 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Byte)),
760 };
761 roundtrip(ti);
762 }
763
764 #[test]
765 fn plain_map_small_roundtrips() {
766 let ti = TypeIdentifier::PlainMapSmall {
767 header: PlainCollectionHeader::default(),
768 bound: 100,
769 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int64)),
770 key_flags: CollectionElementFlag(0),
771 key: Box::new(TypeIdentifier::String8Small { bound: 64 }),
772 };
773 roundtrip(ti);
774 }
775
776 #[test]
777 fn equivalence_hash_minimal_roundtrips() {
778 let hash = EquivalenceHash([
779 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
780 ]);
781 roundtrip(TypeIdentifier::EquivalenceHashMinimal(hash));
782 roundtrip(TypeIdentifier::EquivalenceHashComplete(hash));
783 }
784
785 #[test]
786 fn equivalence_hash_wire_is_discriminator_plus_14_bytes() {
787 let hash = EquivalenceHash([0xAA; 14]);
788 let bytes = TypeIdentifier::EquivalenceHashMinimal(hash)
789 .to_bytes_le()
790 .unwrap();
791 assert_eq!(bytes.len(), 15);
792 assert_eq!(bytes[0], EK_MINIMAL);
793 assert_eq!(&bytes[1..], &[0xAA; 14]);
794 }
795
796 #[test]
797 fn nested_sequence_of_sequence_roundtrips() {
798 let inner = TypeIdentifier::PlainSequenceSmall {
799 header: PlainCollectionHeader::default(),
800 bound: 5,
801 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int16)),
802 };
803 let outer = TypeIdentifier::PlainSequenceSmall {
804 header: PlainCollectionHeader::default(),
805 bound: 3,
806 element: Box::new(inner),
807 };
808 roundtrip(outer);
809 }
810
811 #[test]
812 fn strongly_connected_component_roundtrips() {
813 let scc = TypeIdentifier::StronglyConnectedComponent(StronglyConnectedComponentId {
814 hash: EquivalenceHash([0x11; 14]),
815 scc_length: 5,
816 scc_index: 2,
817 });
818 roundtrip(scc);
819 }
820
821 #[test]
822 fn scc_encode_rejects_negative_values() {
823 let scc = TypeIdentifier::StronglyConnectedComponent(StronglyConnectedComponentId {
824 hash: EquivalenceHash::ZERO,
825 scc_length: -1,
826 scc_index: 0,
827 });
828 assert!(scc.to_bytes_le().is_err());
829 }
830
831 #[test]
832 fn scc_encode_rejects_index_out_of_bounds() {
833 let scc = TypeIdentifier::StronglyConnectedComponent(StronglyConnectedComponentId {
834 hash: EquivalenceHash::ZERO,
835 scc_length: 3,
836 scc_index: 3, });
838 assert!(scc.to_bytes_le().is_err());
839 }
840
841 #[test]
842 fn max_decode_depth_constant_is_reasonable() {
843 const _ASSERT_MIN: usize = TypeIdentifier::MAX_DECODE_DEPTH - 4;
847 const _ASSERT_MAX: usize = 64 - TypeIdentifier::MAX_DECODE_DEPTH;
848 }
849
850 #[test]
851 fn deeply_nested_but_bounded_sequence_decodes_ok() {
852 let l1 = TypeIdentifier::PlainSequenceSmall {
854 header: PlainCollectionHeader::default(),
855 bound: 5,
856 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
857 };
858 let l2 = TypeIdentifier::PlainSequenceSmall {
859 header: PlainCollectionHeader::default(),
860 bound: 5,
861 element: Box::new(l1),
862 };
863 let l3 = TypeIdentifier::PlainSequenceSmall {
864 header: PlainCollectionHeader::default(),
865 bound: 5,
866 element: Box::new(l2),
867 };
868 roundtrip(l3);
869 }
870
871 #[test]
872 fn unknown_discriminator_preserved_in_decode() {
873 let bytes = [0xC7];
874 let decoded = TypeIdentifier::from_bytes_le(&bytes).unwrap();
875 assert_eq!(decoded, TypeIdentifier::Unknown(0xC7));
876 }
877
878 #[test]
881 fn plain_sequence_large_with_u32_max_bound_roundtrips() {
882 let ti = TypeIdentifier::PlainSequenceLarge {
883 header: PlainCollectionHeader::default(),
884 bound: u32::MAX,
885 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Byte)),
886 };
887 roundtrip(ti);
888 }
889
890 #[test]
891 fn plain_array_large_with_many_dimensions_roundtrips() {
892 let ti = TypeIdentifier::PlainArrayLarge {
893 header: PlainCollectionHeader::default(),
894 array_bounds: alloc::vec![
895 1_000, 2_000, 3_000, 4_000, 5_000, 6_000, 7_000, 8_000, 9_000, 10_000, 11_000,
896 12_000,
897 ],
898 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Float32)),
899 };
900 roundtrip(ti);
901 }
902
903 #[test]
904 fn plain_array_small_with_single_dimension_roundtrips() {
905 let ti = TypeIdentifier::PlainArraySmall {
906 header: PlainCollectionHeader::default(),
907 array_bounds: alloc::vec![250],
908 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
909 };
910 roundtrip(ti);
911 }
912
913 #[test]
914 fn plain_map_large_with_nested_map_value_roundtrips() {
915 let inner = TypeIdentifier::PlainMapSmall {
916 header: PlainCollectionHeader::default(),
917 bound: 10,
918 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
919 key_flags: CollectionElementFlag(0),
920 key: Box::new(TypeIdentifier::String8Small { bound: 8 }),
921 };
922 let outer = TypeIdentifier::PlainMapLarge {
923 header: PlainCollectionHeader::default(),
924 bound: 5_000,
925 element: Box::new(inner),
926 key_flags: CollectionElementFlag(0),
927 key: Box::new(TypeIdentifier::String8Small { bound: 16 }),
928 };
929 roundtrip(outer);
930 }
931
932 #[test]
933 fn strongly_connected_component_large_scc_length_and_index() {
934 let scc = TypeIdentifier::StronglyConnectedComponent(StronglyConnectedComponentId {
935 hash: EquivalenceHash([0x5A; 14]),
936 scc_length: i32::MAX,
937 scc_index: i32::MAX - 1,
938 });
939 roundtrip(scc);
940 }
941
942 #[test]
943 fn unknown_discriminators_cover_multiple_bytes() {
944 for d in [0x12_u8, 0x1F, 0x40, 0x50, 0xC7, 0xFE, 0xFF] {
948 let decoded = TypeIdentifier::from_bytes_le(&[d]).unwrap();
949 assert_eq!(decoded, TypeIdentifier::Unknown(d));
950 let re = decoded.to_bytes_le().unwrap();
952 assert_eq!(re, alloc::vec![d]);
953 }
954 }
955
956 #[test]
957 fn encode_first_byte_is_always_discriminator() {
958 let samples: alloc::vec::Vec<TypeIdentifier> = alloc::vec![
959 TypeIdentifier::None,
960 TypeIdentifier::Primitive(PrimitiveKind::Int32),
961 TypeIdentifier::String8Small { bound: 8 },
962 TypeIdentifier::String8Large { bound: 10_000 },
963 TypeIdentifier::String16Small { bound: 8 },
964 TypeIdentifier::String16Large { bound: 10_000 },
965 TypeIdentifier::PlainSequenceSmall {
966 header: PlainCollectionHeader::default(),
967 bound: 1,
968 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
969 },
970 TypeIdentifier::PlainSequenceLarge {
971 header: PlainCollectionHeader::default(),
972 bound: 300,
973 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
974 },
975 TypeIdentifier::PlainArraySmall {
976 header: PlainCollectionHeader::default(),
977 array_bounds: alloc::vec![2, 3],
978 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
979 },
980 TypeIdentifier::PlainArrayLarge {
981 header: PlainCollectionHeader::default(),
982 array_bounds: alloc::vec![500, 500],
983 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
984 },
985 TypeIdentifier::PlainMapSmall {
986 header: PlainCollectionHeader::default(),
987 bound: 1,
988 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
989 key_flags: CollectionElementFlag(0),
990 key: Box::new(TypeIdentifier::String8Small { bound: 1 }),
991 },
992 TypeIdentifier::PlainMapLarge {
993 header: PlainCollectionHeader::default(),
994 bound: 1_000,
995 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
996 key_flags: CollectionElementFlag(0),
997 key: Box::new(TypeIdentifier::String8Small { bound: 1 }),
998 },
999 TypeIdentifier::StronglyConnectedComponent(StronglyConnectedComponentId {
1000 hash: EquivalenceHash([0; 14]),
1001 scc_length: 1,
1002 scc_index: 0,
1003 }),
1004 TypeIdentifier::EquivalenceHashMinimal(EquivalenceHash([0x11; 14])),
1005 TypeIdentifier::EquivalenceHashComplete(EquivalenceHash([0x22; 14])),
1006 TypeIdentifier::Unknown(0x77),
1007 ];
1008 for ti in samples {
1009 let bytes = ti.to_bytes_le().unwrap();
1010 assert!(!bytes.is_empty());
1011 assert_eq!(bytes[0], ti.discriminator());
1012 }
1013 }
1014
1015 #[test]
1016 fn primitive_kind_from_u8_rejects_unknown() {
1017 assert!(PrimitiveKind::from_u8(0xC7).is_none());
1018 assert!(PrimitiveKind::from_u8(0x00).is_none() || PrimitiveKind::from_u8(0x00).is_some());
1019 }
1020
1021 #[test]
1022 fn primitive_kind_roundtrip_via_u8() {
1023 for p in [
1024 PrimitiveKind::Boolean,
1025 PrimitiveKind::Byte,
1026 PrimitiveKind::Int8,
1027 PrimitiveKind::Int16,
1028 PrimitiveKind::Int32,
1029 PrimitiveKind::Int64,
1030 PrimitiveKind::UInt8,
1031 PrimitiveKind::UInt16,
1032 PrimitiveKind::UInt32,
1033 PrimitiveKind::UInt64,
1034 PrimitiveKind::Float32,
1035 PrimitiveKind::Float64,
1036 PrimitiveKind::Float128,
1037 PrimitiveKind::Char8,
1038 PrimitiveKind::Char16,
1039 ] {
1040 assert_eq!(PrimitiveKind::from_u8(p.to_u8()), Some(p));
1041 }
1042 }
1043
1044 #[test]
1045 fn equivalence_kind_roundtrip() {
1046 for k in [
1047 EquivalenceKind::None,
1048 EquivalenceKind::Minimal,
1049 EquivalenceKind::Complete,
1050 EquivalenceKind::Both,
1051 ] {
1052 let encoded = k.to_u8();
1053 let decoded = EquivalenceKind::from_u8(encoded);
1054 if k == EquivalenceKind::None {
1057 assert_eq!(decoded, EquivalenceKind::None);
1058 } else {
1059 assert_eq!(decoded, k);
1060 }
1061 }
1062 }
1063
1064 #[test]
1065 fn equivalence_kind_from_u8_unknown_is_none() {
1066 assert_eq!(EquivalenceKind::from_u8(0xAB), EquivalenceKind::None);
1067 }
1068
1069 #[test]
1070 fn equivalence_hash_zero_constant_is_zeroes() {
1071 assert_eq!(EquivalenceHash::ZERO.0, [0u8; 14]);
1072 }
1073
1074 #[test]
1075 fn unknown_discriminator_encodes_exactly_one_byte() {
1076 let ti = TypeIdentifier::Unknown(0xC7);
1077 let bytes = ti.to_bytes_le().unwrap();
1078 assert_eq!(bytes, alloc::vec![0xC7]);
1079 }
1080}