Skip to main content

zerodds_types/
type_information.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! TypeInformation (XTypes 1.3 §7.6.3.2.2) — Wrapper fuer die
4//! Discovery von strongly-hashed TypeObjects inkl. transitiver
5//! Abhaengigkeiten.
6//!
7//! Wire-Format:
8//!
9//! ```text
10//! struct TypeIdentifierWithSize {
11//!     TypeIdentifier type_id;
12//!     uint32         typeobject_serialized_size;
13//! };
14//!
15//! struct TypeIdentifierWithDependencies {
16//!     TypeIdentifierWithSize       typeid_with_size;
17//!     int32                        dependent_typeid_count; // -1 = unknown
18//!     sequence<TypeIdentifierWithSize> dependent_typeids;
19//! };
20//!
21//! struct TypeInformation {
22//!     TypeIdentifierWithDependencies minimal;
23//!     TypeIdentifierWithDependencies complete;
24//! };
25//! ```
26//!
27//! Das wird als Payload des `PID_TYPE_INFORMATION` (0x0075) in SEDP
28//! Publikations-/Subscriptions-Announcements uebertragen (T8).
29//!
30//! Die Dependencies beschreiben transitiv benoetigte TypeObjects, die
31//! der Empfaenger ueber den TypeLookup-Service (T11..T15) nachladen
32//! kann, sofern ihm nur der TypeIdentifier bekannt ist.
33
34use alloc::vec::Vec;
35
36use zerodds_cdr::{BufferReader, BufferWriter, EncodeError, Endianness};
37
38use crate::error::TypeCodecError;
39use crate::hash::{compute_complete_hash, compute_minimal_hash};
40use crate::type_identifier::TypeIdentifier;
41use crate::type_object::common::{decode_seq, encode_seq};
42use crate::type_object::{CompleteTypeObject, MinimalTypeObject};
43
44/// TypeIdentifier + Groesse des serialisierten TypeObjects (§7.6.3.2.2).
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct TypeIdentifierWithSize {
47    /// Typ-Referenz (direkt oder strongly-hashed).
48    pub type_id: TypeIdentifier,
49    /// Groesse des serialisierten TypeObjects in Bytes.
50    ///
51    /// Null, wenn `type_id` ein direkt-identifiziertes Primitive ist
52    /// (kein TypeObject braucht zu existieren) oder Groesse unbekannt.
53    pub typeobject_serialized_size: u32,
54}
55
56impl TypeIdentifierWithSize {
57    /// Encode.
58    ///
59    /// # Errors
60    /// Buffer-Overflow.
61    pub fn encode_into(&self, w: &mut BufferWriter) -> Result<(), EncodeError> {
62        self.type_id.encode_into(w)?;
63        w.write_u32(self.typeobject_serialized_size)
64    }
65
66    /// Decode.
67    ///
68    /// # Errors
69    /// Buffer-Underflow.
70    pub fn decode_from(r: &mut BufferReader<'_>) -> Result<Self, TypeCodecError> {
71        let type_id = TypeIdentifier::decode_from(r)?;
72        let typeobject_serialized_size = r.read_u32()?;
73        Ok(Self {
74            type_id,
75            typeobject_serialized_size,
76        })
77    }
78}
79
80/// TypeIdentifier + Abhaengigkeiten (§7.6.3.2.2).
81#[derive(Debug, Clone, PartialEq, Eq)]
82pub struct TypeIdentifierWithDependencies {
83    /// Haupt-Typ.
84    pub typeid_with_size: TypeIdentifierWithSize,
85    /// Anzahl transitiver Dependencies (kann `-1` sein = "unknown").
86    ///
87    /// Wenn `dependent_typeids.len() == dependent_typeid_count`, ist
88    /// die Liste vollstaendig. Bei `-1` kann der Empfaenger via
89    /// TypeLookup `getTypeDependencies` nachladen.
90    pub dependent_typeid_count: i32,
91    /// Transitive Dependencies (nur die, die der Sender kennt).
92    pub dependent_typeids: Vec<TypeIdentifierWithSize>,
93}
94
95impl TypeIdentifierWithDependencies {
96    /// Kurz-Konstruktor fuer Typen ohne bekannte Dependencies.
97    #[must_use]
98    pub fn without_dependencies(typeid_with_size: TypeIdentifierWithSize) -> Self {
99        Self {
100            typeid_with_size,
101            dependent_typeid_count: 0,
102            dependent_typeids: Vec::new(),
103        }
104    }
105
106    /// Encode.
107    ///
108    /// # Errors
109    /// Buffer-Overflow.
110    pub fn encode_into(&self, w: &mut BufferWriter) -> Result<(), EncodeError> {
111        self.typeid_with_size.encode_into(w)?;
112        w.write_u32(self.dependent_typeid_count as u32)?;
113        encode_seq(w, &self.dependent_typeids, |w, t| t.encode_into(w))
114    }
115
116    /// Decode.
117    ///
118    /// # Errors
119    /// Buffer-Underflow / Unknown-Kind-Fehler aus [`TypeIdentifier`].
120    pub fn decode_from(r: &mut BufferReader<'_>) -> Result<Self, TypeCodecError> {
121        let typeid_with_size = TypeIdentifierWithSize::decode_from(r)?;
122        let dependent_typeid_count = r.read_u32()? as i32;
123        let dependent_typeids = decode_seq(r, |rr| {
124            TypeIdentifierWithSize::decode_from(rr).map_err(|e| match e {
125                TypeCodecError::Encode(_) => zerodds_cdr::DecodeError::UnexpectedEof {
126                    needed: 0,
127                    offset: 0,
128                },
129                TypeCodecError::Decode(d) => d,
130                TypeCodecError::UnknownTypeKind { .. } => zerodds_cdr::DecodeError::UnexpectedEof {
131                    needed: 0,
132                    offset: 0,
133                },
134            })
135        })?;
136        Ok(Self {
137            typeid_with_size,
138            dependent_typeid_count,
139            dependent_typeids,
140        })
141    }
142}
143
144/// TypeInformation (§7.6.3.2.2) — tupelt `minimal` und `complete`
145/// TypeIdentifier-Referenzen. Wird als Payload von `PID_TYPE_INFORMATION`
146/// (0x0075) in SEDP-Announcements uebertragen.
147#[derive(Debug, Clone, PartialEq, Eq)]
148pub struct TypeInformation {
149    /// Minimal-TypeIdentifier mit Dependencies.
150    pub minimal: TypeIdentifierWithDependencies,
151    /// Complete-TypeIdentifier mit Dependencies.
152    pub complete: TypeIdentifierWithDependencies,
153}
154
155impl TypeInformation {
156    /// Baut TypeInformation aus einem Minimal/Complete-Paar, ohne
157    /// transitive Dependencies. Nuetzlich wenn der Typ self-contained
158    /// ist (alle Member sind Primitives / plain Collections).
159    ///
160    /// # Errors
161    /// `EncodeError` beim Serialisieren fuer Size/Hash.
162    pub fn from_minimal_and_complete(
163        minimal: &MinimalTypeObject,
164        complete: &CompleteTypeObject,
165    ) -> Result<Self, EncodeError> {
166        let min_hash = compute_minimal_hash(minimal)?;
167        let com_hash = compute_complete_hash(complete)?;
168        // Groesse = Bytes des wrapped TypeObjects (inkl. EquivalenceKind).
169        // Bei >4GB (extrem unrealistisch) echten Fehler liefern statt
170        // stummer Truncation — sonst bricht die Hash-Validation beim Peer
171        // mit kryptischer Fehlermeldung.
172        let min_size = u32::try_from(
173            crate::type_object::TypeObject::Minimal(minimal.clone())
174                .to_bytes_le()?
175                .len(),
176        )
177        .map_err(|_| EncodeError::ValueOutOfRange {
178            message: "minimal TypeObject serialized size exceeds u32::MAX",
179        })?;
180        let com_size = u32::try_from(
181            crate::type_object::TypeObject::Complete(complete.clone())
182                .to_bytes_le()?
183                .len(),
184        )
185        .map_err(|_| EncodeError::ValueOutOfRange {
186            message: "complete TypeObject serialized size exceeds u32::MAX",
187        })?;
188        Ok(Self {
189            minimal: TypeIdentifierWithDependencies::without_dependencies(TypeIdentifierWithSize {
190                type_id: TypeIdentifier::EquivalenceHashMinimal(min_hash),
191                typeobject_serialized_size: min_size,
192            }),
193            complete: TypeIdentifierWithDependencies::without_dependencies(
194                TypeIdentifierWithSize {
195                    type_id: TypeIdentifier::EquivalenceHashComplete(com_hash),
196                    typeobject_serialized_size: com_size,
197                },
198            ),
199        })
200    }
201
202    /// Fuegt eine transitive Dependency zu beiden Seiten hinzu.
203    pub fn add_dependency(
204        &mut self,
205        minimal_dep: TypeIdentifierWithSize,
206        complete_dep: TypeIdentifierWithSize,
207    ) {
208        self.minimal.dependent_typeids.push(minimal_dep);
209        self.minimal.dependent_typeid_count =
210            self.minimal.dependent_typeids.len().min(i32::MAX as usize) as i32;
211        self.complete.dependent_typeids.push(complete_dep);
212        self.complete.dependent_typeid_count =
213            self.complete.dependent_typeids.len().min(i32::MAX as usize) as i32;
214    }
215
216    /// Encode als XCDR-CDR-Bytes (ohne Encapsulation-Header — der wird
217    /// vom PID-Layer geliefert).
218    ///
219    /// # Errors
220    /// Buffer-Overflow.
221    pub fn encode_into(&self, w: &mut BufferWriter) -> Result<(), EncodeError> {
222        self.minimal.encode_into(w)?;
223        self.complete.encode_into(w)
224    }
225
226    /// Decode.
227    ///
228    /// # Errors
229    /// Buffer-Underflow / Unknown-TypeKind.
230    pub fn decode_from(r: &mut BufferReader<'_>) -> Result<Self, TypeCodecError> {
231        let minimal = TypeIdentifierWithDependencies::decode_from(r)?;
232        let complete = TypeIdentifierWithDependencies::decode_from(r)?;
233        Ok(Self { minimal, complete })
234    }
235
236    /// Convenience: LE-Bytes.
237    ///
238    /// # Errors
239    /// Encode.
240    pub fn to_bytes_le(&self) -> Result<Vec<u8>, EncodeError> {
241        let mut w = BufferWriter::new(Endianness::Little);
242        self.encode_into(&mut w)?;
243        Ok(w.into_bytes())
244    }
245
246    /// Convenience: aus LE-Bytes.
247    ///
248    /// # Errors
249    /// Decode / Unknown-TypeKind.
250    pub fn from_bytes_le(bytes: &[u8]) -> Result<Self, TypeCodecError> {
251        let mut r = BufferReader::new(bytes, Endianness::Little);
252        Self::decode_from(&mut r)
253    }
254}
255
256// ============================================================================
257// Tests
258// ============================================================================
259
260#[cfg(test)]
261#[allow(clippy::unwrap_used)]
262mod tests {
263    use super::*;
264    use crate::builder::{Extensibility, TypeObjectBuilder};
265    use crate::type_identifier::{EquivalenceHash, PrimitiveKind};
266
267    fn sample_type_info() -> TypeInformation {
268        let b = TypeObjectBuilder::struct_type("::chat::Chatter")
269            .extensibility(Extensibility::Appendable)
270            .member("id", TypeIdentifier::Primitive(PrimitiveKind::Int64), |m| {
271                m.key()
272            })
273            .member("text", TypeIdentifier::String8Small { bound: 255 }, |m| m);
274        let minimal = MinimalTypeObject::Struct(b.build_minimal());
275        let complete = CompleteTypeObject::Struct(b.build_complete());
276        TypeInformation::from_minimal_and_complete(&minimal, &complete).unwrap()
277    }
278
279    #[test]
280    fn typeinformation_roundtrips() {
281        let ti = sample_type_info();
282        let bytes = ti.to_bytes_le().unwrap();
283        let decoded = TypeInformation::from_bytes_le(&bytes).unwrap();
284        assert_eq!(ti, decoded);
285    }
286
287    #[test]
288    fn typeinformation_minimal_has_minimal_discriminator() {
289        let ti = sample_type_info();
290        assert!(matches!(
291            ti.minimal.typeid_with_size.type_id,
292            TypeIdentifier::EquivalenceHashMinimal(_)
293        ));
294        assert!(matches!(
295            ti.complete.typeid_with_size.type_id,
296            TypeIdentifier::EquivalenceHashComplete(_)
297        ));
298    }
299
300    #[test]
301    fn typeinformation_size_matches_actual_typeobject_bytes() {
302        let b = TypeObjectBuilder::struct_type("::X").member(
303            "a",
304            TypeIdentifier::Primitive(PrimitiveKind::Int32),
305            |m| m,
306        );
307        let minimal = MinimalTypeObject::Struct(b.build_minimal());
308        let complete = CompleteTypeObject::Struct(b.build_complete());
309        let actual_min_size = crate::type_object::TypeObject::Minimal(minimal.clone())
310            .to_bytes_le()
311            .unwrap()
312            .len();
313        let actual_com_size = crate::type_object::TypeObject::Complete(complete.clone())
314            .to_bytes_le()
315            .unwrap()
316            .len();
317        let ti = TypeInformation::from_minimal_and_complete(&minimal, &complete).unwrap();
318        assert_eq!(
319            ti.minimal.typeid_with_size.typeobject_serialized_size,
320            actual_min_size as u32
321        );
322        assert_eq!(
323            ti.complete.typeid_with_size.typeobject_serialized_size,
324            actual_com_size as u32
325        );
326    }
327
328    #[test]
329    fn add_dependency_updates_count_and_list() {
330        let mut ti = sample_type_info();
331        assert_eq!(ti.minimal.dependent_typeid_count, 0);
332        let dep_min = TypeIdentifierWithSize {
333            type_id: TypeIdentifier::EquivalenceHashMinimal(EquivalenceHash([0xAA; 14])),
334            typeobject_serialized_size: 42,
335        };
336        let dep_com = TypeIdentifierWithSize {
337            type_id: TypeIdentifier::EquivalenceHashComplete(EquivalenceHash([0xBB; 14])),
338            typeobject_serialized_size: 84,
339        };
340        ti.add_dependency(dep_min.clone(), dep_com.clone());
341        assert_eq!(ti.minimal.dependent_typeid_count, 1);
342        assert_eq!(ti.minimal.dependent_typeids[0], dep_min);
343        assert_eq!(ti.complete.dependent_typeid_count, 1);
344        assert_eq!(ti.complete.dependent_typeids[0], dep_com);
345
346        // Roundtrip auch mit Dependencies
347        let bytes = ti.to_bytes_le().unwrap();
348        assert_eq!(TypeInformation::from_bytes_le(&bytes).unwrap(), ti);
349    }
350
351    #[test]
352    fn negative_dependent_typeid_count_roundtrips() {
353        // -1 = "unknown/incomplete" (spec-compliant).
354        let mut ti = sample_type_info();
355        ti.minimal.dependent_typeid_count = -1;
356        ti.complete.dependent_typeid_count = -1;
357        let bytes = ti.to_bytes_le().unwrap();
358        let decoded = TypeInformation::from_bytes_le(&bytes).unwrap();
359        assert_eq!(decoded.minimal.dependent_typeid_count, -1);
360        assert_eq!(decoded.complete.dependent_typeid_count, -1);
361    }
362}