Skip to main content

hiroz_protocol/
entity.rs

1//! ROS 2 entity types for key expression generation.
2
3#![cfg_attr(not(feature = "std"), no_std)]
4
5extern crate alloc;
6
7use alloc::string::String;
8use core::{fmt::Display, ops::Deref};
9use zenoh::{key_expr::KeyExpr, session::ZenohId};
10
11use crate::qos::QosProfile;
12
13/// Placeholder for empty namespace/enclave.
14pub const EMPTY_PLACEHOLDER: &str = "%";
15
16/// Liveliness key expression wrapper.
17#[derive(Clone, Debug, PartialEq, Eq, Hash)]
18pub struct LivelinessKE(pub KeyExpr<'static>);
19
20impl LivelinessKE {
21    pub fn new(ke: KeyExpr<'static>) -> Self {
22        Self(ke)
23    }
24}
25
26impl Deref for LivelinessKE {
27    type Target = KeyExpr<'static>;
28    fn deref(&self) -> &Self::Target {
29        &self.0
30    }
31}
32
33/// Topic key expression wrapper.
34#[derive(Clone, Debug, PartialEq, Eq, Hash)]
35pub struct TopicKE(KeyExpr<'static>);
36
37impl TopicKE {
38    pub fn new(ke: KeyExpr<'static>) -> Self {
39        Self(ke)
40    }
41}
42
43impl Deref for TopicKE {
44    type Target = KeyExpr<'static>;
45    fn deref(&self) -> &Self::Target {
46        &self.0
47    }
48}
49
50impl Display for TopicKE {
51    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
52        write!(f, "{}", self.0)
53    }
54}
55
56/// ROS 2 node entity.
57#[derive(Debug, Hash, Clone, PartialEq, Eq)]
58pub struct NodeEntity {
59    pub domain_id: usize,
60    pub z_id: ZenohId,
61    pub id: usize,
62    pub name: String,
63    pub namespace: String,
64    pub enclave: String,
65}
66
67impl NodeEntity {
68    pub fn new(
69        domain_id: usize,
70        z_id: ZenohId,
71        id: usize,
72        name: String,
73        namespace: String,
74        enclave: String,
75    ) -> Self {
76        Self {
77            domain_id,
78            z_id,
79            id,
80            name,
81            namespace,
82            enclave,
83        }
84    }
85}
86
87/// ROS 2 endpoint kind (publisher, subscription, service, client).
88#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)]
89pub enum EndpointKind {
90    Publisher,
91    Subscription,
92    Service,
93    Client,
94}
95
96impl Display for EndpointKind {
97    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
98        match self {
99            EndpointKind::Publisher => write!(f, "MP"),
100            EndpointKind::Subscription => write!(f, "MS"),
101            EndpointKind::Service => write!(f, "SS"),
102            EndpointKind::Client => write!(f, "SC"),
103        }
104    }
105}
106
107impl core::str::FromStr for EndpointKind {
108    type Err = &'static str;
109
110    fn from_str(s: &str) -> Result<Self, Self::Err> {
111        match s {
112            "MP" => Ok(EndpointKind::Publisher),
113            "MS" => Ok(EndpointKind::Subscription),
114            "SS" => Ok(EndpointKind::Service),
115            "SC" => Ok(EndpointKind::Client),
116            _ => Err("Invalid endpoint kind"),
117        }
118    }
119}
120
121/// ROS 2 entity kind: either a node or an endpoint.
122#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)]
123pub enum EntityKind {
124    Node,
125    Endpoint(EndpointKind),
126}
127
128impl Display for EntityKind {
129    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
130        match self {
131            EntityKind::Node => write!(f, "NN"),
132            EntityKind::Endpoint(k) => k.fmt(f),
133        }
134    }
135}
136
137impl core::str::FromStr for EntityKind {
138    type Err = &'static str;
139
140    fn from_str(s: &str) -> Result<Self, Self::Err> {
141        match s {
142            "NN" => Ok(EntityKind::Node),
143            _ => Ok(EntityKind::Endpoint(s.parse()?)),
144        }
145    }
146}
147
148impl From<EndpointKind> for EntityKind {
149    fn from(kind: EndpointKind) -> Self {
150        EntityKind::Endpoint(kind)
151    }
152}
153
154impl TryFrom<EntityKind> for EndpointKind {
155    type Error = &'static str;
156
157    fn try_from(kind: EntityKind) -> Result<Self, Self::Error> {
158        match kind {
159            EntityKind::Endpoint(k) => Ok(k),
160            EntityKind::Node => Err("Node is not a valid endpoint kind"),
161        }
162    }
163}
164
165/// Type hash (RIHS format).
166#[derive(Debug, Hash, PartialEq, Eq, Clone)]
167pub struct TypeHash {
168    pub version: u8,
169    pub value: [u8; 32],
170}
171
172impl TypeHash {
173    /// Placeholder value for type hash when not supported (ROS 2 Humble)
174    const TYPE_HASH_NOT_SUPPORTED: &'static str = "TypeHashNotSupported";
175
176    pub const fn new(version: u8, value: [u8; 32]) -> Self {
177        Self { version, value }
178    }
179
180    pub const fn zero() -> Self {
181        Self {
182            version: 1,
183            value: [0u8; 32],
184        }
185    }
186
187    pub fn from_rihs_string(rihs_str: &str) -> Option<Self> {
188        // Handle ROS 2 Humble's "not supported" placeholder
189        if rihs_str == Self::TYPE_HASH_NOT_SUPPORTED {
190            return Some(TypeHash::zero());
191        }
192
193        if let Some(hex_part) = rihs_str.strip_prefix("RIHS01_") {
194            if hex_part.len() == 64 {
195                let mut hash_bytes = [0u8; 32];
196                for (i, chunk) in hex_part.as_bytes().chunks(2).enumerate() {
197                    if i < 32 {
198                        if let Ok(byte_val) =
199                            u8::from_str_radix(core::str::from_utf8(chunk).unwrap_or("00"), 16)
200                        {
201                            hash_bytes[i] = byte_val;
202                        } else {
203                            return None;
204                        }
205                    }
206                }
207                return Some(TypeHash {
208                    version: 1,
209                    value: hash_bytes,
210                });
211            }
212        }
213        None
214    }
215
216    /// Returns true when RIHS01 type hashing is available (not the case on ROS 2 Humble).
217    pub fn is_supported() -> bool {
218        cfg!(not(feature = "no-type-hash"))
219    }
220
221    pub fn to_rihs_string(&self) -> String {
222        #[cfg(feature = "no-type-hash")]
223        {
224            // ROS 2 Humble doesn't support type hashing
225            Self::TYPE_HASH_NOT_SUPPORTED.to_string()
226        }
227
228        #[cfg(not(feature = "no-type-hash"))]
229        {
230            use alloc::format;
231            match self.version {
232                1 => {
233                    let hex_str: String = self.value.iter().map(|b| format!("{:02x}", b)).collect();
234                    format!("RIHS01_{}", hex_str)
235                }
236                _ => format!(
237                    "RIHS{:02x}_{}",
238                    self.version,
239                    self.value
240                        .iter()
241                        .map(|b| format!("{:02x}", b))
242                        .collect::<String>()
243                ),
244            }
245        }
246    }
247}
248
249impl Display for TypeHash {
250    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
251        write!(f, "{}", self.to_rihs_string())
252    }
253}
254
255/// Type information (name + hash).
256#[derive(Debug, Hash, PartialEq, Eq, Clone)]
257pub struct TypeInfo {
258    pub name: String,
259    pub hash: TypeHash,
260}
261
262impl TypeInfo {
263    pub fn new(name: &str, hash: TypeHash) -> Self {
264        TypeInfo {
265            name: name.to_string(),
266            hash,
267        }
268    }
269}
270
271/// ROS 2 endpoint entity (publisher, subscription, service, client).
272#[derive(Debug, Hash, PartialEq, Eq, Clone)]
273pub struct EndpointEntity {
274    pub id: usize,
275    pub node: Option<NodeEntity>,
276    pub kind: EndpointKind,
277    pub topic: String,
278    pub type_info: Option<TypeInfo>,
279    pub qos: QosProfile,
280}
281
282impl EndpointEntity {
283    pub fn entity_kind(&self) -> EntityKind {
284        self.kind.into()
285    }
286}
287
288/// Generic ROS 2 entity (node or endpoint).
289#[derive(Debug, Clone, PartialEq, Eq)]
290pub enum Entity {
291    Node(NodeEntity),
292    Endpoint(EndpointEntity),
293}
294
295/// Errors during entity conversion.
296#[derive(Debug, Clone, Copy, PartialEq, Eq)]
297pub enum EntityConversionError {
298    MissingAdminSpace,
299    MissingDomainId,
300    MissingZId,
301    MissingNodeId,
302    MissingEntityId,
303    MissingEntityKind,
304    MissingEnclave,
305    MissingNamespace,
306    MissingNodeName,
307    MissingTopicName,
308    MissingTopicType,
309    MissingTopicHash,
310    MissingTopicQoS,
311    ParsingError,
312    QosDecodeError(crate::qos::QosDecodeError),
313}
314
315impl Display for EntityConversionError {
316    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
317        write!(f, "{:?}", self)
318    }
319}
320
321#[cfg(feature = "std")]
322impl std::error::Error for EntityConversionError {}