use super::gid::Gid;
use super::qos::QosProfile;
pub const ADMIN_SPACE: &str = "@ros2_lv";
const SLASH_REPLACEMENT: char = '%';
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum EntityKind {
Node,
Publisher,
Subscription,
Service,
Client,
}
impl EntityKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Node => "NN",
Self::Publisher => "MP",
Self::Subscription => "MS",
Self::Service => "SS",
Self::Client => "SC",
}
}
fn parse(s: &str) -> Option<Self> {
Some(match s {
"NN" => Self::Node,
"MP" => Self::Publisher,
"MS" => Self::Subscription,
"SS" => Self::Service,
"SC" => Self::Client,
_ => return None,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NodeInfo {
pub domain_id: u32,
pub enclave: String,
pub namespace: String,
pub name: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TopicInfo {
pub name: String,
pub type_name: String,
pub type_hash: String,
pub qos: QosProfile,
}
impl TopicInfo {
#[must_use]
pub fn keyexpr(&self, domain_id: u32) -> String {
format!(
"{domain_id}/{}/{}/{}",
self.name.trim_start_matches('/').trim_end_matches('/'),
self.type_name,
self.type_hash
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Entity {
pub zid: String,
pub nid: u64,
pub id: u64,
pub kind: EntityKind,
pub node: NodeInfo,
pub topic: Option<TopicInfo>,
}
#[must_use]
pub fn mangle(s: &str) -> String {
s.replace('/', "%")
}
#[must_use]
pub fn demangle(s: &str) -> String {
s.replace(SLASH_REPLACEMENT, "/")
}
impl Entity {
#[must_use]
pub fn liveliness_keyexpr(&self, qos_defaults: &QosProfile) -> String {
let mut parts = vec![
ADMIN_SPACE.to_owned(),
self.node.domain_id.to_string(),
self.zid.clone(),
self.nid.to_string(),
self.id.to_string(),
self.kind.as_str().to_owned(),
mangle(&self.node.enclave),
mangle(&self.node.namespace),
mangle(&self.node.name),
];
if let Some(t) = &self.topic {
parts.push(mangle(&t.name));
parts.push(mangle(&t.type_name));
parts.push(mangle(&t.type_hash));
parts.push(t.qos.to_keyexpr(qos_defaults));
}
parts.join("/")
}
#[must_use]
pub fn gid(&self, qos_defaults: &QosProfile) -> Gid {
Gid::from_liveliness_keyexpr(&self.liveliness_keyexpr(qos_defaults))
}
#[must_use]
pub fn topic_keyexpr(&self) -> Option<String> {
self.topic.as_ref().map(|t| t.keyexpr(self.node.domain_id))
}
#[must_use]
pub fn parse(keyexpr: &str, qos_defaults: &QosProfile) -> Option<Self> {
let parts: Vec<&str> = keyexpr.split('/').collect();
if parts.len() < 9 || parts[0] != ADMIN_SPACE {
return None;
}
let kind = EntityKind::parse(parts[5])?;
let node = NodeInfo {
domain_id: parts[1].parse().ok()?,
enclave: demangle(parts[6]),
namespace: demangle(parts[7]),
name: demangle(parts[8]),
};
let topic = if kind == EntityKind::Node {
None
} else {
if parts.len() < 13 {
return None;
}
Some(TopicInfo {
name: demangle(parts[9]),
type_name: demangle(parts[10]),
type_hash: demangle(parts[11]),
qos: QosProfile::from_keyexpr(parts[12], qos_defaults)?,
})
};
Some(Self {
zid: parts[2].to_owned(),
nid: parts[3].parse().ok()?,
id: parts[4].parse().ok()?,
kind,
node,
topic,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
const D: &QosProfile = &QosProfile::RMW_ZENOH_DEFAULT;
fn node() -> NodeInfo {
NodeInfo {
domain_id: 0,
enclave: "/".into(),
namespace: "/".into(),
name: "natnet".into(),
}
}
#[test]
fn node_token() {
let e = Entity {
zid: "a1b2".into(),
nid: 0,
id: 0,
kind: EntityKind::Node,
node: node(),
topic: None,
};
assert_eq!(e.liveliness_keyexpr(D), "@ros2_lv/0/a1b2/0/0/NN/%/%/natnet");
assert_eq!(Entity::parse(&e.liveliness_keyexpr(D), D), Some(e));
}
#[test]
fn publisher_token_and_key() {
let e = Entity {
zid: "a1b2".into(),
nid: 0,
id: 3,
kind: EntityKind::Publisher,
node: NodeInfo {
namespace: "/mocap".into(),
..node()
},
topic: Some(TopicInfo {
name: "/mocap/rigid_bodies/rover".into(),
type_name: "geometry_msgs::msg::dds_::PoseStamped_".into(),
type_hash: "RIHS01_10f3".into(),
qos: QosProfile::sensor_data().adapted(D),
}),
};
assert_eq!(
e.liveliness_keyexpr(D),
"@ros2_lv/0/a1b2/0/3/MP/%/%mocap/natnet/%mocap%rigid_bodies%rover/geometry_msgs::msg::dds_::PoseStamped_/RIHS01_10f3/2::,5:,:,:,,"
);
assert_eq!(
e.topic_keyexpr().unwrap(),
"0/mocap/rigid_bodies/rover/geometry_msgs::msg::dds_::PoseStamped_/RIHS01_10f3"
);
assert_eq!(Entity::parse(&e.liveliness_keyexpr(D), D), Some(e));
}
}