1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//! The five named QoS profiles (RFC 04 §3). The profile vocabulary is closed;
//! registry entries reference these by name and publishers set QoS only
//! through them.
#[cfg(feature = "zenoh")]
use zenoh::qos::{CongestionControl, Priority, Reliability};
/// A named QoS profile: reliability × congestion control × priority.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum QosProfile {
/// `telemetry` default — superseded samples; a drop is replaced.
Sampled,
/// `state` that self-heals by refresh cadence.
Refreshed,
/// `state` written on rare transitions consumers cannot learn late; `events`.
Transition,
/// `state/*/alert/*` — a transition that must arrive promptly.
Alert,
/// `@media` — a stale frame is worthless; the encoder must never block.
Frame,
}
impl QosProfile {
/// Every profile, in RFC 04 §3's order. The vocabulary is closed, so a
/// picker built from this cannot drift from the enum — adding a variant
/// without adding it here is a compile error, not a silently missing
/// option in somebody's UI.
pub const ALL: [QosProfile; 5] = [
Self::Sampled,
Self::Refreshed,
Self::Transition,
Self::Alert,
Self::Frame,
];
pub fn from_name(name: &str) -> Option<Self> {
match name {
"sampled" => Some(Self::Sampled),
"refreshed" => Some(Self::Refreshed),
"transition" => Some(Self::Transition),
"alert" => Some(Self::Alert),
"frame" => Some(Self::Frame),
_ => None,
}
}
pub fn name(self) -> &'static str {
match self {
Self::Sampled => "sampled",
Self::Refreshed => "refreshed",
Self::Transition => "transition",
Self::Alert => "alert",
Self::Frame => "frame",
}
}
/// The `express` axis (RFC 04 §3): bypass transport batching. Plain
/// metadata, so not gated on the `zenoh` feature.
///
/// **`alert` alone, since v1.26.** v1.5 also set it on `frame`, reading
/// both as "latency-shaped". Batching engages only under back-pressure,
/// so `express` is a no-op on an unsaturated link and acts only when the
/// link is already saturated — which is where `frame`, a `drop` profile,
/// is supposed to be shedding stale frames rather than spending
/// per-message overhead on the goodput that decides how many survive.
/// The axis is rare-and-must-arrive versus continuous-and-sheddable.
pub fn express(self) -> bool {
matches!(self, Self::Alert)
}
#[cfg(feature = "zenoh")]
pub fn reliability(self) -> Reliability {
match self {
Self::Sampled | Self::Refreshed | Self::Frame => Reliability::BestEffort,
Self::Transition | Self::Alert => Reliability::Reliable,
}
}
#[cfg(feature = "zenoh")]
pub fn congestion_control(self) -> CongestionControl {
match self {
Self::Sampled | Self::Refreshed | Self::Frame => CongestionControl::Drop,
Self::Transition | Self::Alert => CongestionControl::Block,
}
}
#[cfg(feature = "zenoh")]
pub fn priority(self) -> Priority {
match self {
Self::Sampled => Priority::DataLow,
Self::Refreshed | Self::Transition => Priority::Data,
Self::Alert | Self::Frame => Priority::InteractiveHigh,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn express_is_the_rare_and_must_arrive_profile_alone() {
assert!(QosProfile::Alert.express());
// `frame` lost it in v1.26: express only acts under back-pressure,
// and under back-pressure a `drop` profile is meant to shed.
assert!(!QosProfile::Frame.express());
assert!(!QosProfile::Sampled.express());
assert!(!QosProfile::Refreshed.express());
assert!(!QosProfile::Transition.express());
}
#[test]
fn names_round_trip() {
for p in QosProfile::ALL {
assert_eq!(QosProfile::from_name(p.name()), Some(p));
}
assert_eq!(QosProfile::from_name("telemetry"), None); // old vocabulary
}
/// `ALL` is the vocabulary, not a copy of it: every variant appears once.
#[test]
fn all_is_complete_and_unique() {
let mut names: Vec<&str> = QosProfile::ALL.iter().map(|p| p.name()).collect();
names.sort_unstable();
names.dedup();
assert_eq!(names.len(), QosProfile::ALL.len());
assert_eq!(
names,
["alert", "frame", "refreshed", "sampled", "transition"]
);
}
/// Pins the RFC 04 §3 table.
#[cfg(feature = "zenoh")]
#[test]
fn profile_table() {
assert_eq!(QosProfile::Sampled.priority(), Priority::DataLow);
assert_eq!(
QosProfile::Alert.congestion_control(),
CongestionControl::Block
);
assert_eq!(
QosProfile::Frame.congestion_control(),
CongestionControl::Drop
);
assert_eq!(QosProfile::Frame.priority(), Priority::InteractiveHigh);
assert_eq!(QosProfile::Transition.reliability(), Reliability::Reliable);
}
}