Skip to main content

zerodds_discovery/
capabilities.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Peer-Capabilities aus dem `BuiltinEndpointSet`-Bitfield.
4//!
5//! Klassifiziert eine remote `ParticipantBuiltinTopicData::builtin_
6//! endpoint_set` u32-Maske in eine high-level Capability-Struct, die
7//! der Caller direkt fuers Matching nutzen kann. Zusaetzlich gibt es
8//! Boolean-Helpers, um pro-Bit-Prufungen lesbar zu halten.
9//!
10//! # Spec-Referenzen
11//!
12//! - DDSI-RTPS 2.5 §9.3.2.12 (Standard-Bits 0..5, 10..11, 28..29)
13//! - DDS-Security 1.2 §7.4.7.1 (Secure-Discovery-Bits 16..27)
14
15extern crate alloc;
16use zerodds_rtps::participant_data::endpoint_flag;
17
18/// High-level Klassifikation eines Peer-`BuiltinEndpointSet`s.
19/// Wird vom DCPS-Runtime aus dem SPDP-Beacon eines Peers errechnet
20/// und an die SEDP-/WLP-/Security-Match-Logic weitergegeben.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
22pub struct PeerCapabilities {
23    /// Roh-Bitmaske, wie sie vom Peer announced wurde — fuer
24    /// Audit-/Diagnose-Pfade.
25    pub raw: u32,
26    /// SPDP-Endpoints (Bits 0/1).
27    pub has_spdp: bool,
28    /// SEDP-Publications-Endpoints (Bits 2/3).
29    pub has_sedp_publications: bool,
30    /// SEDP-Subscriptions-Endpoints (Bits 4/5).
31    pub has_sedp_subscriptions: bool,
32    /// Writer-Liveliness-Protocol (Bits 10/11).
33    pub has_wlp: bool,
34    /// TypeLookup-Service (Bits 12/13, XTypes 1.3 §7.6.3.3.4).
35    pub has_type_lookup: bool,
36    /// XTypes-Topics-Discovery (Bits 28/29).
37    pub has_topics_discovery: bool,
38    /// Secure-Publications-Endpoints (Bits 16/17).
39    pub has_secure_publications: bool,
40    /// Secure-Subscriptions-Endpoints (Bits 18/19).
41    pub has_secure_subscriptions: bool,
42    /// Secure-WLP-Endpoints (Bits 20/21).
43    pub has_secure_wlp: bool,
44    /// Auth-Stateless-Endpoints (Bits 22/23).
45    pub has_stateless_auth: bool,
46    /// Crypto-KeyExchange-Endpoints (Bits 24/25).
47    pub has_volatile_secure: bool,
48    /// Secure-Participant-Discovery (Bits 26/27).
49    pub has_secure_participant: bool,
50}
51
52impl PeerCapabilities {
53    /// Klassifiziert eine Peer-Bitmaske.
54    #[must_use]
55    pub fn from_bits(raw: u32) -> Self {
56        let bit_pair_set = |a: u32, b: u32| -> bool { raw & a != 0 && raw & b != 0 };
57        Self {
58            raw,
59            has_spdp: bit_pair_set(
60                endpoint_flag::PARTICIPANT_ANNOUNCER,
61                endpoint_flag::PARTICIPANT_DETECTOR,
62            ),
63            has_sedp_publications: bit_pair_set(
64                endpoint_flag::PUBLICATIONS_ANNOUNCER,
65                endpoint_flag::PUBLICATIONS_DETECTOR,
66            ),
67            has_sedp_subscriptions: bit_pair_set(
68                endpoint_flag::SUBSCRIPTIONS_ANNOUNCER,
69                endpoint_flag::SUBSCRIPTIONS_DETECTOR,
70            ),
71            has_wlp: bit_pair_set(
72                endpoint_flag::PARTICIPANT_MESSAGE_DATA_WRITER,
73                endpoint_flag::PARTICIPANT_MESSAGE_DATA_READER,
74            ),
75            has_type_lookup: (raw & endpoint_flag::TYPE_LOOKUP_REQUEST != 0)
76                && (raw & endpoint_flag::TYPE_LOOKUP_REPLY != 0),
77            has_topics_discovery: bit_pair_set(
78                endpoint_flag::TOPICS_ANNOUNCER,
79                endpoint_flag::TOPICS_DETECTOR,
80            ),
81            has_secure_publications: bit_pair_set(
82                endpoint_flag::PUBLICATIONS_SECURE_WRITER,
83                endpoint_flag::PUBLICATIONS_SECURE_READER,
84            ),
85            has_secure_subscriptions: bit_pair_set(
86                endpoint_flag::SUBSCRIPTIONS_SECURE_WRITER,
87                endpoint_flag::SUBSCRIPTIONS_SECURE_READER,
88            ),
89            has_secure_wlp: bit_pair_set(
90                endpoint_flag::PARTICIPANT_MESSAGE_SECURE_WRITER,
91                endpoint_flag::PARTICIPANT_MESSAGE_SECURE_READER,
92            ),
93            has_stateless_auth: bit_pair_set(
94                endpoint_flag::PARTICIPANT_STATELESS_MESSAGE_WRITER,
95                endpoint_flag::PARTICIPANT_STATELESS_MESSAGE_READER,
96            ),
97            has_volatile_secure: bit_pair_set(
98                endpoint_flag::PARTICIPANT_VOLATILE_MESSAGE_SECURE_WRITER,
99                endpoint_flag::PARTICIPANT_VOLATILE_MESSAGE_SECURE_READER,
100            ),
101            has_secure_participant: bit_pair_set(
102                endpoint_flag::PARTICIPANT_SECURE_WRITER,
103                endpoint_flag::PARTICIPANT_SECURE_READER,
104            ),
105        }
106    }
107
108    /// `true` wenn der Peer mindestens ein Secure-Discovery-Bit
109    /// gesetzt hat (Sub-Bits 16..27). Wird vom Security-Pfad genutzt,
110    /// um zu entscheiden, ob ein Secure-Handshake versucht werden
111    /// kann oder ob der Peer Plain-Discovery erwartet.
112    #[must_use]
113    pub fn supports_security(&self) -> bool {
114        self.raw & endpoint_flag::ALL_SECURE != 0
115    }
116
117    /// `true` wenn der Peer alle Standard-Bits gesetzt hat
118    /// (Erwartung an einen Spec-konformen DDSI-2.5-Stack ohne
119    /// Security).
120    #[must_use]
121    pub fn fully_standard(&self) -> bool {
122        self.raw & endpoint_flag::ALL_STANDARD == endpoint_flag::ALL_STANDARD
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    #[test]
131    fn capabilities_from_zero_bitmask_is_all_false() {
132        let c = PeerCapabilities::from_bits(0);
133        assert_eq!(c.raw, 0);
134        assert!(!c.has_spdp);
135        assert!(!c.has_sedp_publications);
136        assert!(!c.has_sedp_subscriptions);
137        assert!(!c.has_wlp);
138        assert!(!c.has_topics_discovery);
139        assert!(!c.supports_security());
140        assert!(!c.fully_standard());
141    }
142
143    #[test]
144    fn capabilities_full_standard_bundle() {
145        let c = PeerCapabilities::from_bits(endpoint_flag::ALL_STANDARD);
146        assert!(c.has_spdp);
147        assert!(c.has_sedp_publications);
148        assert!(c.has_sedp_subscriptions);
149        assert!(c.has_wlp);
150        // SEDP-Topics-Endpoints (Bits 28/29) sind per RTPS 2.5 §8.5.4.4
151        // optional und nicht in `ALL_STANDARD` enthalten — DCPSTopic-
152        // Samples werden synthetisch aus Pub/Sub abgeleitet.
153        assert!(!c.has_topics_discovery);
154        assert!(c.fully_standard());
155        // Keine Security-Bits.
156        assert!(!c.supports_security());
157        assert!(!c.has_secure_publications);
158    }
159
160    #[test]
161    fn capabilities_topics_discovery_when_explicitly_added() {
162        // Vendors, die die nativen SEDP-Topics-Endpoints implementieren,
163        // koennen die Bits zur Maske hinzumixen — `has_topics_discovery`
164        // bleibt also korrekt detektierbar.
165        let mask = endpoint_flag::ALL_STANDARD
166            | endpoint_flag::TOPICS_ANNOUNCER
167            | endpoint_flag::TOPICS_DETECTOR;
168        let c = PeerCapabilities::from_bits(mask);
169        assert!(c.has_topics_discovery);
170    }
171
172    #[test]
173    fn capabilities_full_secure_bundle() {
174        let c = PeerCapabilities::from_bits(endpoint_flag::ALL_SECURE);
175        assert!(c.supports_security());
176        assert!(c.has_secure_publications);
177        assert!(c.has_secure_subscriptions);
178        assert!(c.has_secure_wlp);
179        assert!(c.has_stateless_auth);
180        assert!(c.has_volatile_secure);
181        assert!(c.has_secure_participant);
182        // Standard-Bits sind nicht im Bundle.
183        assert!(!c.has_spdp);
184        assert!(!c.has_wlp);
185    }
186
187    #[test]
188    fn capabilities_partial_pair_does_not_count() {
189        // Nur PUBLICATIONS_ANNOUNCER (kein DETECTOR) → has_sedp_publications=false.
190        let c = PeerCapabilities::from_bits(endpoint_flag::PUBLICATIONS_ANNOUNCER);
191        assert!(!c.has_sedp_publications);
192    }
193
194    #[test]
195    fn capabilities_combined_standard_and_secure() {
196        let mask = endpoint_flag::ALL_STANDARD | endpoint_flag::ALL_SECURE;
197        let c = PeerCapabilities::from_bits(mask);
198        assert!(c.fully_standard());
199        assert!(c.supports_security());
200        assert!(c.has_wlp);
201        assert!(c.has_secure_wlp);
202    }
203
204    #[test]
205    fn capabilities_legacy_peer_only_spdp_sedp() {
206        // Legacy-ZeroDDS und alte Cyclone-Builds setzen nur Bits 0..5.
207        let mask = endpoint_flag::PARTICIPANT_ANNOUNCER
208            | endpoint_flag::PARTICIPANT_DETECTOR
209            | endpoint_flag::PUBLICATIONS_ANNOUNCER
210            | endpoint_flag::PUBLICATIONS_DETECTOR
211            | endpoint_flag::SUBSCRIPTIONS_ANNOUNCER
212            | endpoint_flag::SUBSCRIPTIONS_DETECTOR;
213        let c = PeerCapabilities::from_bits(mask);
214        assert!(c.has_spdp);
215        assert!(c.has_sedp_publications);
216        assert!(c.has_sedp_subscriptions);
217        assert!(!c.has_wlp);
218        assert!(!c.has_topics_discovery);
219        assert!(!c.supports_security());
220        assert!(!c.fully_standard()); // WLP/Topics fehlen
221    }
222
223    #[test]
224    fn capabilities_raw_is_preserved() {
225        let mask = 0xDEAD_BEEF & 0x3FFF_FFFFu32; // valid 32-bit-ish
226        let c = PeerCapabilities::from_bits(mask);
227        assert_eq!(c.raw, mask);
228    }
229}