Skip to main content

zerodds_types/
type_lookup.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! TypeLookup Service IDL (XTypes 1.3 §7.6.3.3).
4//!
5//! Das ist ein DDS-RPC-Service mit zwei Operationen:
6//! - `getTypes(TypeIdentifier[])` → `TypeObject[]` (Minimal/Complete)
7//! - `getTypeDependencies(TypeIdentifier[], continuation_point)`
8//!   → `TypeIdentifierWithSize[] + continuation_point`
9//!
10//! Wir definieren die IDL-Strukturen manuell. Ein kuenftiger
11//! `zerodds-idlc`-Codegen wird diese aus dem OMG-IDL-Schnipsel direkt
12//! generieren.
13
14use alloc::vec::Vec;
15
16use zerodds_cdr::{BufferReader, BufferWriter, EncodeError};
17
18use crate::error::TypeCodecError;
19use crate::type_identifier::TypeIdentifier;
20use crate::type_information::TypeIdentifierWithSize;
21use crate::type_object::common::{decode_seq, encode_seq};
22use crate::type_object::{CompleteTypeObject, MinimalTypeObject};
23
24// ============================================================================
25// getTypes
26// ============================================================================
27
28/// Request fuer `TypeLookup::getTypes`.
29#[derive(Debug, Clone, Default, PartialEq, Eq)]
30pub struct GetTypesRequest {
31    /// Angefragte TypeIdentifiers (typischerweise EK_MINIMAL/EK_COMPLETE).
32    pub type_ids: Vec<TypeIdentifier>,
33}
34
35impl GetTypesRequest {
36    /// Encode.
37    ///
38    /// # Errors
39    /// Buffer-Overflow.
40    pub fn encode_into(&self, w: &mut BufferWriter) -> Result<(), EncodeError> {
41        encode_seq(w, &self.type_ids, |w, t| t.encode_into(w))
42    }
43
44    /// Decode.
45    ///
46    /// # Errors
47    /// Buffer-Underflow.
48    pub fn decode_from(r: &mut BufferReader<'_>) -> Result<Self, TypeCodecError> {
49        let type_ids = decode_seq(r, |rr| {
50            TypeIdentifier::decode_from(rr).map_err(|e| zerodds_cdr::DecodeError::InvalidString {
51                offset: 0,
52                reason: match e {
53                    zerodds_cdr::DecodeError::UnexpectedEof { .. } => "eof",
54                    _ => "decode",
55                },
56            })
57        })?;
58        Ok(Self { type_ids })
59    }
60}
61
62/// Ein getTypes-Reply-Item: ein TypeObject (Minimal oder Complete).
63/// Der Kind wird im ersten Byte der serialisierten Form diskriminiert
64/// (siehe [`crate::type_object::TypeObject`]).
65///
66/// Variant-Size wie [`crate::type_object::TypeObject`] — Boxing der
67/// `Complete`-Variante waere Mikro-Optimierung mit Refactor-Pflicht
68/// auf vielen Callsites; ReplyTypeObject lebt im TypeLookup-Reply-
69/// Pfad, nicht auf dem Sample-Hot-Path.
70#[allow(clippy::large_enum_variant)]
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub enum ReplyTypeObject {
73    /// Minimal-Variante.
74    Minimal(MinimalTypeObject),
75    /// Complete-Variante.
76    Complete(CompleteTypeObject),
77}
78
79/// Antwort fuer `TypeLookup::getTypes`.
80#[derive(Debug, Clone, Default, PartialEq, Eq)]
81pub struct GetTypesReply {
82    /// Liste der zurueckgelieferten TypeObjects. Nicht-gefundene werden
83    /// typischerweise ausgelassen; der Caller muss nach TypeIdentifier
84    /// matchen (ueber den Hash).
85    pub types: Vec<ReplyTypeObject>,
86}
87
88impl GetTypesReply {
89    /// Encode als sequence<TypeObject>.
90    ///
91    /// # Errors
92    /// Buffer-Overflow.
93    pub fn encode_into(&self, w: &mut BufferWriter) -> Result<(), EncodeError> {
94        encode_seq(w, &self.types, |w, t| match t {
95            ReplyTypeObject::Minimal(m) => {
96                crate::type_object::TypeObject::Minimal(m.clone()).encode_into(w)
97            }
98            ReplyTypeObject::Complete(c) => {
99                crate::type_object::TypeObject::Complete(c.clone()).encode_into(w)
100            }
101        })
102    }
103
104    /// Decode.
105    ///
106    /// # Errors
107    /// Buffer-Underflow / Unknown-TypeKind.
108    pub fn decode_from(r: &mut BufferReader<'_>) -> Result<Self, TypeCodecError> {
109        let n = r.read_u32()? as usize;
110        // DoS-Cap: ein boeser Peer darf uns nicht mit `u32::MAX`-
111        // Type-Objects GB an RAM allozieren. `safe_capacity` kappt
112        // auf noch lesbare Bytes / min-elem-size (1 byte pro TypeObject
113        // als konservative Untergrenze — ein echter TypeObject ist
114        // mind. ~20 byte).
115        let cap = crate::type_object::common::safe_capacity(n, 1, r.remaining());
116        let mut types = Vec::with_capacity(cap);
117        for _ in 0..n {
118            let to = crate::type_object::TypeObject::decode_from(r)?;
119            types.push(match to {
120                crate::type_object::TypeObject::Minimal(m) => ReplyTypeObject::Minimal(m),
121                crate::type_object::TypeObject::Complete(c) => ReplyTypeObject::Complete(c),
122            });
123        }
124        Ok(Self { types })
125    }
126}
127
128// ============================================================================
129// getTypeDependencies
130// ============================================================================
131
132/// Opaque-Continuation-Point fuer paginierte Dependency-Listen.
133/// XTypes spec §7.6.3.3.3 erlaubt bis zu 32 bytes.
134#[derive(Debug, Clone, Default, PartialEq, Eq)]
135pub struct ContinuationPoint(pub Vec<u8>);
136
137impl ContinuationPoint {
138    /// Maximum-Laenge (§7.6.3.3.3).
139    pub const MAX_LEN: usize = 32;
140
141    /// Encode als fixed-length octet[32] — wir kodieren es als
142    /// sequence<octet> aus Vereinfachung (spec erlaubt beides via
143    /// @bound; die meisten Implementierungen nutzen sequence).
144    ///
145    /// # Errors
146    /// Buffer-Overflow.
147    pub fn encode_into(&self, w: &mut BufferWriter) -> Result<(), EncodeError> {
148        let len = u32::try_from(self.0.len().min(Self::MAX_LEN)).unwrap_or(Self::MAX_LEN as u32);
149        w.write_u32(len)?;
150        w.write_bytes(&self.0[..len as usize])
151    }
152
153    /// Decode.
154    ///
155    /// # Errors
156    /// Buffer-Underflow / Laenge > MAX_LEN.
157    pub fn decode_from(r: &mut BufferReader<'_>) -> Result<Self, TypeCodecError> {
158        let len = r.read_u32()? as usize;
159        if len > Self::MAX_LEN {
160            return Err(TypeCodecError::UnknownTypeKind { kind: 0 });
161        }
162        Ok(Self(r.read_bytes(len)?.to_vec()))
163    }
164}
165
166/// Request fuer `TypeLookup::getTypeDependencies`.
167#[derive(Debug, Clone, Default, PartialEq, Eq)]
168pub struct GetTypeDependenciesRequest {
169    /// TypeIds, deren Dependencies wir brauchen.
170    pub type_ids: Vec<TypeIdentifier>,
171    /// Continuation-Point aus vorigem Reply (leer bei erstem Request).
172    pub continuation_point: ContinuationPoint,
173}
174
175impl GetTypeDependenciesRequest {
176    /// Encode.
177    ///
178    /// # Errors
179    /// Buffer-Overflow.
180    pub fn encode_into(&self, w: &mut BufferWriter) -> Result<(), EncodeError> {
181        encode_seq(w, &self.type_ids, |w, t| t.encode_into(w))?;
182        self.continuation_point.encode_into(w)
183    }
184
185    /// Decode.
186    ///
187    /// # Errors
188    /// Buffer-Underflow.
189    pub fn decode_from(r: &mut BufferReader<'_>) -> Result<Self, TypeCodecError> {
190        let type_ids = decode_seq(r, |rr| {
191            TypeIdentifier::decode_from(rr).map_err(|_| zerodds_cdr::DecodeError::InvalidString {
192                offset: 0,
193                reason: "type_id",
194            })
195        })?;
196        let continuation_point = ContinuationPoint::decode_from(r)?;
197        Ok(Self {
198            type_ids,
199            continuation_point,
200        })
201    }
202}
203
204/// Antwort fuer `TypeLookup::getTypeDependencies`.
205#[derive(Debug, Clone, Default, PartialEq, Eq)]
206pub struct GetTypeDependenciesReply {
207    /// Dependencies.
208    pub dependent_typeids: Vec<TypeIdentifierWithSize>,
209    /// Continuation-Point (leer = Ende).
210    pub continuation_point: ContinuationPoint,
211}
212
213impl GetTypeDependenciesReply {
214    /// Encode.
215    ///
216    /// # Errors
217    /// Buffer-Overflow.
218    pub fn encode_into(&self, w: &mut BufferWriter) -> Result<(), EncodeError> {
219        encode_seq(w, &self.dependent_typeids, |w, t| t.encode_into(w))?;
220        self.continuation_point.encode_into(w)
221    }
222
223    /// Decode.
224    ///
225    /// # Errors
226    /// Buffer-Underflow.
227    pub fn decode_from(r: &mut BufferReader<'_>) -> Result<Self, TypeCodecError> {
228        let dependent_typeids = decode_seq(r, |rr| {
229            TypeIdentifierWithSize::decode_from(rr).map_err(|_| {
230                zerodds_cdr::DecodeError::InvalidString {
231                    offset: 0,
232                    reason: "ti_size",
233                }
234            })
235        })?;
236        let continuation_point = ContinuationPoint::decode_from(r)?;
237        Ok(Self {
238            dependent_typeids,
239            continuation_point,
240        })
241    }
242}
243
244// ============================================================================
245// Tests
246// ============================================================================
247
248#[cfg(test)]
249#[allow(clippy::unwrap_used)]
250mod tests {
251    use super::*;
252    use crate::type_identifier::{EquivalenceHash, PrimitiveKind};
253    use zerodds_cdr::{BufferReader, BufferWriter, Endianness};
254
255    fn roundtrip_get_types_request(req: GetTypesRequest) {
256        let mut w = BufferWriter::new(Endianness::Little);
257        req.encode_into(&mut w).unwrap();
258        let bytes = w.into_bytes();
259        let mut r = BufferReader::new(&bytes, Endianness::Little);
260        let decoded = GetTypesRequest::decode_from(&mut r).unwrap();
261        assert_eq!(decoded, req);
262    }
263
264    #[test]
265    fn get_types_request_roundtrips() {
266        roundtrip_get_types_request(GetTypesRequest {
267            type_ids: alloc::vec![
268                TypeIdentifier::EquivalenceHashMinimal(EquivalenceHash([0x01; 14])),
269                TypeIdentifier::Primitive(PrimitiveKind::Int64),
270            ],
271        });
272    }
273
274    #[test]
275    fn continuation_point_roundtrip_and_max_len() {
276        let cp = ContinuationPoint(alloc::vec![0x11, 0x22, 0x33]);
277        let mut w = BufferWriter::new(Endianness::Little);
278        cp.encode_into(&mut w).unwrap();
279        let bytes = w.into_bytes();
280        let mut r = BufferReader::new(&bytes, Endianness::Little);
281        assert_eq!(ContinuationPoint::decode_from(&mut r).unwrap(), cp);
282
283        // MAX_LEN clamp
284        let oversized = ContinuationPoint(alloc::vec![0xFF; 64]);
285        let mut w = BufferWriter::new(Endianness::Little);
286        oversized.encode_into(&mut w).unwrap();
287        let bytes = w.into_bytes();
288        let mut r = BufferReader::new(&bytes, Endianness::Little);
289        let decoded = ContinuationPoint::decode_from(&mut r).unwrap();
290        assert_eq!(decoded.0.len(), ContinuationPoint::MAX_LEN);
291    }
292
293    #[test]
294    fn get_types_reply_roundtrip_with_mixed_minimal_and_complete() {
295        use crate::builder::TypeObjectBuilder;
296        let min = MinimalTypeObject::Struct(
297            TypeObjectBuilder::struct_type("::X")
298                .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
299                .build_minimal(),
300        );
301        let com = CompleteTypeObject::Struct(
302            TypeObjectBuilder::struct_type("::X")
303                .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
304                .build_complete(),
305        );
306        let reply = GetTypesReply {
307            types: alloc::vec![
308                ReplyTypeObject::Minimal(min),
309                ReplyTypeObject::Complete(com),
310            ],
311        };
312        let mut w = BufferWriter::new(Endianness::Little);
313        reply.encode_into(&mut w).unwrap();
314        let bytes = w.into_bytes();
315        let mut r = BufferReader::new(&bytes, Endianness::Little);
316        let decoded = GetTypesReply::decode_from(&mut r).unwrap();
317        assert_eq!(decoded.types.len(), 2);
318    }
319
320    #[test]
321    fn continuation_point_too_large_on_decode_rejected() {
322        // u32-length > MAX_LEN → UnknownTypeKind (Platzhalter fuer
323        // "length out of range"; passende ErrorVariante folgt.)
324        let mut w = BufferWriter::new(Endianness::Little);
325        w.write_u32(100).unwrap(); // > MAX_LEN=32
326        w.write_bytes(&[0u8; 100]).unwrap();
327        let bytes = w.into_bytes();
328        let mut r = BufferReader::new(&bytes, Endianness::Little);
329        let err = ContinuationPoint::decode_from(&mut r).unwrap_err();
330        assert!(matches!(err, TypeCodecError::UnknownTypeKind { .. }));
331    }
332
333    #[test]
334    fn get_type_dependencies_request_reply_roundtrip() {
335        let req = GetTypeDependenciesRequest {
336            type_ids: alloc::vec![TypeIdentifier::EquivalenceHashMinimal(EquivalenceHash(
337                [0xAA; 14]
338            ))],
339            continuation_point: ContinuationPoint::default(),
340        };
341        let mut w = BufferWriter::new(Endianness::Little);
342        req.encode_into(&mut w).unwrap();
343        let bytes = w.into_bytes();
344        let mut r = BufferReader::new(&bytes, Endianness::Little);
345        assert_eq!(
346            GetTypeDependenciesRequest::decode_from(&mut r).unwrap(),
347            req
348        );
349
350        let reply = GetTypeDependenciesReply {
351            dependent_typeids: alloc::vec![TypeIdentifierWithSize {
352                type_id: TypeIdentifier::EquivalenceHashMinimal(EquivalenceHash([0xBB; 14])),
353                typeobject_serialized_size: 128,
354            }],
355            continuation_point: ContinuationPoint(alloc::vec![0x42]),
356        };
357        let mut w = BufferWriter::new(Endianness::Little);
358        reply.encode_into(&mut w).unwrap();
359        let bytes = w.into_bytes();
360        let mut r = BufferReader::new(&bytes, Endianness::Little);
361        assert_eq!(
362            GetTypeDependenciesReply::decode_from(&mut r).unwrap(),
363            reply
364        );
365    }
366
367    // ---- Edge cases for empty and boundary sequences --------------------
368
369    #[test]
370    fn empty_get_types_request_roundtrips() {
371        roundtrip_get_types_request(GetTypesRequest::default());
372    }
373
374    #[test]
375    fn empty_get_types_reply_roundtrips() {
376        let reply = GetTypesReply::default();
377        let mut w = BufferWriter::new(Endianness::Little);
378        reply.encode_into(&mut w).unwrap();
379        let bytes = w.into_bytes();
380        let mut r = BufferReader::new(&bytes, Endianness::Little);
381        let decoded = GetTypesReply::decode_from(&mut r).unwrap();
382        assert_eq!(decoded.types.len(), 0);
383        assert_eq!(decoded, reply);
384    }
385
386    #[test]
387    fn empty_get_type_dependencies_request_roundtrips() {
388        let req = GetTypeDependenciesRequest::default();
389        let mut w = BufferWriter::new(Endianness::Little);
390        req.encode_into(&mut w).unwrap();
391        let bytes = w.into_bytes();
392        let mut r = BufferReader::new(&bytes, Endianness::Little);
393        let decoded = GetTypeDependenciesRequest::decode_from(&mut r).unwrap();
394        assert!(decoded.type_ids.is_empty());
395        assert!(decoded.continuation_point.0.is_empty());
396        assert_eq!(decoded, req);
397    }
398
399    #[test]
400    fn empty_get_type_dependencies_reply_roundtrips() {
401        let reply = GetTypeDependenciesReply::default();
402        let mut w = BufferWriter::new(Endianness::Little);
403        reply.encode_into(&mut w).unwrap();
404        let bytes = w.into_bytes();
405        let mut r = BufferReader::new(&bytes, Endianness::Little);
406        let decoded = GetTypeDependenciesReply::decode_from(&mut r).unwrap();
407        assert!(decoded.dependent_typeids.is_empty());
408        assert!(decoded.continuation_point.0.is_empty());
409    }
410
411    #[test]
412    fn continuation_point_len_zero_encodes_to_four_zero_bytes() {
413        let cp = ContinuationPoint(alloc::vec![]);
414        let mut w = BufferWriter::new(Endianness::Little);
415        cp.encode_into(&mut w).unwrap();
416        let bytes = w.into_bytes();
417        // length prefix only, no payload.
418        assert_eq!(bytes, alloc::vec![0, 0, 0, 0]);
419        let mut r = BufferReader::new(&bytes, Endianness::Little);
420        assert_eq!(ContinuationPoint::decode_from(&mut r).unwrap(), cp);
421    }
422
423    #[test]
424    fn continuation_point_len_max_roundtrips() {
425        let cp = ContinuationPoint(alloc::vec![0xAB; ContinuationPoint::MAX_LEN]);
426        let mut w = BufferWriter::new(Endianness::Little);
427        cp.encode_into(&mut w).unwrap();
428        let bytes = w.into_bytes();
429        let mut r = BufferReader::new(&bytes, Endianness::Little);
430        let decoded = ContinuationPoint::decode_from(&mut r).unwrap();
431        assert_eq!(decoded.0.len(), ContinuationPoint::MAX_LEN);
432        assert_eq!(decoded, cp);
433    }
434
435    #[test]
436    fn continuation_point_len_max_plus_one_on_decode_rejected() {
437        let mut w = BufferWriter::new(Endianness::Little);
438        w.write_u32((ContinuationPoint::MAX_LEN + 1) as u32)
439            .unwrap();
440        w.write_bytes(&[0u8; 33]).unwrap();
441        let bytes = w.into_bytes();
442        let mut r = BufferReader::new(&bytes, Endianness::Little);
443        let err = ContinuationPoint::decode_from(&mut r).unwrap_err();
444        assert!(matches!(err, TypeCodecError::UnknownTypeKind { .. }));
445    }
446
447    #[test]
448    fn reply_type_object_minimal_and_complete_serialize_distinct_discriminator() {
449        use crate::builder::TypeObjectBuilder;
450        let min = ReplyTypeObject::Minimal(MinimalTypeObject::Struct(
451            TypeObjectBuilder::struct_type("::X")
452                .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
453                .build_minimal(),
454        ));
455        let com = ReplyTypeObject::Complete(crate::type_object::CompleteTypeObject::Struct(
456            TypeObjectBuilder::struct_type("::X")
457                .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
458                .build_complete(),
459        ));
460        let reply = GetTypesReply {
461            types: alloc::vec![min, com],
462        };
463        let mut w = BufferWriter::new(Endianness::Little);
464        reply.encode_into(&mut w).unwrap();
465        let bytes = w.into_bytes();
466        // First 4 bytes are the sequence length = 2.
467        assert_eq!(&bytes[..4], &[2, 0, 0, 0]);
468        // Round-trip yields same variants order.
469        let mut r = BufferReader::new(&bytes, Endianness::Little);
470        let decoded = GetTypesReply::decode_from(&mut r).unwrap();
471        assert!(matches!(decoded.types[0], ReplyTypeObject::Minimal(_)));
472        assert!(matches!(decoded.types[1], ReplyTypeObject::Complete(_)));
473    }
474
475    #[test]
476    fn get_type_dependencies_request_with_continuation_payload_roundtrips() {
477        let req = GetTypeDependenciesRequest {
478            type_ids: alloc::vec![],
479            continuation_point: ContinuationPoint(alloc::vec![0xDE, 0xAD, 0xBE, 0xEF]),
480        };
481        let mut w = BufferWriter::new(Endianness::Little);
482        req.encode_into(&mut w).unwrap();
483        let bytes = w.into_bytes();
484        let mut r = BufferReader::new(&bytes, Endianness::Little);
485        assert_eq!(
486            GetTypeDependenciesRequest::decode_from(&mut r).unwrap(),
487            req
488        );
489    }
490}