Skip to main content

hiroz_protocol/
qos.rs

1//! QoS profile encoding/decoding for liveliness tokens.
2
3#![cfg_attr(not(feature = "std"), no_std)]
4
5extern crate alloc;
6
7use alloc::string::String;
8use core::fmt::Display;
9
10/// QoS profile for ROS 2 entities.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
12pub struct QosProfile {
13    pub reliability: QosReliability,
14    pub durability: QosDurability,
15    pub history: QosHistory,
16}
17
18impl QosProfile {
19    /// Encode QoS to string for liveliness token.
20    /// Format matches rmw_zenoh_cpp: [reliability]:[durability]:[history],[depth]:[deadline]:[lifespan]:[liveliness]
21    pub fn encode(&self) -> String {
22        use alloc::format;
23        let default_qos = Self::default();
24
25        // Reliability - empty if default (RMW values: 1=Reliable, 2=BestEffort)
26        let reliability = if self.reliability != default_qos.reliability {
27            match self.reliability {
28                QosReliability::Reliable => "1",
29                QosReliability::BestEffort => "2",
30            }
31        } else {
32            ""
33        };
34
35        // Durability - empty if default (RMW values: 1=TransientLocal, 2=Volatile)
36        let durability = if self.durability != default_qos.durability {
37            match self.durability {
38                QosDurability::TransientLocal => "1",
39                QosDurability::Volatile => "2",
40            }
41        } else {
42            ""
43        };
44
45        // History format: <history_kind>,<depth>
46        // Only include kind if non-default, always include depth
47        let history = match self.history {
48            QosHistory::KeepLast(depth) => {
49                if self.history != default_qos.history {
50                    format!("1,{}", depth)
51                } else {
52                    format!(",{}", depth)
53                }
54            }
55            QosHistory::KeepAll => "2,".to_string(),
56        };
57
58        // Deadline, lifespan, liveliness - use defaults (empty/infinite)
59        let deadline = ",";
60        let lifespan = ",";
61        let liveliness = ",,";
62
63        format!(
64            "{}:{}:{}:{}:{}:{}",
65            reliability, durability, history, deadline, lifespan, liveliness
66        )
67    }
68
69    /// Decode QoS from liveliness token string.
70    pub fn decode(s: &str) -> Result<Self, QosDecodeError> {
71        let fields: alloc::vec::Vec<&str> = s.split(':').collect();
72        if fields.len() < 3 {
73            return Err(QosDecodeError::InvalidFormat);
74        }
75
76        let default_qos = Self::default();
77
78        // Parse reliability (RMW values: 1=Reliable, 2=BestEffort)
79        let reliability = match fields[0] {
80            "" => default_qos.reliability,
81            "1" => QosReliability::Reliable,
82            "2" => QosReliability::BestEffort,
83            _ => return Err(QosDecodeError::InvalidReliability),
84        };
85
86        // Parse durability (RMW values: 1=TransientLocal, 2=Volatile)
87        let durability = match fields[1] {
88            "" => default_qos.durability,
89            "1" => QosDurability::TransientLocal,
90            "2" => QosDurability::Volatile,
91            _ => return Err(QosDecodeError::InvalidDurability),
92        };
93
94        // Parse history: <kind>,<depth>
95        let history_parts: alloc::vec::Vec<&str> = fields[2].split(',').collect();
96        if history_parts.len() < 2 {
97            return Err(QosDecodeError::InvalidHistory);
98        }
99
100        let history = match history_parts[0] {
101            "" | "1" => {
102                // KeepLast - parse depth
103                let depth = history_parts[1]
104                    .parse::<usize>()
105                    .map_err(|_| QosDecodeError::InvalidHistory)?;
106                QosHistory::KeepLast(depth)
107            }
108            "2" => QosHistory::KeepAll,
109            _ => return Err(QosDecodeError::InvalidHistory),
110        };
111
112        Ok(QosProfile {
113            reliability,
114            durability,
115            history,
116        })
117    }
118}
119
120/// QoS reliability policy.
121///
122/// ROS 2 default: Reliable
123#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
124#[repr(u8)]
125pub enum QosReliability {
126    BestEffort = 0,
127    #[default]
128    Reliable = 1,
129}
130
131/// QoS durability policy.
132#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
133#[repr(u8)]
134pub enum QosDurability {
135    #[default]
136    Volatile = 0,
137    TransientLocal = 1,
138}
139
140/// QoS history policy.
141#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
142pub enum QosHistory {
143    KeepLast(usize),
144    KeepAll,
145}
146
147impl Default for QosHistory {
148    fn default() -> Self {
149        QosHistory::KeepLast(10)
150    }
151}
152
153impl QosHistory {
154    pub fn from_depth(depth: usize) -> Self {
155        QosHistory::KeepLast(depth)
156    }
157
158    pub fn depth(&self) -> usize {
159        match self {
160            QosHistory::KeepLast(d) => *d,
161            QosHistory::KeepAll => 0,
162        }
163    }
164}
165
166/// QoS decode errors.
167#[derive(Debug, Clone, Copy, PartialEq, Eq)]
168pub enum QosDecodeError {
169    InvalidFormat,
170    InvalidReliability,
171    InvalidDurability,
172    InvalidHistory,
173}
174
175impl Display for QosDecodeError {
176    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
177        match self {
178            QosDecodeError::InvalidFormat => write!(f, "Invalid QoS format"),
179            QosDecodeError::InvalidReliability => write!(f, "Invalid reliability value"),
180            QosDecodeError::InvalidDurability => write!(f, "Invalid durability value"),
181            QosDecodeError::InvalidHistory => write!(f, "Invalid history value"),
182        }
183    }
184}