Skip to main content

zerodds_rtps/
qos_bridge.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Bridge von SEDP-BuiltinTopicData (Wire) zu zerodds-qos-Policies.
4//!
5//! `as_writer_qos()` / `as_reader_qos()` ziehen die wire-getragenen
6//! QoS-Felder auf eine vollständige `WriterQos`/`ReaderQos`-Form hoch;
7//! restliche Policies bleiben auf Default.
8
9use crate::publication_data::PublicationBuiltinTopicData;
10use crate::subscription_data::SubscriptionBuiltinTopicData;
11
12use zerodds_qos::{DurabilityQosPolicy, ReaderQos, WriterQos};
13
14// ---------- BuiltinTopicData → Qos-Aggregate ----------
15//
16// **Wichtig:** `PublicationBuiltinTopicData` /
17// `SubscriptionBuiltinTopicData` tragen aktuell nur eine Teilmenge
18// der QoS auf Wire (Durability, Reliability). Die übrigen Policies
19// (Deadline, Liveliness, Partition, Ownership, …) bleiben auf den
20// zerodds-qos-Defaults, wenn sie nicht explizit gesetzt werden.
21//
22// Effekt auf `zerodds_qos::check_compatibility`: wenn ein Peer real
23// eine strenge Deadline requestet, wir aber den Default INFINITE
24// annehmen, meldet der Check "Compatible", obwohl die reale Wire-
25// Verbindung den OFFERED_INCOMPATIBLE_QOS-Listener triggern würde.
26//
27// Für lokal konstruierte QoS (z.B. im DCPS-Layer) können Anwendungen
28// die `with_*`-Helpers nutzen, um vollständige QoS in den Bridge-
29// Typen mitzuführen.
30
31impl PublicationBuiltinTopicData {
32    /// Baut aus den Wire-Felder eine `WriterQos`.
33    ///
34    /// **Einschraenkung:** Nur Durability + Reliability werden aus
35    /// `self` uebernommen; alle anderen Policies bleiben auf ihren
36    /// `WriterQos::default()`-Werten. Anwendungen, die gegen den
37    /// discovered Peer matchen wollen, muessen dieser Einschraenkung
38    /// bewusst sein — siehe Modul-Dokumentation.
39    #[must_use]
40    pub fn as_writer_qos(&self) -> WriterQos {
41        WriterQos {
42            durability: DurabilityQosPolicy {
43                kind: self.durability,
44            },
45            reliability: self.reliability,
46            ..WriterQos::default()
47        }
48    }
49
50    /// Wendet eine vollstaendige `WriterQos` auf diesen Builtin-Topic-
51    /// Data-Payload an, soweit Wire-Felder es erlauben.
52    /// Policies, die (noch) nicht serialisiert werden, gehen verloren.
53    #[must_use]
54    pub fn with_writer_qos(mut self, qos: &WriterQos) -> Self {
55        self.durability = qos.durability.kind;
56        self.reliability = qos.reliability;
57        self
58    }
59}
60
61impl SubscriptionBuiltinTopicData {
62    /// Analog [`PublicationBuiltinTopicData::as_writer_qos`] fuer Reader.
63    ///
64    /// **Einschraenkung:** Nur Durability + Reliability; uebrige
65    /// Policies auf `ReaderQos::default()`.
66    #[must_use]
67    pub fn as_reader_qos(&self) -> ReaderQos {
68        ReaderQos {
69            durability: DurabilityQosPolicy {
70                kind: self.durability,
71            },
72            reliability: self.reliability,
73            ..ReaderQos::default()
74        }
75    }
76
77    /// Analog [`PublicationBuiltinTopicData::with_writer_qos`] fuer Reader.
78    #[must_use]
79    pub fn with_reader_qos(mut self, qos: &ReaderQos) -> Self {
80        self.durability = qos.durability.kind;
81        self.reliability = qos.reliability;
82        self
83    }
84}
85
86#[cfg(test)]
87#[allow(clippy::unwrap_used, clippy::unreachable, clippy::panic)]
88mod tests {
89    use super::*;
90    use crate::publication_data::{DurabilityKind, ReliabilityKind, ReliabilityQos};
91    use crate::wire_types::{EntityId, Guid, GuidPrefix};
92    use zerodds_qos::Duration;
93
94    #[test]
95    fn durability_kind_is_reexport_not_duplicate() {
96        // Nach der // Der Test haelt diese Invariante fest — wenn jemand die
97        // Typen wieder duplizieren wollte, bricht er hier.
98        fn assert_same_type<T>(_a: &T, _b: &T) {}
99        let rtps = DurabilityKind::Transient;
100        let qos = zerodds_qos::DurabilityKind::Transient;
101        assert_same_type(&rtps, &qos);
102        assert_eq!(rtps, qos);
103    }
104
105    #[test]
106    fn reliability_kind_is_reexport_not_duplicate() {
107        let rtps = ReliabilityKind::Reliable;
108        let qos = zerodds_qos::ReliabilityKind::Reliable;
109        assert_eq!(rtps, qos);
110    }
111
112    #[test]
113    fn duration_is_reexport_not_duplicate() {
114        let d = Duration::from_secs(7);
115        let qd = zerodds_qos::Duration::from_secs(7);
116        assert_eq!(d, qd);
117    }
118
119    #[test]
120    fn writer_reader_qos_match_by_defaults() {
121        let pub_data = PublicationBuiltinTopicData {
122            key: Guid::new(
123                GuidPrefix::from_bytes([1; 12]),
124                EntityId::user_writer_with_key([0, 0, 1]),
125            ),
126            participant_key: Guid::new(GuidPrefix::from_bytes([1; 12]), EntityId::PARTICIPANT),
127            topic_name: alloc::string::String::from("T"),
128            type_name: alloc::string::String::from("X"),
129            durability: DurabilityKind::TransientLocal,
130            reliability: ReliabilityQos {
131                kind: ReliabilityKind::Reliable,
132                max_blocking_time: Duration {
133                    seconds: 0,
134                    fraction: 0,
135                },
136            },
137            ownership: zerodds_qos::OwnershipKind::Shared,
138            ownership_strength: 0,
139            liveliness: zerodds_qos::LivelinessQosPolicy::default(),
140            deadline: zerodds_qos::DeadlineQosPolicy::default(),
141            lifespan: zerodds_qos::LifespanQosPolicy::default(),
142            partition: alloc::vec::Vec::new(),
143            user_data: alloc::vec::Vec::new(),
144            topic_data: alloc::vec::Vec::new(),
145            group_data: alloc::vec::Vec::new(),
146            type_information: None,
147            data_representation: alloc::vec![2],
148            security_info: None,
149            service_instance_name: None,
150            related_entity_guid: None,
151            topic_aliases: None,
152            type_identifier: zerodds_types::TypeIdentifier::None,
153        };
154        let sub_data = SubscriptionBuiltinTopicData {
155            key: Guid::new(
156                GuidPrefix::from_bytes([2; 12]),
157                EntityId::user_reader_with_key([0, 0, 2]),
158            ),
159            participant_key: Guid::new(GuidPrefix::from_bytes([2; 12]), EntityId::PARTICIPANT),
160            topic_name: alloc::string::String::from("T"),
161            type_name: alloc::string::String::from("X"),
162            durability: DurabilityKind::Volatile,
163            reliability: ReliabilityQos {
164                kind: ReliabilityKind::BestEffort,
165                max_blocking_time: Duration {
166                    seconds: 0,
167                    fraction: 0,
168                },
169            },
170            ownership: zerodds_qos::OwnershipKind::Shared,
171            liveliness: zerodds_qos::LivelinessQosPolicy::default(),
172            deadline: zerodds_qos::DeadlineQosPolicy::default(),
173            partition: alloc::vec::Vec::new(),
174            user_data: alloc::vec::Vec::new(),
175            topic_data: alloc::vec::Vec::new(),
176            group_data: alloc::vec::Vec::new(),
177            type_information: None,
178            data_representation: alloc::vec![2],
179            content_filter: None,
180            security_info: None,
181            service_instance_name: None,
182            related_entity_guid: None,
183            topic_aliases: None,
184            type_identifier: zerodds_types::TypeIdentifier::None,
185        };
186        let wq = pub_data.as_writer_qos();
187        let rq = sub_data.as_reader_qos();
188        assert!(zerodds_qos::check_compatibility(&wq, &rq).is_compatible());
189    }
190
191    /// #24 Round-2-Review: bridged negative-compatibility test.
192    ///
193    /// BestEffort-Writer (discovered) + Reliable-Reader (lokal) darf nach
194    /// Bridge-Konvertierung NICHT kompatibel sein — sonst verschleiert die
195    /// Bridge einen echten QoS-Mismatch.
196    #[test]
197    fn besteffort_writer_reliable_reader_is_incompatible_via_bridge() {
198        let pub_data = PublicationBuiltinTopicData {
199            key: Guid::new(
200                GuidPrefix::from_bytes([1; 12]),
201                EntityId::user_writer_with_key([0, 0, 1]),
202            ),
203            participant_key: Guid::new(GuidPrefix::from_bytes([1; 12]), EntityId::PARTICIPANT),
204            topic_name: alloc::string::String::from("T"),
205            type_name: alloc::string::String::from("X"),
206            durability: DurabilityKind::Volatile,
207            reliability: ReliabilityQos {
208                kind: ReliabilityKind::BestEffort,
209                max_blocking_time: Duration {
210                    seconds: 0,
211                    fraction: 0,
212                },
213            },
214            ownership: zerodds_qos::OwnershipKind::Shared,
215            ownership_strength: 0,
216            liveliness: zerodds_qos::LivelinessQosPolicy::default(),
217            deadline: zerodds_qos::DeadlineQosPolicy::default(),
218            lifespan: zerodds_qos::LifespanQosPolicy::default(),
219            partition: alloc::vec::Vec::new(),
220            user_data: alloc::vec::Vec::new(),
221            topic_data: alloc::vec::Vec::new(),
222            group_data: alloc::vec::Vec::new(),
223            type_information: None,
224            data_representation: alloc::vec![2],
225            security_info: None,
226            service_instance_name: None,
227            related_entity_guid: None,
228            topic_aliases: None,
229            type_identifier: zerodds_types::TypeIdentifier::None,
230        };
231        let sub_data = SubscriptionBuiltinTopicData {
232            key: Guid::new(
233                GuidPrefix::from_bytes([2; 12]),
234                EntityId::user_reader_with_key([0, 0, 2]),
235            ),
236            participant_key: Guid::new(GuidPrefix::from_bytes([2; 12]), EntityId::PARTICIPANT),
237            topic_name: alloc::string::String::from("T"),
238            type_name: alloc::string::String::from("X"),
239            durability: DurabilityKind::Volatile,
240            reliability: ReliabilityQos {
241                kind: ReliabilityKind::Reliable,
242                max_blocking_time: Duration {
243                    seconds: 0,
244                    fraction: 0,
245                },
246            },
247            ownership: zerodds_qos::OwnershipKind::Shared,
248            liveliness: zerodds_qos::LivelinessQosPolicy::default(),
249            deadline: zerodds_qos::DeadlineQosPolicy::default(),
250            partition: alloc::vec::Vec::new(),
251            user_data: alloc::vec::Vec::new(),
252            topic_data: alloc::vec::Vec::new(),
253            group_data: alloc::vec::Vec::new(),
254            type_information: None,
255            data_representation: alloc::vec![2],
256            content_filter: None,
257            security_info: None,
258            service_instance_name: None,
259            related_entity_guid: None,
260            topic_aliases: None,
261            type_identifier: zerodds_types::TypeIdentifier::None,
262        };
263        let wq = pub_data.as_writer_qos();
264        let rq = sub_data.as_reader_qos();
265        let res = zerodds_qos::check_compatibility(&wq, &rq);
266        assert!(!res.is_compatible());
267        match res {
268            zerodds_qos::CompatibilityResult::Incompatible(reasons) => {
269                assert!(
270                    reasons.contains(&zerodds_qos::IncompatibleReason::Reliability),
271                    "expected Reliability reason, got {reasons:?}"
272                );
273            }
274            zerodds_qos::CompatibilityResult::Compatible => {
275                unreachable!("BestEffort writer vs Reliable reader must not match")
276            }
277        }
278    }
279
280    /// Durability-Mismatch: Volatile-Writer vs TransientLocal-Reader →
281    /// durability-Reason.
282    #[test]
283    fn volatile_writer_transient_local_reader_incompatible_via_bridge() {
284        let pub_data = PublicationBuiltinTopicData {
285            key: Guid::new(
286                GuidPrefix::from_bytes([1; 12]),
287                EntityId::user_writer_with_key([0, 0, 1]),
288            ),
289            participant_key: Guid::new(GuidPrefix::from_bytes([1; 12]), EntityId::PARTICIPANT),
290            topic_name: alloc::string::String::from("T"),
291            type_name: alloc::string::String::from("X"),
292            durability: DurabilityKind::Volatile,
293            reliability: ReliabilityQos {
294                kind: ReliabilityKind::Reliable,
295                max_blocking_time: Duration {
296                    seconds: 0,
297                    fraction: 0,
298                },
299            },
300            ownership: zerodds_qos::OwnershipKind::Shared,
301            ownership_strength: 0,
302            liveliness: zerodds_qos::LivelinessQosPolicy::default(),
303            deadline: zerodds_qos::DeadlineQosPolicy::default(),
304            lifespan: zerodds_qos::LifespanQosPolicy::default(),
305            partition: alloc::vec::Vec::new(),
306            user_data: alloc::vec::Vec::new(),
307            topic_data: alloc::vec::Vec::new(),
308            group_data: alloc::vec::Vec::new(),
309            type_information: None,
310            data_representation: alloc::vec![2],
311            security_info: None,
312            service_instance_name: None,
313            related_entity_guid: None,
314            topic_aliases: None,
315            type_identifier: zerodds_types::TypeIdentifier::None,
316        };
317        let sub_data = SubscriptionBuiltinTopicData {
318            key: Guid::new(
319                GuidPrefix::from_bytes([2; 12]),
320                EntityId::user_reader_with_key([0, 0, 2]),
321            ),
322            participant_key: Guid::new(GuidPrefix::from_bytes([2; 12]), EntityId::PARTICIPANT),
323            topic_name: alloc::string::String::from("T"),
324            type_name: alloc::string::String::from("X"),
325            durability: DurabilityKind::TransientLocal,
326            reliability: ReliabilityQos {
327                kind: ReliabilityKind::BestEffort,
328                max_blocking_time: Duration {
329                    seconds: 0,
330                    fraction: 0,
331                },
332            },
333            ownership: zerodds_qos::OwnershipKind::Shared,
334            liveliness: zerodds_qos::LivelinessQosPolicy::default(),
335            deadline: zerodds_qos::DeadlineQosPolicy::default(),
336            partition: alloc::vec::Vec::new(),
337            user_data: alloc::vec::Vec::new(),
338            topic_data: alloc::vec::Vec::new(),
339            group_data: alloc::vec::Vec::new(),
340            type_information: None,
341            data_representation: alloc::vec![2],
342            content_filter: None,
343            security_info: None,
344            service_instance_name: None,
345            related_entity_guid: None,
346            topic_aliases: None,
347            type_identifier: zerodds_types::TypeIdentifier::None,
348        };
349        let wq = pub_data.as_writer_qos();
350        let rq = sub_data.as_reader_qos();
351        let res = zerodds_qos::check_compatibility(&wq, &rq);
352        assert!(!res.is_compatible());
353    }
354}