Skip to main content

zerodds_discovery/type_lookup/
endpoints.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! TypeLookup Builtin-Endpoint-GUIDs — XTypes 1.3 §7.6.3.3.4.
4//!
5//! ## Spec-Mapping
6//!
7//! - 4 Builtin-Endpoints pro Participant: TL_SVC_REQUEST_{WRITER,READER}
8//!   und TL_SVC_REPLY_{WRITER,READER}.
9//! - QoS: Reliability=RELIABLE, Durability=VOLATILE, History=KEEP_LAST(1).
10//! - Topic-Names: `dds.builtin.TOS.<HEX-GUID>` (Service-Instance-Name).
11//!
12//! Dieses Modul liefert die GUIDs + Service-Instance-Name-Formatter.
13//! Die Instantiierung der Reliable-Writer/Reader-Pairs liegt im
14//! DCPS-Layer (siehe `crates/dcps/src/runtime.rs` Builtin-Endpoint-
15//! Spawn-Pfad).
16
17use alloc::format;
18use alloc::string::String;
19
20use zerodds_rtps::wire_types::{EntityId, Guid, GuidPrefix};
21
22/// Topic-Name-Praefix fuer TypeLookup-Service-Topics (§7.6.3.3.4).
23pub const TYPELOOKUP_TOPIC_PREFIX: &str = "dds.builtin.TOS";
24
25/// Bildet den Service-Instance-Namen `dds.builtin.TOS.<HEX>` aus
26/// einem `GuidPrefix` (§7.6.3.3.4 c).
27///
28/// Der Hex-Anteil ist **24 Hex-Chars** (12 Bytes GuidPrefix). Die
29/// Spec spricht von "16-hex" was sich auf den 8-byte-RPC-Service-
30/// Instance-Identifier-Anteil bezieht; in unserer Implementation
31/// nutzen wir den vollen 12-byte GuidPrefix als eindeutigen
32/// Participant-Identifier — kompatibel mit Cyclone DDS' Wahl.
33#[must_use]
34pub fn format_service_instance_name(prefix: &GuidPrefix) -> String {
35    let mut hex = String::with_capacity(24);
36    for b in prefix.0.iter() {
37        hex.push_str(&format!("{b:02x}"));
38    }
39    format!("{TYPELOOKUP_TOPIC_PREFIX}.{hex}")
40}
41
42/// Strikt-spec-treue Variante: nur die ersten 8 Bytes des Prefix
43/// werden als Hex codiert (16 Hex-Chars). Wird von einigen
44/// Implementierungen als "Service-Instance-Identifier" benutzt.
45#[must_use]
46pub fn format_service_instance_name_short(prefix: &GuidPrefix) -> String {
47    let mut hex = String::with_capacity(16);
48    for b in prefix.0[..8].iter() {
49        hex.push_str(&format!("{b:02x}"));
50    }
51    format!("{TYPELOOKUP_TOPIC_PREFIX}.{hex}")
52}
53
54/// Vollstaendiges Wiring der vier Builtin-Endpoint-GUIDs eines
55/// Participants fuer den TypeLookup-Service.
56///
57/// QoS (§7.6.3.3.4): Reliability=RELIABLE, Durability=VOLATILE,
58/// History=KEEP_LAST(1). Wird vom DCPS-Runtime gelesen.
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub struct TypeLookupEndpoints {
61    /// Eigener `GuidPrefix`.
62    pub prefix: GuidPrefix,
63}
64
65impl TypeLookupEndpoints {
66    /// Konstruktor.
67    #[must_use]
68    pub fn new(prefix: GuidPrefix) -> Self {
69        Self { prefix }
70    }
71
72    /// GUID des Request-Writers (Client-Outgoing → Server-Incoming).
73    #[must_use]
74    pub fn request_writer(&self) -> Guid {
75        Guid::new(self.prefix, EntityId::TL_SVC_REQ_WRITER)
76    }
77
78    /// GUID des Request-Readers (Server-Incoming).
79    #[must_use]
80    pub fn request_reader(&self) -> Guid {
81        Guid::new(self.prefix, EntityId::TL_SVC_REQ_READER)
82    }
83
84    /// GUID des Reply-Writers (Server-Outgoing).
85    #[must_use]
86    pub fn reply_writer(&self) -> Guid {
87        Guid::new(self.prefix, EntityId::TL_SVC_REPLY_WRITER)
88    }
89
90    /// GUID des Reply-Readers (Client-Incoming).
91    #[must_use]
92    pub fn reply_reader(&self) -> Guid {
93        Guid::new(self.prefix, EntityId::TL_SVC_REPLY_READER)
94    }
95
96    /// Service-Instance-Name fuer beide Topics (§7.6.3.3.4 c).
97    #[must_use]
98    pub fn service_instance_name(&self) -> String {
99        format_service_instance_name(&self.prefix)
100    }
101
102    /// Alle vier GUIDs als Array — fuer Iterieren bei Endpoint-
103    /// Registrierung im RTPS-Stack.
104    #[must_use]
105    pub fn all_guids(&self) -> [Guid; 4] {
106        [
107            self.request_writer(),
108            self.request_reader(),
109            self.reply_writer(),
110            self.reply_reader(),
111        ]
112    }
113}
114
115#[cfg(test)]
116#[allow(clippy::unwrap_used)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn service_instance_name_format() {
122        let prefix = GuidPrefix::from_bytes([
123            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
124        ]);
125        let name = format_service_instance_name(&prefix);
126        assert_eq!(name, "dds.builtin.TOS.0102030405060708090a0b0c");
127        assert!(name.starts_with(TYPELOOKUP_TOPIC_PREFIX));
128    }
129
130    #[test]
131    fn service_instance_name_short_takes_first_8_bytes() {
132        let prefix = GuidPrefix::from_bytes([
133            0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55,
134        ]);
135        let name = format_service_instance_name_short(&prefix);
136        assert_eq!(name, "dds.builtin.TOS.aabbccddeeff0011");
137    }
138
139    #[test]
140    fn endpoints_have_distinct_entity_ids() {
141        let e = TypeLookupEndpoints::new(GuidPrefix::from_bytes([0; 12]));
142        let g = e.all_guids();
143        for i in 0..g.len() {
144            for j in (i + 1)..g.len() {
145                assert_ne!(g[i].entity_id, g[j].entity_id);
146            }
147        }
148    }
149
150    #[test]
151    fn endpoints_share_prefix() {
152        let prefix = GuidPrefix::from_bytes([0xAA; 12]);
153        let e = TypeLookupEndpoints::new(prefix);
154        for g in e.all_guids() {
155            assert_eq!(g.prefix, prefix);
156        }
157    }
158
159    #[test]
160    fn entity_ids_match_spec_constants() {
161        let e = TypeLookupEndpoints::new(GuidPrefix::from_bytes([1; 12]));
162        assert_eq!(e.request_writer().entity_id, EntityId::TL_SVC_REQ_WRITER);
163        assert_eq!(e.request_reader().entity_id, EntityId::TL_SVC_REQ_READER);
164        assert_eq!(e.reply_writer().entity_id, EntityId::TL_SVC_REPLY_WRITER);
165        assert_eq!(e.reply_reader().entity_id, EntityId::TL_SVC_REPLY_READER);
166    }
167
168    #[test]
169    fn service_instance_name_via_endpoints() {
170        let prefix = GuidPrefix::from_bytes([0x00; 12]);
171        let e = TypeLookupEndpoints::new(prefix);
172        assert_eq!(
173            e.service_instance_name(),
174            format_service_instance_name(&prefix)
175        );
176    }
177}