Skip to main content

zerodds_cdr/
type_code.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! CORBA `TypeCode` + its CDR wire form (§15.3.5) — the self-describing part
5//! of an `any`. A `TypeCode` is a `TCKind` (`unsigned long`) plus parameters:
6//!
7//! * **Empty-param kinds** (null/void/short/long/…/boolean/char/octet/wchar/
8//!   any/TypeCode/longlong/…): just the TCKind.
9//! * **Simple-param** (`tk_string`/`tk_wstring`): TCKind + `bound` (ulong),
10//!   inline (NOT encapsulated).
11//! * **Complex-param** (`tk_objref`/`tk_struct`/`tk_enum`/`tk_sequence`/
12//!   `tk_array`/`tk_alias`/`tk_except`/`tk_union`): TCKind + a CDR
13//!   encapsulation (`sequence<octet>` = length + byte-order octet +
14//!   parameters, alignment relative to the byte-order octet).
15//!
16//! Indirection (TCKind `0xffffffff` + negative offset, §15.3.5.1) is
17//! **resolved** during decoding: a position cache `(stream offset → TypeCode)`
18//! makes it possible to clone *repeated* TypeCodes and to represent
19//! *recursive* ones (e.g. `struct Node { sequence<Node> kids; }`) as a
20//! [`TypeCode::Recursive`] marker. Encode-side indirection (offset
21//! backpatching) is a separate feature; for cross-ORB interop, decode
22//! acceptance is sufficient.
23
24use alloc::boxed::Box;
25use alloc::string::String;
26use alloc::vec::Vec;
27
28use crate::buffer::{BufferReader, BufferWriter};
29use crate::endianness::Endianness;
30use crate::error::{DecodeError, EncodeError};
31
32/// `TCKind` discriminators (§15.3.5.1, OMG wire values). The constant names
33/// map 1:1 to the OMG `tk_*` identifiers.
34#[allow(missing_docs)]
35pub mod tckind {
36    pub const TK_NULL: u32 = 0;
37    pub const TK_VOID: u32 = 1;
38    pub const TK_SHORT: u32 = 2;
39    pub const TK_LONG: u32 = 3;
40    pub const TK_USHORT: u32 = 4;
41    pub const TK_ULONG: u32 = 5;
42    pub const TK_FLOAT: u32 = 6;
43    pub const TK_DOUBLE: u32 = 7;
44    pub const TK_BOOLEAN: u32 = 8;
45    pub const TK_CHAR: u32 = 9;
46    pub const TK_OCTET: u32 = 10;
47    pub const TK_ANY: u32 = 11;
48    pub const TK_TYPECODE: u32 = 12;
49    pub const TK_OBJREF: u32 = 14;
50    pub const TK_STRUCT: u32 = 15;
51    pub const TK_ENUM: u32 = 17;
52    pub const TK_STRING: u32 = 18;
53    pub const TK_SEQUENCE: u32 = 19;
54    pub const TK_ALIAS: u32 = 21;
55    pub const TK_EXCEPT: u32 = 22;
56    pub const TK_LONGLONG: u32 = 23;
57    pub const TK_ULONGLONG: u32 = 24;
58    pub const TK_WCHAR: u32 = 26;
59    pub const TK_WSTRING: u32 = 27;
60    pub const INDIRECTION: u32 = 0xffff_ffff;
61}
62
63/// CORBA `TypeCode` (subset: all scalar kinds + string/wstring + sequence +
64/// struct + enum + alias + objref — the common structured `any` contents).
65#[derive(Debug, Clone, PartialEq, Eq)]
66pub enum TypeCode {
67    /// `tk_null`.
68    Null,
69    /// `tk_void`.
70    Void,
71    /// `tk_short`.
72    Short,
73    /// `tk_long`.
74    Long,
75    /// `tk_ushort`.
76    UShort,
77    /// `tk_ulong`.
78    ULong,
79    /// `tk_longlong`.
80    LongLong,
81    /// `tk_ulonglong`.
82    ULongLong,
83    /// `tk_float`.
84    Float,
85    /// `tk_double`.
86    Double,
87    /// `tk_boolean`.
88    Boolean,
89    /// `tk_char`.
90    Char,
91    /// `tk_octet`.
92    Octet,
93    /// `tk_wchar`.
94    WChar,
95    /// `tk_any`.
96    Any,
97    /// `tk_TypeCode`.
98    TypeCodeTc,
99    /// `tk_string` with `bound` (0 = unbounded).
100    String(u32),
101    /// `tk_wstring` with `bound`.
102    WString(u32),
103    /// `tk_sequence`: element type + `bound`.
104    Sequence {
105        /// Element TypeCode.
106        element: Box<TypeCode>,
107        /// Bound (0 = unbounded).
108        bound: u32,
109    },
110    /// `tk_struct` (and `tk_except`): RepositoryId, name, ordered members.
111    Struct {
112        /// `IDL:…:1.0`.
113        repo_id: String,
114        /// Struct name.
115        name: String,
116        /// `(member_name, member_type)` in declaration order.
117        members: Vec<(String, TypeCode)>,
118        /// `true` ⇒ `tk_except` instead of `tk_struct` (same wire form).
119        is_except: bool,
120    },
121    /// `tk_enum`: RepositoryId, name, enumerator names (index = value).
122    Enum {
123        /// `IDL:…:1.0`.
124        repo_id: String,
125        /// Enum name.
126        name: String,
127        /// Enumerator names.
128        members: Vec<String>,
129    },
130    /// `tk_alias` (typedef): RepositoryId, name, resolved content.
131    Alias {
132        /// `IDL:…:1.0`.
133        repo_id: String,
134        /// Alias name.
135        name: String,
136        /// Resolved content TypeCode.
137        content: Box<TypeCode>,
138    },
139    /// `tk_objref`: interface RepositoryId + name.
140    ObjRef {
141        /// `IDL:…:1.0`.
142        repo_id: String,
143        /// Interface name.
144        name: String,
145    },
146    /// Recursive reference (§15.3.5.1 indirection): points via `repo_id` to an
147    /// enclosing TypeCode that is still being decoded (e.g.
148    /// `struct Node { sequence<Node> kids; }`). Decode-only marker — breaks the
149    /// otherwise infinite type graph.
150    Recursive {
151        /// RepositoryId of the recursively referenced type.
152        repo_id: String,
153    },
154}
155
156impl TypeCode {
157    /// The `TCKind` wire value.
158    #[must_use]
159    pub const fn tckind(&self) -> u32 {
160        use tckind::*;
161        match self {
162            Self::Null => TK_NULL,
163            Self::Void => TK_VOID,
164            Self::Short => TK_SHORT,
165            Self::Long => TK_LONG,
166            Self::UShort => TK_USHORT,
167            Self::ULong => TK_ULONG,
168            Self::LongLong => TK_LONGLONG,
169            Self::ULongLong => TK_ULONGLONG,
170            Self::Float => TK_FLOAT,
171            Self::Double => TK_DOUBLE,
172            Self::Boolean => TK_BOOLEAN,
173            Self::Char => TK_CHAR,
174            Self::Octet => TK_OCTET,
175            Self::WChar => TK_WCHAR,
176            Self::Any => TK_ANY,
177            Self::TypeCodeTc => TK_TYPECODE,
178            Self::String(_) => TK_STRING,
179            Self::WString(_) => TK_WSTRING,
180            Self::Sequence { .. } => TK_SEQUENCE,
181            Self::Struct {
182                is_except: true, ..
183            } => TK_EXCEPT,
184            Self::Struct { .. } => TK_STRUCT,
185            Self::Enum { .. } => TK_ENUM,
186            Self::Alias { .. } => TK_ALIAS,
187            Self::ObjRef { .. } => TK_OBJREF,
188            // Decode-only marker; the wire carries the indirection instead.
189            Self::Recursive { .. } => INDIRECTION,
190        }
191    }
192
193    /// Encodes the TypeCode (§15.3.5) into the buffer.
194    ///
195    /// # Errors
196    /// Buffer write error or length overflow.
197    pub fn encode(&self, w: &mut BufferWriter) -> Result<(), EncodeError> {
198        w.write_u32(self.tckind())?;
199        match self {
200            // Empty-param kinds: just the TCKind.
201            Self::Null
202            | Self::Void
203            | Self::Short
204            | Self::Long
205            | Self::UShort
206            | Self::ULong
207            | Self::LongLong
208            | Self::ULongLong
209            | Self::Float
210            | Self::Double
211            | Self::Boolean
212            | Self::Char
213            | Self::Octet
214            | Self::WChar
215            | Self::Any
216            | Self::TypeCodeTc => Ok(()),
217            // Simple-param: bound inline.
218            Self::String(b) | Self::WString(b) => w.write_u32(*b),
219            // Complex-param: encapsulation.
220            Self::Sequence { element, bound } => {
221                let encap = build_encap(w.endianness(), |e| {
222                    element.encode(e)?;
223                    e.write_u32(*bound)
224                })?;
225                write_encap(w, &encap)
226            }
227            Self::Struct {
228                repo_id,
229                name,
230                members,
231                ..
232            } => {
233                let encap = build_encap(w.endianness(), |e| {
234                    e.write_string(repo_id)?;
235                    e.write_string(name)?;
236                    e.write_u32(u32::try_from(members.len()).map_err(|_| {
237                        EncodeError::ValueOutOfRange {
238                            message: "TypeCode struct member count exceeds u32",
239                        }
240                    })?)?;
241                    for (mn, mt) in members {
242                        e.write_string(mn)?;
243                        mt.encode(e)?;
244                    }
245                    Ok(())
246                })?;
247                write_encap(w, &encap)
248            }
249            Self::Enum {
250                repo_id,
251                name,
252                members,
253            } => {
254                let encap = build_encap(w.endianness(), |e| {
255                    e.write_string(repo_id)?;
256                    e.write_string(name)?;
257                    e.write_u32(u32::try_from(members.len()).map_err(|_| {
258                        EncodeError::ValueOutOfRange {
259                            message: "TypeCode enum member count exceeds u32",
260                        }
261                    })?)?;
262                    for mn in members {
263                        e.write_string(mn)?;
264                    }
265                    Ok(())
266                })?;
267                write_encap(w, &encap)
268            }
269            Self::Alias {
270                repo_id,
271                name,
272                content,
273            } => {
274                let encap = build_encap(w.endianness(), |e| {
275                    e.write_string(repo_id)?;
276                    e.write_string(name)?;
277                    content.encode(e)
278                })?;
279                write_encap(w, &encap)
280            }
281            Self::ObjRef { repo_id, name } => {
282                let encap = build_encap(w.endianness(), |e| {
283                    e.write_string(repo_id)?;
284                    e.write_string(name)
285                })?;
286                write_encap(w, &encap)
287            }
288            // Encode-side indirection (a backpatch offset table) is a
289            // separate feature; decode acceptance is enough for cross-ORB interop.
290            Self::Recursive { .. } => Err(EncodeError::ValueOutOfRange {
291                message: "TypeCode::Recursive encode (indirection emit) not yet supported",
292            }),
293        }
294    }
295
296    /// Decodes a TypeCode (§15.3.5) including **indirection** (§15.3.5.1):
297    /// recursive and repeated TypeCodes (`0xffffffff` + negative offset)
298    /// are resolved — repeated ones cloned via a position cache, recursive
299    /// ones as a [`TypeCode::Recursive`] marker.
300    ///
301    /// # Errors
302    /// `InvalidEnum` on an unknown TCKind / unresolvable indirection;
303    /// buffer read error.
304    pub fn decode(r: &mut BufferReader<'_>) -> Result<Self, DecodeError> {
305        let mut cache = TcCache::new();
306        // base 0: cache positions are reader-absolute (`r.position()`).
307        Self::decode_ctx(r, 0, &mut cache)
308    }
309
310    /// Position-tracking decode. `base` = absolute stream offset of the
311    /// reader's byte 0; `cache` maps `(base + position)` of a TCKind to the
312    /// TypeCode decoded there — a precondition for resolving indirections
313    /// (which point backward by byte offset).
314    fn decode_ctx(
315        r: &mut BufferReader<'_>,
316        base: usize,
317        cache: &mut TcCache,
318    ) -> Result<Self, DecodeError> {
319        use tckind::*;
320        // TCKind is a 4-aligned `unsigned long`. Apply the alignment BEFORE
321        // recording the position (read_u32 would otherwise do it internally
322        // first) — otherwise the cache position points at the padding instead
323        // of the TCKind, and indirections to nested TypeCodes fail to resolve.
324        r.align(4)?;
325        let tckind_pos = base + r.position();
326        let kind = r.read_u32()?;
327        match kind {
328            TK_NULL => Ok(Self::Null),
329            TK_VOID => Ok(Self::Void),
330            TK_SHORT => Ok(Self::Short),
331            TK_LONG => Ok(Self::Long),
332            TK_USHORT => Ok(Self::UShort),
333            TK_ULONG => Ok(Self::ULong),
334            TK_LONGLONG => Ok(Self::LongLong),
335            TK_ULONGLONG => Ok(Self::ULongLong),
336            TK_FLOAT => Ok(Self::Float),
337            TK_DOUBLE => Ok(Self::Double),
338            TK_BOOLEAN => Ok(Self::Boolean),
339            TK_CHAR => Ok(Self::Char),
340            TK_OCTET => Ok(Self::Octet),
341            TK_WCHAR => Ok(Self::WChar),
342            TK_ANY => Ok(Self::Any),
343            TK_TYPECODE => Ok(Self::TypeCodeTc),
344            TK_STRING => Ok(Self::String(r.read_u32()?)),
345            TK_WSTRING => Ok(Self::WString(r.read_u32()?)),
346            INDIRECTION => {
347                // 0xffffffff followed by a signed long: offset relative to the
348                // position of the offset field, backward to the target TCKind (§15.3.5.1).
349                let off_field_pos = base + r.position();
350                let offset = r.read_u32()? as i32;
351                if offset >= 0 {
352                    return Err(DecodeError::InvalidEnum {
353                        kind: "TypeCode indirection: offset must be negative",
354                        value: INDIRECTION,
355                    });
356                }
357                let target =
358                    usize::try_from(off_field_pos as i64 + i64::from(offset)).map_err(|_| {
359                        DecodeError::InvalidEnum {
360                            kind: "TypeCode indirection: target before stream start",
361                            value: INDIRECTION,
362                        }
363                    })?;
364                match cache.get(&target) {
365                    Some(TcCacheEntry::Done(tc)) => Ok(tc.clone()),
366                    Some(TcCacheEntry::InProgress(repo_id)) => Ok(Self::Recursive {
367                        repo_id: repo_id.clone(),
368                    }),
369                    None => Err(DecodeError::InvalidEnum {
370                        kind: "TypeCode indirection: unresolved target",
371                        value: INDIRECTION,
372                    }),
373                }
374            }
375            TK_SEQUENCE => decode_encap_ctx(r, base, cache, |e, cb, cache| {
376                let element = Box::new(Self::decode_ctx(e, cb, cache)?);
377                let bound = e.read_u32()?;
378                let tc = Self::Sequence { element, bound };
379                cache.insert(tckind_pos, TcCacheEntry::Done(tc.clone()));
380                Ok(tc)
381            }),
382            TK_STRUCT | TK_EXCEPT => decode_encap_ctx(r, base, cache, |e, cb, cache| {
383                let repo_id = e.read_string()?;
384                let name = e.read_string()?;
385                // Placeholder BEFORE the members: a member that indirects
386                // recursively back to this struct resolves to Recursive.
387                cache.insert(tckind_pos, TcCacheEntry::InProgress(repo_id.clone()));
388                let count = e.read_u32()? as usize;
389                let mut members = Vec::with_capacity(count.min(256));
390                for _ in 0..count {
391                    let mn = e.read_string()?;
392                    let mt = Self::decode_ctx(e, cb, cache)?;
393                    members.push((mn, mt));
394                }
395                let tc = Self::Struct {
396                    repo_id,
397                    name,
398                    members,
399                    is_except: kind == TK_EXCEPT,
400                };
401                cache.insert(tckind_pos, TcCacheEntry::Done(tc.clone()));
402                Ok(tc)
403            }),
404            TK_ENUM => decode_encap_ctx(r, base, cache, |e, _cb, cache| {
405                let repo_id = e.read_string()?;
406                let name = e.read_string()?;
407                let count = e.read_u32()? as usize;
408                let mut members = Vec::with_capacity(count.min(1024));
409                for _ in 0..count {
410                    members.push(e.read_string()?);
411                }
412                let tc = Self::Enum {
413                    repo_id,
414                    name,
415                    members,
416                };
417                cache.insert(tckind_pos, TcCacheEntry::Done(tc.clone()));
418                Ok(tc)
419            }),
420            TK_ALIAS => decode_encap_ctx(r, base, cache, |e, cb, cache| {
421                let repo_id = e.read_string()?;
422                let name = e.read_string()?;
423                cache.insert(tckind_pos, TcCacheEntry::InProgress(repo_id.clone()));
424                let content = Box::new(Self::decode_ctx(e, cb, cache)?);
425                let tc = Self::Alias {
426                    repo_id,
427                    name,
428                    content,
429                };
430                cache.insert(tckind_pos, TcCacheEntry::Done(tc.clone()));
431                Ok(tc)
432            }),
433            TK_OBJREF => decode_encap_ctx(r, base, cache, |e, _cb, cache| {
434                let repo_id = e.read_string()?;
435                let name = e.read_string()?;
436                let tc = Self::ObjRef { repo_id, name };
437                cache.insert(tckind_pos, TcCacheEntry::Done(tc.clone()));
438                Ok(tc)
439            }),
440            other => Err(DecodeError::InvalidEnum {
441                kind: "TypeCode TCKind (unsupported)",
442                value: other,
443            }),
444        }
445    }
446}
447
448/// Cache entry for indirection resolution (§15.3.5.1).
449enum TcCacheEntry {
450    /// Complex TypeCode still being decoded — an indirection to it is
451    /// recursive and yields [`TypeCode::Recursive`].
452    InProgress(String),
453    /// Fully decoded — an indirection to it is a repeated TypeCode.
454    Done(TypeCode),
455}
456
457/// Position cache: `(stream offset of the TCKind) → entry`.
458type TcCache = alloc::collections::BTreeMap<usize, TcCacheEntry>;
459
460/// Builds a CDR encapsulation: byte-order octet + body (alignment from the
461/// byte-order octet — natural alignment in the fresh sub-buffer).
462fn build_encap<F>(endianness: Endianness, body: F) -> Result<Vec<u8>, EncodeError>
463where
464    F: FnOnce(&mut BufferWriter) -> Result<(), EncodeError>,
465{
466    let mut e = BufferWriter::new(endianness);
467    e.write_u8(match endianness {
468        Endianness::Big => 0,
469        Endianness::Little => 1,
470    })?;
471    body(&mut e)?;
472    Ok(e.into_bytes())
473}
474
475/// Writes an encapsulation as `sequence<octet>` (length + bytes).
476fn write_encap(w: &mut BufferWriter, encap: &[u8]) -> Result<(), EncodeError> {
477    let len = u32::try_from(encap.len()).map_err(|_| EncodeError::ValueOutOfRange {
478        message: "TypeCode encapsulation exceeds u32",
479    })?;
480    w.write_u32(len)?;
481    w.write_bytes(encap)
482}
483
484/// Reads an encapsulation (`sequence<octet>`) and decodes its body in the order
485/// given by the byte-order octet (origin = byte-order octet). Passes the
486/// **absolute stream offset** of the encapsulation's byte 0 (`base + position at
487/// `read_bytes`) as `child_base` to the body — needed so that indirections
488/// inside the encapsulation resolve to TCKinds outside (recursively).
489fn decode_encap_ctx<F>(
490    r: &mut BufferReader<'_>,
491    base: usize,
492    cache: &mut TcCache,
493    body: F,
494) -> Result<TypeCode, DecodeError>
495where
496    F: FnOnce(&mut BufferReader<'_>, usize, &mut TcCache) -> Result<TypeCode, DecodeError>,
497{
498    let offset = r.position();
499    let len = r.read_u32()? as usize;
500    // The stream position from which read_bytes copies = the absolute offset of
501    // byte 0 of the sub-buffer.
502    let child_base = base + r.position();
503    let bytes = r.read_bytes(len)?;
504    if bytes.is_empty() {
505        return Err(DecodeError::InvalidString {
506            offset,
507            reason: "empty TypeCode encapsulation",
508        });
509    }
510    let endianness = match bytes[0] {
511        0 => Endianness::Big,
512        1 => Endianness::Little,
513        _ => {
514            return Err(DecodeError::InvalidString {
515                offset,
516                reason: "invalid TypeCode encapsulation byte-order",
517            });
518        }
519    };
520    // Reader over the WHOLE encapsulation (origin = byte-order octet); skip the
521    // octet, then read the body in its order.
522    let mut e = BufferReader::new(bytes, endianness);
523    let _bo = e.read_u8()?;
524    body(&mut e, child_base, cache)
525}
526
527#[cfg(test)]
528#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
529mod tests {
530    use super::*;
531    use alloc::vec;
532
533    fn rt(tc: &TypeCode, e: Endianness) {
534        let mut w = BufferWriter::new(e);
535        tc.encode(&mut w).unwrap();
536        let bytes = w.into_bytes();
537        let mut r = BufferReader::new(&bytes, e);
538        assert_eq!(&TypeCode::decode(&mut r).unwrap(), tc, "{tc:?} / {e:?}");
539    }
540
541    #[test]
542    fn simple_kinds_roundtrip() {
543        for tc in [
544            TypeCode::Null,
545            TypeCode::Long,
546            TypeCode::Double,
547            TypeCode::Boolean,
548            TypeCode::Octet,
549            TypeCode::WChar,
550            TypeCode::Any,
551            TypeCode::String(0),
552            TypeCode::String(255),
553            TypeCode::WString(64),
554        ] {
555            rt(&tc, Endianness::Big);
556            rt(&tc, Endianness::Little);
557        }
558    }
559
560    #[test]
561    fn sequence_of_long_roundtrip() {
562        let tc = TypeCode::Sequence {
563            element: Box::new(TypeCode::Long),
564            bound: 0,
565        };
566        rt(&tc, Endianness::Big);
567        rt(&tc, Endianness::Little);
568    }
569
570    /// Indirection (§15.3.5.1) — **recursive** struct `Node { Node n; }`: one
571    /// member indirects to the enclosing struct that is still being decoded
572    /// → [`TypeCode::Recursive`]. Hand-built frame (the encoder emits no
573    /// indirection). Top-level struct: the encap body starts at abs offset 8,
574    /// the indirection's offset field sits at 12+i_pos → offset = -(12+i_pos)
575    /// points at the Node TCKind at abs 0.
576    #[test]
577    fn indirection_recursive_struct() {
578        use tckind::{INDIRECTION, TK_STRUCT};
579        let mut eb = BufferWriter::new(Endianness::Big);
580        eb.write_u8(0).unwrap(); // BO octet (big)
581        eb.write_string("IDL:Node:1.0").unwrap();
582        eb.write_string("Node").unwrap();
583        eb.write_u32(1).unwrap(); // 1 member
584        eb.write_string("n").unwrap();
585        eb.align(4);
586        let i_pos = eb.position();
587        eb.write_u32(INDIRECTION).unwrap();
588        let offset: i32 = -(12 + i_pos as i32);
589        eb.write_u32(offset as u32).unwrap();
590        let body = eb.into_bytes();
591
592        let mut w = BufferWriter::new(Endianness::Big);
593        w.write_u32(TK_STRUCT).unwrap();
594        w.write_u32(body.len() as u32).unwrap();
595        w.write_bytes(&body).unwrap();
596        let bytes = w.into_bytes();
597
598        let decoded = TypeCode::decode(&mut BufferReader::new(&bytes, Endianness::Big)).unwrap();
599        assert_eq!(
600            decoded,
601            TypeCode::Struct {
602                repo_id: "IDL:Node:1.0".into(),
603                name: "Node".into(),
604                members: vec![(
605                    "n".into(),
606                    TypeCode::Recursive {
607                        repo_id: "IDL:Node:1.0".into()
608                    }
609                )],
610                is_except: false,
611            }
612        );
613    }
614
615    /// Indirection with an unresolvable target (no previously decoded TCKind at
616    /// the target position) → error instead of panic/infinite loop.
617    #[test]
618    fn indirection_unresolved_target_is_error() {
619        use tckind::{INDIRECTION, TK_STRUCT};
620        // struct with one member whose type is an indirection with offset -4
621        // (points into the middle of the length/repo-ID field, no TCKind there).
622        let mut eb = BufferWriter::new(Endianness::Big);
623        eb.write_u8(0).unwrap();
624        eb.write_string("IDL:X:1.0").unwrap();
625        eb.write_string("X").unwrap();
626        eb.write_u32(1).unwrap();
627        eb.write_string("m").unwrap();
628        eb.align(4);
629        eb.write_u32(INDIRECTION).unwrap();
630        eb.write_u32((-4_i32) as u32).unwrap(); // target = offset field - 4, no TCKind
631        let body = eb.into_bytes();
632        let mut w = BufferWriter::new(Endianness::Big);
633        w.write_u32(TK_STRUCT).unwrap();
634        w.write_u32(body.len() as u32).unwrap();
635        w.write_bytes(&body).unwrap();
636        let bytes = w.into_bytes();
637        assert!(TypeCode::decode(&mut BufferReader::new(&bytes, Endianness::Big)).is_err());
638    }
639
640    /// Indirection (§15.3.5.1) — **repeated** TypeCode: `struct Outer { S a;
641    /// S b; }`, where `b`'s type is an indirection to `a`'s (already decoded)
642    /// `S` TypeCode → cloned from the cache. Both members are the same `S`.
643    /// Same encap: the abs offset 8 cancels out, so
644    /// offset = `s_subpos - (i_pos + 4)`.
645    #[test]
646    fn indirection_repeated_typecode() {
647        use tckind::{INDIRECTION, TK_STRUCT};
648        let s = TypeCode::Struct {
649            repo_id: "IDL:S:1.0".into(),
650            name: "S".into(),
651            members: vec![("x".into(), TypeCode::Long)],
652            is_except: false,
653        };
654        let mut eb = BufferWriter::new(Endianness::Big);
655        eb.write_u8(0).unwrap(); // BO
656        eb.write_string("IDL:Outer:1.0").unwrap();
657        eb.write_string("Outer").unwrap();
658        eb.write_u32(2).unwrap(); // 2 members
659        eb.write_string("a").unwrap();
660        eb.align(4);
661        let s_subpos = eb.position();
662        s.encode(&mut eb).unwrap(); // S at s_subpos
663        eb.write_string("b").unwrap();
664        eb.align(4);
665        let i_pos = eb.position();
666        eb.write_u32(INDIRECTION).unwrap();
667        let offset: i32 = s_subpos as i32 - (i_pos as i32 + 4);
668        eb.write_u32(offset as u32).unwrap();
669        let body = eb.into_bytes();
670
671        let mut w = BufferWriter::new(Endianness::Big);
672        w.write_u32(TK_STRUCT).unwrap();
673        w.write_u32(body.len() as u32).unwrap();
674        w.write_bytes(&body).unwrap();
675        let bytes = w.into_bytes();
676
677        let decoded = TypeCode::decode(&mut BufferReader::new(&bytes, Endianness::Big)).unwrap();
678        assert_eq!(
679            decoded,
680            TypeCode::Struct {
681                repo_id: "IDL:Outer:1.0".into(),
682                name: "Outer".into(),
683                members: vec![("a".into(), s.clone()), ("b".into(), s.clone())],
684                is_except: false,
685            }
686        );
687    }
688
689    /// A positive offset (indirection points forward) is invalid → error.
690    #[test]
691    fn indirection_forward_offset_rejected() {
692        use tckind::INDIRECTION;
693        let mut w = BufferWriter::new(Endianness::Big);
694        w.write_u32(INDIRECTION).unwrap();
695        w.write_u32(4_u32).unwrap(); // positive offset
696        let bytes = w.into_bytes();
697        assert!(TypeCode::decode(&mut BufferReader::new(&bytes, Endianness::Big)).is_err());
698    }
699
700    #[test]
701    fn struct_roundtrip_both_orders() {
702        let tc = TypeCode::Struct {
703            repo_id: "IDL:Point:1.0".into(),
704            name: "Point".into(),
705            members: vec![
706                ("x".into(), TypeCode::Long),
707                ("y".into(), TypeCode::Long),
708                ("label".into(), TypeCode::String(0)),
709            ],
710            is_except: false,
711        };
712        rt(&tc, Endianness::Big);
713        rt(&tc, Endianness::Little);
714    }
715
716    #[test]
717    fn enum_and_nested_sequence_of_struct() {
718        rt(
719            &TypeCode::Enum {
720                repo_id: "IDL:Color:1.0".into(),
721                name: "Color".into(),
722                members: vec!["RED".into(), "GREEN".into(), "BLUE".into()],
723            },
724            Endianness::Big,
725        );
726        // sequence<Point> — complex element in a complex TC (nested encaps).
727        let point = TypeCode::Struct {
728            repo_id: "IDL:Point:1.0".into(),
729            name: "Point".into(),
730            members: vec![("x".into(), TypeCode::Long), ("y".into(), TypeCode::Long)],
731            is_except: false,
732        };
733        let seq = TypeCode::Sequence {
734            element: Box::new(point),
735            bound: 10,
736        };
737        rt(&seq, Endianness::Big);
738        rt(&seq, Endianness::Little);
739    }
740
741    #[test]
742    fn struct_wire_layout_be() {
743        // tk_struct=15, then encap (len + bo=0 + repo_id + name + count + members).
744        let tc = TypeCode::Struct {
745            repo_id: "X".into(),
746            name: "X".into(),
747            members: vec![],
748            is_except: false,
749        };
750        let mut w = BufferWriter::new(Endianness::Big);
751        tc.encode(&mut w).unwrap();
752        let bytes = w.into_bytes();
753        assert_eq!(&bytes[0..4], &[0, 0, 0, 15], "TCKind tk_struct");
754        // then uint32 encap length, then byte-order octet 0.
755        let encap_len = u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]) as usize;
756        assert_eq!(bytes.len(), 8 + encap_len);
757        assert_eq!(bytes[8], 0, "encap byte-order = big");
758    }
759}