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