Skip to main content

pamoja_ros2/
key.rs

1//! Assembling the Zenoh key expression a `rmw_zenoh` peer subscribes to.
2//!
3//! `rmw_zenoh` puts every ROS 2 topic and service on a Zenoh key expression of the form
4//! `<domain_id>/<fully_qualified_name>/<dds_type_name>/<type_hash>`, so a pamoja peer that builds
5//! the same key talks to ROS 2 nodes over Zenoh with no DDS in the path. This assembles that key
6//! from its parts and validates the result as a Zenoh key expression through [`pamoja_zenoh`], so a
7//! malformed key is caught here rather than silently failing to match on the wire.
8
9use crate::name::is_fully_qualified;
10use crate::typehash::{dds_type_name, TypeHash};
11use alloc::format;
12use alloc::string::String;
13
14/// Builds the `rmw_zenoh` key expression for a ROS 2 topic or service.
15///
16/// # Arguments
17///
18/// * `domain_id` - the ROS domain id (the `ROS_DOMAIN_ID`, default 0).
19/// * `fqn` - the fully qualified name (starting with `/`), for example `/chatter`.
20/// * `ros_type` - the interface type as `package/namespace/Type`, for example `std_msgs/msg/String`.
21/// * `hash` - the message [`TypeHash`].
22///
23/// # Returns
24///
25/// `Some(key)` such as `0/chatter/std_msgs::msg::dds_::String_/RIHS01_...`; `None` if `fqn` is not
26/// fully qualified, if `ros_type` is not a valid three-part interface type, or if the assembled key
27/// is somehow not a valid Zenoh key expression.
28///
29/// # Examples
30///
31/// ```
32/// use pamoja_ros2::key::entity_key;
33/// use pamoja_ros2::typehash::TypeHash;
34///
35/// let hash =
36///     TypeHash::parse("RIHS01_df668c740482bbd48fb39d76a70dfd4bd59db1288021743503259e948f6b1a18")
37///         .unwrap();
38/// let key = entity_key(0, "/chatter", "std_msgs/msg/String", &hash).unwrap();
39/// assert_eq!(
40///     key,
41///     "0/chatter/std_msgs::msg::dds_::String_/RIHS01_df668c740482bbd48fb39d76a70dfd4bd59db1288021743503259e948f6b1a18",
42/// );
43/// ```
44pub fn entity_key(domain_id: u32, fqn: &str, ros_type: &str, hash: &TypeHash) -> Option<String> {
45    if !is_fully_qualified(fqn) {
46        return None;
47    }
48    let type_name = dds_type_name(ros_type)?;
49    // `fqn` starts with `/`, so `{domain_id}{fqn}` joins as `0/chatter` with a single separator.
50    let key = format!("{domain_id}{fqn}/{type_name}/{hash}");
51    if pamoja_zenoh::keyexpr::is_valid(&key) {
52        Some(key)
53    } else {
54        None
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    fn hash(text: &str) -> TypeHash {
63        TypeHash::parse(text).unwrap()
64    }
65
66    #[test]
67    fn matches_the_published_topic_key() {
68        let h = hash("RIHS01_df668c740482bbd48fb39d76a70dfd4bd59db1288021743503259e948f6b1a18");
69        let key = entity_key(0, "/chatter", "std_msgs/msg/String", &h).unwrap();
70        assert_eq!(
71            key,
72            "0/chatter/std_msgs::msg::dds_::String_/RIHS01_df668c740482bbd48fb39d76a70dfd4bd59db1288021743503259e948f6b1a18",
73        );
74    }
75
76    #[test]
77    fn matches_the_published_namespaced_key() {
78        let h = hash("RIHS01_df668c740482bbd48fb39d76a70dfd4bd59db1288021743503259e948f6b1a18");
79        let key = entity_key(0, "/robot1/chatter", "std_msgs/msg/String", &h).unwrap();
80        assert_eq!(
81            key,
82            "0/robot1/chatter/std_msgs::msg::dds_::String_/RIHS01_df668c740482bbd48fb39d76a70dfd4bd59db1288021743503259e948f6b1a18",
83        );
84    }
85
86    #[test]
87    fn matches_the_published_service_key() {
88        let h = hash("RIHS01_e118de6bf5eeb66a2491b5bda11202e7b68f198d6f67922cf30364858239c81a");
89        let key = entity_key(2, "/add_two_ints", "example_interfaces/srv/AddTwoInts", &h).unwrap();
90        assert_eq!(
91            key,
92            "2/add_two_ints/example_interfaces::srv::dds_::AddTwoInts_/RIHS01_e118de6bf5eeb66a2491b5bda11202e7b68f198d6f67922cf30364858239c81a",
93        );
94    }
95
96    #[test]
97    fn rejects_bad_inputs() {
98        let h = hash("RIHS01_df668c740482bbd48fb39d76a70dfd4bd59db1288021743503259e948f6b1a18");
99        assert!(entity_key(0, "chatter", "std_msgs/msg/String", &h).is_none()); // not absolute
100        assert!(entity_key(0, "/chatter", "std_msgs/String", &h).is_none()); // bad type
101    }
102}