Skip to main content

zerodds_ccm/
dds4ccm.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! DDS for Lightweight CCM 1.1 — Connector-Stub-Layer.
5//!
6//! Spec-Quelle: `omg-ccm/dds4ccm-1.1.pdf`. Wir implementieren die
7//! Connector-Datenmodelle + Codegen-Datenstrukturen + QoS-Profile-
8//! Defaults als Stub-Layer fuer Migrations-Tooling. Die echte
9//! Wire-Bindung erfolgt durch den ZeroDDS-DCPS-Stack
10//! (`crates/dcps/`).
11
12use alloc::string::String;
13use alloc::vec::Vec;
14
15// ===========================================================================
16// §7.2.2.1 DDS-DCPS Basic Port Interfaces (Reader/Writer/Updater)
17// ===========================================================================
18
19/// Spec §7.2.2.1 — Connector-Port-Kind.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum BasicPortKind {
22    /// `CCM_DDS::Reader<T>` — DDS-DataReader-Wrapper.
23    Reader,
24    /// `CCM_DDS::Writer<T>` — DDS-DataWriter-Wrapper.
25    Writer,
26    /// `CCM_DDS::Updater<T>` — Updater-Variant (write+update Ops).
27    Updater,
28    /// `CCM_DDS::Getter<T>` — Pull-Style-Getter.
29    Getter,
30    /// `CCM_DDS::Listener<T>` — Listener-Pattern.
31    Listener,
32    /// `CCM_DDS::StateListener<T>` — State-Listener-Variant.
33    StateListener,
34}
35
36/// Spec §7.2.2.1 — Basic-Port-Definition (typed by T).
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct BasicPort {
39    /// Port-Name (z.B. `"sensor_data"`).
40    pub name: String,
41    /// Port-Kind.
42    pub kind: BasicPortKind,
43    /// Type-Parameter T (Repository-ID).
44    pub type_id: String,
45}
46
47// ===========================================================================
48// §7.2.2.2 DDS-DCPS Extended Ports
49// ===========================================================================
50
51/// Spec §7.2.2.2 — Extended-Port-Kind (Multi-Topic-/Filtered-Variants).
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub enum ExtendedPortKind {
54    /// `MultiTopicReader<T>`.
55    MultiTopicReader,
56    /// `ContentFilteredReader<T>`.
57    ContentFilteredReader,
58    /// `QueryConditionReader<T>`.
59    QueryConditionReader,
60    /// `WaitsetReader<T>`.
61    WaitsetReader,
62}
63
64/// Spec §7.2.2.2 — Extended-Port-Definition.
65#[derive(Debug, Clone, PartialEq, Eq)]
66pub struct ExtendedPort {
67    /// Port-Name.
68    pub name: String,
69    /// Port-Kind.
70    pub kind: ExtendedPortKind,
71    /// Type-Parameter T.
72    pub type_id: String,
73}
74
75// ===========================================================================
76// §7.3 Connectors
77// ===========================================================================
78
79/// Spec §7.3.1-§7.3.3 — Connector-Pattern.
80#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81pub enum ConnectorPattern {
82    /// `Base` — generischer DCPS-Connector.
83    Base,
84    /// `StateTransfer` — Pattern §7.3.2 (TransientLocal+KeepLast(1)).
85    StateTransfer,
86    /// `EventTransfer` — Pattern §7.3.3 (Volatile+KeepAll).
87    EventTransfer,
88    /// `DLRL` — DLRL-Cache-Connector (Spec §8.3).
89    Dlrl,
90}
91
92/// Spec §7.3 — Connector-Definition.
93#[derive(Debug, Clone, PartialEq, Eq)]
94pub struct Connector {
95    /// Connector-Name.
96    pub name: String,
97    /// Connector-Pattern.
98    pub pattern: ConnectorPattern,
99    /// Topic-Type (Repository-ID).
100    pub type_id: String,
101    /// Domain-ID.
102    pub domain_id: u32,
103    /// QoS-Profile-Name (Cross-Ref §7.4.3).
104    pub qos_profile: Option<String>,
105    /// Provided Basic-Ports.
106    pub basic_ports: Vec<BasicPort>,
107    /// Provided Extended-Ports.
108    pub extended_ports: Vec<ExtendedPort>,
109}
110
111impl Connector {
112    /// Konstruktor.
113    #[must_use]
114    pub fn new(
115        name: impl Into<String>,
116        pattern: ConnectorPattern,
117        type_id: impl Into<String>,
118    ) -> Self {
119        Self {
120            name: name.into(),
121            pattern,
122            type_id: type_id.into(),
123            domain_id: 0,
124            qos_profile: None,
125            basic_ports: Vec::new(),
126            extended_ports: Vec::new(),
127        }
128    }
129
130    /// Spec §7.4.3 — QoS-Profile binden.
131    pub fn with_qos_profile(mut self, profile: impl Into<String>) -> Self {
132        self.qos_profile = Some(profile.into());
133        self
134    }
135
136    /// Spec §7.4.1 — Domain-ID setzen.
137    #[must_use]
138    pub fn with_domain(mut self, domain_id: u32) -> Self {
139        self.domain_id = domain_id;
140        self
141    }
142
143    /// Fuegt einen Basic-Port hinzu.
144    pub fn add_basic_port(&mut self, p: BasicPort) {
145        self.basic_ports.push(p);
146    }
147
148    /// Fuegt einen Extended-Port hinzu.
149    pub fn add_extended_port(&mut self, p: ExtendedPort) {
150        self.extended_ports.push(p);
151    }
152
153    /// Anzahl Ports gesamt.
154    #[must_use]
155    pub fn port_count(&self) -> usize {
156        self.basic_ports.len() + self.extended_ports.len()
157    }
158}
159
160// ===========================================================================
161// §7.4.4 Threading Policy
162// ===========================================================================
163
164/// Spec §7.4.4 — Threading-Policy fuer Connector-Operations.
165#[derive(Debug, Clone, Copy, PartialEq, Eq)]
166pub enum ConnectorThreadingPolicy {
167    /// `THREAD_PER_CONNECTOR` — separater Thread pro Connector.
168    ThreadPerConnector,
169    /// `SHARED_THREAD_POOL` — globaler Worker-Pool.
170    SharedThreadPool,
171    /// `INVOKE_INLINE` — auf dem Caller-Thread.
172    InvokeInline,
173}
174
175// ===========================================================================
176// Annex E — Pattern-spezifische QoS-Profile
177// ===========================================================================
178
179/// Spec Annex E — Default-QoS-Profile pro Pattern.
180pub mod qos_profiles {
181    /// Spec §7.3.2 — State-Transfer-Default: TransientLocal +
182    /// KeepLast(1) + Reliable.
183    pub const STATE_TRANSFER_DEFAULT: &str = "DDS4CCM:StateTransfer:Default";
184
185    /// Spec §7.3.3 — Event-Transfer-Default: Volatile + KeepAll +
186    /// Reliable.
187    pub const EVENT_TRANSFER_DEFAULT: &str = "DDS4CCM:EventTransfer:Default";
188
189    /// Spec §7.3.1 — Base-Connector-Default: Volatile + KeepLast(10)
190    /// + BestEffort.
191    pub const BASE_DEFAULT: &str = "DDS4CCM:Base:Default";
192
193    /// Spec §8.3 — DLRL-Connector-Default.
194    pub const DLRL_DEFAULT: &str = "DDS4CCM:DLRL:Default";
195}
196
197// ===========================================================================
198// §8 DLRL Ports + Connectors
199// ===========================================================================
200
201/// Spec §8.2.1.1 / §8.2.1.2 — DLRL-Port-Kind.
202#[derive(Debug, Clone, Copy, PartialEq, Eq)]
203pub enum DlrlPortKind {
204    /// `CacheOperation` (§8.2.1.1).
205    CacheOperation,
206    /// `DLRLClass` / `ObjectHome` (§8.2.1.2).
207    ObjectHome,
208}
209
210/// Spec §8 — DLRL-Port-Definition.
211#[derive(Debug, Clone, PartialEq, Eq)]
212pub struct DlrlPort {
213    /// Port-Name.
214    pub name: String,
215    /// Port-Kind.
216    pub kind: DlrlPortKind,
217    /// Type-Parameter (DLRL-Object-Type).
218    pub type_id: String,
219}
220
221// ===========================================================================
222// Annex A/B — IDL3+ Stub
223// ===========================================================================
224
225/// Spec Annex A/B — IDL3+-Output-Form fuer DCPS-/DLRL-Ports.
226#[derive(Debug, Clone, Copy, PartialEq, Eq)]
227pub enum IdlOutputForm {
228    /// IDL3-Compatible (Templated-Modules ausgespiegelt).
229    Idl3Compatible,
230    /// IDL3+ (mit Templated-Modules).
231    Idl3Plus,
232}
233
234// ===========================================================================
235// Tests
236// ===========================================================================
237
238#[cfg(test)]
239#[allow(clippy::expect_used)]
240mod tests {
241    use super::*;
242
243    #[test]
244    fn basic_port_kinds_distinct() {
245        assert_ne!(BasicPortKind::Reader, BasicPortKind::Writer);
246        assert_ne!(BasicPortKind::Updater, BasicPortKind::Getter);
247        assert_ne!(BasicPortKind::Listener, BasicPortKind::StateListener);
248    }
249
250    #[test]
251    fn basic_port_construct() {
252        let p = BasicPort {
253            name: "sensor".into(),
254            kind: BasicPortKind::Reader,
255            type_id: "IDL:demo/Sensor:1.0".into(),
256        };
257        assert_eq!(p.name, "sensor");
258    }
259
260    #[test]
261    fn extended_port_kinds_distinct() {
262        assert_ne!(
263            ExtendedPortKind::MultiTopicReader,
264            ExtendedPortKind::ContentFilteredReader
265        );
266    }
267
268    #[test]
269    fn connector_construct_default_domain_zero() {
270        let c = Connector::new("c", ConnectorPattern::Base, "IDL:T:1.0");
271        assert_eq!(c.domain_id, 0);
272        assert!(c.qos_profile.is_none());
273        assert_eq!(c.port_count(), 0);
274    }
275
276    #[test]
277    fn connector_with_qos_profile() {
278        let c = Connector::new("c", ConnectorPattern::StateTransfer, "IDL:T:1.0")
279            .with_qos_profile(qos_profiles::STATE_TRANSFER_DEFAULT)
280            .with_domain(42);
281        assert_eq!(
282            c.qos_profile.as_deref(),
283            Some("DDS4CCM:StateTransfer:Default")
284        );
285        assert_eq!(c.domain_id, 42);
286    }
287
288    #[test]
289    fn connector_add_basic_port_increments_count() {
290        let mut c = Connector::new("c", ConnectorPattern::Base, "IDL:T:1.0");
291        c.add_basic_port(BasicPort {
292            name: "in".into(),
293            kind: BasicPortKind::Reader,
294            type_id: "IDL:T:1.0".into(),
295        });
296        assert_eq!(c.port_count(), 1);
297    }
298
299    #[test]
300    fn connector_add_extended_port_increments_count() {
301        let mut c = Connector::new("c", ConnectorPattern::Base, "IDL:T:1.0");
302        c.add_extended_port(ExtendedPort {
303            name: "filt".into(),
304            kind: ExtendedPortKind::ContentFilteredReader,
305            type_id: "IDL:T:1.0".into(),
306        });
307        assert_eq!(c.port_count(), 1);
308    }
309
310    #[test]
311    fn connector_pattern_distinct() {
312        assert_ne!(ConnectorPattern::Base, ConnectorPattern::StateTransfer);
313        assert_ne!(ConnectorPattern::EventTransfer, ConnectorPattern::Dlrl);
314    }
315
316    #[test]
317    fn threading_policy_distinct() {
318        assert_ne!(
319            ConnectorThreadingPolicy::ThreadPerConnector,
320            ConnectorThreadingPolicy::SharedThreadPool
321        );
322        assert_ne!(
323            ConnectorThreadingPolicy::SharedThreadPool,
324            ConnectorThreadingPolicy::InvokeInline
325        );
326    }
327
328    #[test]
329    fn qos_profile_constants_match_spec_namespace() {
330        assert!(qos_profiles::STATE_TRANSFER_DEFAULT.starts_with("DDS4CCM:"));
331        assert!(qos_profiles::EVENT_TRANSFER_DEFAULT.starts_with("DDS4CCM:"));
332        assert!(qos_profiles::BASE_DEFAULT.starts_with("DDS4CCM:"));
333        assert!(qos_profiles::DLRL_DEFAULT.starts_with("DDS4CCM:"));
334    }
335
336    #[test]
337    fn dlrl_port_kinds_distinct() {
338        assert_ne!(DlrlPortKind::CacheOperation, DlrlPortKind::ObjectHome);
339    }
340
341    #[test]
342    fn dlrl_port_construct() {
343        let p = DlrlPort {
344            name: "trader_cache".into(),
345            kind: DlrlPortKind::CacheOperation,
346            type_id: "IDL:demo/TraderCache:1.0".into(),
347        };
348        assert_eq!(p.name, "trader_cache");
349    }
350
351    #[test]
352    fn idl_output_form_distinct() {
353        assert_ne!(IdlOutputForm::Idl3Compatible, IdlOutputForm::Idl3Plus);
354    }
355}