Skip to main content

zerodds_types/type_identifier/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! XTypes 1.3 TypeIdentifier (Spec §7.3.4.2).
4//!
5//! TypeIdentifier ist eine CDR-Union mit `octet`-Discriminator. Er
6//! identifiziert einen Typ entweder **direkt** (Primitives, plain
7//! Collections) oder **indirekt** ueber einen 14-byte SHA-256-Hash auf
8//! das serialisierte TypeObject (EK_MINIMAL / EK_COMPLETE).
9//!
10//! # Wire-Encoding (XCDR2 LE)
11//!
12//! ```text
13//! TypeIdentifier {
14//!     octet _d;            // 1 byte discriminator
15//!     // body abhaengig von _d:
16//!     // TK_NONE..TK_CHAR16:          no body
17//!     // TI_STRING8_SMALL/LE_SMALL:   { octet bound; }
18//!     // TI_STRING8_LARGE/LE_LARGE:   { uint32 bound; }  // 4-byte aligned
19//!     // TI_PLAIN_SEQUENCE_SMALL:     { PlainCollectionHeader; octet bound; @external TypeIdentifier elem; }
20//!     // TI_PLAIN_SEQUENCE_LARGE:     { PlainCollectionHeader; uint32 bound; @external TypeIdentifier elem; }
21//!     // TI_PLAIN_ARRAY_SMALL:        { PlainCollectionHeader; seq<octet,5> dims; @external TypeIdentifier elem; }
22//!     // TI_PLAIN_ARRAY_LARGE:        { PlainCollectionHeader; seq<uint32,5> dims; @external TypeIdentifier elem; }
23//!     // TI_PLAIN_MAP_SMALL:          { PlainCollectionHeader; octet bound;
24//!     //                                @external TypeIdentifier elem;
25//!     //                                CollectionElementFlag key_flags;
26//!     //                                @external TypeIdentifier key; }
27//!     // TI_PLAIN_MAP_LARGE:          { ... uint32 bound ... }
28//!     // TI_STRONGLY_CONNECTED_COMPONENT: { SCC_ID scc_id; }
29//!     // EK_MINIMAL / EK_COMPLETE:    { octet hash[14]; }
30//! }
31//! ```
32
33pub 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/// 14-byte SHA256-Hash ueber ein serialisiertes TypeObject.
50///
51/// Spec §7.3.1.2: der Hash wird aus der **ersten 14 Bytes** der SHA-256
52/// ueber das XCDR2-serialisierte TypeObject gebildet.
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
54pub struct EquivalenceHash(pub [u8; EQUIVALENCE_HASH_LEN]);
55
56impl EquivalenceHash {
57    /// Null-Hash (Platzhalter).
58    pub const ZERO: Self = Self([0; EQUIVALENCE_HASH_LEN]);
59}
60
61/// Collection-Element-Flags (§7.3.4.7.1). 16-bit Bitmaske mit nur einem
62/// relevanten Bit (TRY_CONSTRUCT) fuer TypeIdentifier.
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
64pub struct CollectionElementFlag(pub u16);
65
66/// Equivalence-Kind des Elements einer PlainCollection (§7.3.4.7.1).
67///
68/// EK_MINIMAL = TI ist strongly-hashed Minimal, EK_COMPLETE = Complete,
69/// EK_BOTH = identisch fuer beide, `None` = element ist selbst primitive
70/// oder plain (kein strong-hash).
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub enum EquivalenceKind {
73    /// Element ist ein Primitive oder anderes plain — kein Hash.
74    None,
75    /// Element-TypeIdentifier ist EK_MINIMAL.
76    Minimal,
77    /// Element-TypeIdentifier ist EK_COMPLETE.
78    Complete,
79    /// Minimal + Complete sind gleich (z.B. bei vollstaendig primitiven Types).
80    Both,
81}
82
83impl EquivalenceKind {
84    /// Kodiert als `octet` (§7.3.4.7.1 EquivalenceKind).
85    #[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    /// Decoder.
96    #[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/// Header fuer plain collections (§7.3.4.7.1).
108#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
109pub struct PlainCollectionHeader {
110    /// Equivalence-Kind des Element-TypeIdentifiers.
111    pub equiv_kind: u8,
112    /// Try-Construct-Flags.
113    pub element_flags: CollectionElementFlag,
114}
115
116/// Identifiziert eine starkconnected-Component-ID fuer rekursive Typen.
117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118pub struct StronglyConnectedComponentId {
119    /// 14-byte SHA256 der SCC.
120    pub hash: EquivalenceHash,
121    /// Komponenten-Index innerhalb der SCC.
122    pub scc_length: i32,
123    /// Index des Typs in der SCC.
124    pub scc_index: i32,
125}
126
127/// TypeIdentifier — XTypes §7.3.4.2.
128///
129/// Direkt identifiziert primitive und plain-Typen, indirekt (ueber
130/// 14-byte Hash) composite Types (struct, union, etc.). Plain
131/// collections koennen rekursiv TypeIdentifier enthalten, deshalb
132/// `Box` fuer die nested Varianten.
133#[derive(Debug, Clone, PartialEq, Eq, Default)]
134#[non_exhaustive]
135pub enum TypeIdentifier {
136    /// TK_NONE — Spec-Sentinel für "kein TypeIdentifier bekannt".
137    #[default]
138    None,
139    /// Primitive ohne Body — discriminator trägt alle Infos.
140    Primitive(PrimitiveKind),
141    /// `string<Bound>` mit 8-bit Zeichen, Bound <= 255 bytes.
142    String8Small {
143        /// Max-Laenge (0 = unbounded).
144        bound: u8,
145    },
146    /// `string<Bound>` mit Bound > 255.
147    String8Large {
148        /// Max-Laenge.
149        bound: u32,
150    },
151    /// `wstring<Bound>` mit Bound <= 255 chars.
152    String16Small {
153        /// Max-Laenge.
154        bound: u8,
155    },
156    /// `wstring<Bound>`.
157    String16Large {
158        /// Max-Laenge.
159        bound: u32,
160    },
161    /// `sequence<T, N>` mit N <= 255.
162    PlainSequenceSmall {
163        /// Header (EquivKind des Elements + Flags).
164        header: PlainCollectionHeader,
165        /// Maximum-Bound (0 = unbounded).
166        bound: u8,
167        /// Element-TypeIdentifier.
168        element: Box<TypeIdentifier>,
169    },
170    /// `sequence<T, N>` mit N > 255.
171    PlainSequenceLarge {
172        /// Header.
173        header: PlainCollectionHeader,
174        /// Max-Bound.
175        bound: u32,
176        /// Element-TypeIdentifier.
177        element: Box<TypeIdentifier>,
178    },
179    /// `T[D1, D2, ...]` mit allen Dimensionen <= 255.
180    PlainArraySmall {
181        /// Header.
182        header: PlainCollectionHeader,
183        /// Array-Dimensionen (max 2^20 Gesamt laut §7.3.4.7).
184        array_bounds: Vec<u8>,
185        /// Element-TypeIdentifier.
186        element: Box<TypeIdentifier>,
187    },
188    /// `T[D1, D2, ...]` mit mindestens einer Dimension > 255.
189    PlainArrayLarge {
190        /// Header.
191        header: PlainCollectionHeader,
192        /// Array-Dimensionen.
193        array_bounds: Vec<u32>,
194        /// Element-TypeIdentifier.
195        element: Box<TypeIdentifier>,
196    },
197    /// `map<K, V, N>` mit N <= 255.
198    PlainMapSmall {
199        /// Header.
200        header: PlainCollectionHeader,
201        /// Max-Groesse.
202        bound: u8,
203        /// Value-TypeIdentifier.
204        element: Box<TypeIdentifier>,
205        /// Key-Flags.
206        key_flags: CollectionElementFlag,
207        /// Key-TypeIdentifier.
208        key: Box<TypeIdentifier>,
209    },
210    /// `map<K, V, N>` mit N > 255.
211    PlainMapLarge {
212        /// Header.
213        header: PlainCollectionHeader,
214        /// Max-Groesse.
215        bound: u32,
216        /// Value-TypeIdentifier.
217        element: Box<TypeIdentifier>,
218        /// Key-Flags.
219        key_flags: CollectionElementFlag,
220        /// Key-TypeIdentifier.
221        key: Box<TypeIdentifier>,
222    },
223    /// Stark-zusammenhaengende Komponente (rekursive Typen) — §7.3.4.9.
224    StronglyConnectedComponent(StronglyConnectedComponentId),
225    /// 14-byte Hash des MinimalTypeObject.
226    EquivalenceHashMinimal(EquivalenceHash),
227    /// 14-byte Hash des CompleteTypeObject.
228    EquivalenceHashComplete(EquivalenceHash),
229    /// Unbekannter/unsupportierter Discriminator (Forward-Compat).
230    Unknown(u8),
231}
232
233/// Primitive Kind (kein Body im TypeIdentifier, nur der Discriminator).
234#[derive(Debug, Clone, Copy, PartialEq, Eq)]
235pub enum PrimitiveKind {
236    /// `bool`.
237    Boolean,
238    /// `octet` / `byte`.
239    Byte,
240    /// `int8`.
241    Int8,
242    /// `int16`.
243    Int16,
244    /// `int32`.
245    Int32,
246    /// `int64`.
247    Int64,
248    /// `uint8`.
249    UInt8,
250    /// `uint16`.
251    UInt16,
252    /// `uint32`.
253    UInt32,
254    /// `uint64`.
255    UInt64,
256    /// `float32`.
257    Float32,
258    /// `float64`.
259    Float64,
260    /// `float128`.
261    Float128,
262    /// `char` (8-bit).
263    Char8,
264    /// `wchar` (16-bit).
265    Char16,
266}
267
268impl PrimitiveKind {
269    /// Diskriminator-Byte im TypeIdentifier.
270    #[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    /// Versucht, aus einem Discriminator-Byte eine primitive Kind zu lesen.
292    #[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
315// ============================================================================
316// Wire-Encoding
317// ============================================================================
318
319impl TypeIdentifier {
320    /// Encoded als XCDR2-little-endian Bytes (TypeIdentifier ohne
321    /// Encapsulation-Header — der wird vom Caller (z.B. TYPE_INFORMATION
322    /// PID oder TypeLookup-RPC) geliefert).
323    ///
324    /// # Errors
325    /// `EncodeError` bei Buffer-Overflow (sehr unwahrscheinlich fuer
326    /// normale Typen; 2^32-Grenze bei large-Kinds).
327    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                // sequence<octet> — 4-byte length + bytes (XCDR2 LE).
359                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                // Spec §7.3.4.9: scc_length >= 0, 0 <= scc_index < scc_length.
413                // Negative Werte + Index-OoB verhindern (wuerde sonst als
414                // riesige u32 durchs Two's-Complement entweichen).
415                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    /// Maximum-Rekursionstiefe beim Wire-Decode eines verschachtelten
431    /// `TypeIdentifier` (Plain-Collections koennen rekursiv referenzieren).
432    /// Schuetzt vor Stack-Overflow bei pathologischen Datagrammen.
433    pub const MAX_DECODE_DEPTH: usize = 16;
434
435    /// Decode aus XCDR2-little-endian Bytes. Der Reader muss auf den
436    /// Discriminator positioniert sein. Interne Rekursion ist gecapt
437    /// (siehe [`Self::MAX_DECODE_DEPTH`]).
438    ///
439    /// # Errors
440    /// `DecodeError` bei Buffer-Underflow, inkonsistentem Length-Feld
441    /// oder wenn die maximale Rekursionstiefe ueberschritten wird.
442    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                // Primitiver Discriminator → kein Body. Der outer match-Arm
461                // stellt sicher, dass from_u8 Some(...) liefert — falls
462                // jemand die Liste erweitert und from_u8 vergisst, faellt
463                // d in den `other =>`-Pfad als Unknown(d).
464                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    /// Der Discriminator-Byte fuer diesen TypeIdentifier.
590    #[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    /// Kurzform: encode in neuen BufferWriter, LE.
613    ///
614    /// # Errors
615    /// `EncodeError` bei Overflow.
616    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    /// Kurzform: decode aus LE-Bytes.
623    ///
624    /// # Errors
625    /// `DecodeError`.
626    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// ============================================================================
652// Tests
653// ============================================================================
654
655#[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 }); // unbounded
705    }
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, // muss < 3 sein
837        });
838        assert!(scc.to_bytes_le().is_err());
839    }
840
841    #[test]
842    fn max_decode_depth_constant_is_reasonable() {
843        // DoS-Guard: Rekursion beim Wire-Decode eines nested
844        // `TypeIdentifier` ist begrenzt. Cap muss > 4 (fuer realistische
845        // nested Sequences) und < 64 (DoS-Schutz) sein.
846        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        // 3-deep sequence<sequence<sequence<int32>>> — innerhalb Cap.
853        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    // ---- Additional edge-case roundtrips --------------------------------
879
880    #[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        // Bewusst Discriminator-Bytes, die keinem TK_/TI_/EK_ zugeordnet
945        // sind. 0x01 (TK_BOOLEAN), 0x70ff (TI_STRING8_*) usw. werden
946        // vermieden. 0x12-0x1F, 0x40, 0x50, 0xC7, 0xFE, 0xFF sind frei.
947        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            // Encode should round-trip back to the same discriminator byte.
951            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            // None round-trips to None (encoded as 0 which isn't a known
1055            // discriminator → `from_u8` falls back to None).
1056            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}