zerodds_security/
security_topic_qos.rs1use core::fmt;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub struct BuiltinSecurityTopicProfile {
27 pub reliability: ReliabilityKind,
29 pub durability: DurabilityKind,
31 pub history: HistoryKind,
33 pub require_receiver_specific_macs: bool,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum ReliabilityKind {
40 BestEffort,
42 Reliable,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum DurabilityKind {
49 Volatile,
51 TransientLocal,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub enum HistoryKind {
58 KeepLast(u32),
60 KeepAll,
62}
63
64impl fmt::Display for ReliabilityKind {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 f.write_str(match self {
67 Self::BestEffort => "BEST_EFFORT",
68 Self::Reliable => "RELIABLE",
69 })
70 }
71}
72
73#[must_use]
81pub fn stateless_message_profile() -> BuiltinSecurityTopicProfile {
82 BuiltinSecurityTopicProfile {
83 reliability: ReliabilityKind::BestEffort,
84 durability: DurabilityKind::Volatile,
85 history: HistoryKind::KeepLast(1),
86 require_receiver_specific_macs: false,
87 }
88}
89
90#[must_use]
101pub fn volatile_message_secure_profile() -> BuiltinSecurityTopicProfile {
102 BuiltinSecurityTopicProfile {
103 reliability: ReliabilityKind::Reliable,
104 durability: DurabilityKind::Volatile,
105 history: HistoryKind::KeepAll,
106 require_receiver_specific_macs: true,
107 }
108}
109
110pub fn validate_security_topic_profile(
116 topic_name: &str,
117 actual: BuiltinSecurityTopicProfile,
118) -> Result<(), &'static str> {
119 let expected = match topic_name {
120 crate::generic_message::TOPIC_STATELESS_MESSAGE => stateless_message_profile(),
121 crate::generic_message::TOPIC_VOLATILE_MESSAGE_SECURE => volatile_message_secure_profile(),
122 _ => return Err("not a security builtin topic"),
123 };
124 if actual.reliability != expected.reliability {
125 return Err("reliability mismatch");
126 }
127 if actual.durability != expected.durability {
128 return Err("durability mismatch");
129 }
130 if actual.history != expected.history {
131 return Err("history mismatch");
132 }
133 if actual.require_receiver_specific_macs != expected.require_receiver_specific_macs {
134 return Err("receiver-specific-MAC requirement mismatch");
135 }
136 Ok(())
137}
138
139#[cfg(test)]
140#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn stateless_profile_is_best_effort() {
146 let p = stateless_message_profile();
147 assert_eq!(p.reliability, ReliabilityKind::BestEffort);
148 assert_eq!(p.durability, DurabilityKind::Volatile);
149 assert_eq!(p.history, HistoryKind::KeepLast(1));
150 assert!(!p.require_receiver_specific_macs);
151 }
152
153 #[test]
154 fn volatile_secure_is_reliable_with_receiver_macs() {
155 let p = volatile_message_secure_profile();
156 assert_eq!(p.reliability, ReliabilityKind::Reliable);
157 assert_eq!(p.history, HistoryKind::KeepAll);
158 assert!(p.require_receiver_specific_macs);
159 }
160
161 #[test]
162 fn validate_known_topic_with_matching_profile() {
163 validate_security_topic_profile(
164 crate::generic_message::TOPIC_STATELESS_MESSAGE,
165 stateless_message_profile(),
166 )
167 .unwrap();
168 validate_security_topic_profile(
169 crate::generic_message::TOPIC_VOLATILE_MESSAGE_SECURE,
170 volatile_message_secure_profile(),
171 )
172 .unwrap();
173 }
174
175 #[test]
176 fn validate_rejects_wrong_reliability_on_volatile_topic() {
177 let mut wrong = volatile_message_secure_profile();
178 wrong.reliability = ReliabilityKind::BestEffort;
179 let err = validate_security_topic_profile(
180 crate::generic_message::TOPIC_VOLATILE_MESSAGE_SECURE,
181 wrong,
182 )
183 .unwrap_err();
184 assert_eq!(err, "reliability mismatch");
185 }
186
187 #[test]
188 fn validate_rejects_missing_receiver_macs() {
189 let mut wrong = volatile_message_secure_profile();
190 wrong.require_receiver_specific_macs = false;
191 let err = validate_security_topic_profile(
192 crate::generic_message::TOPIC_VOLATILE_MESSAGE_SECURE,
193 wrong,
194 )
195 .unwrap_err();
196 assert_eq!(err, "receiver-specific-MAC requirement mismatch");
197 }
198
199 #[test]
200 fn validate_rejects_unknown_topic() {
201 let err =
202 validate_security_topic_profile("Other", stateless_message_profile()).unwrap_err();
203 assert_eq!(err, "not a security builtin topic");
204 }
205
206 #[test]
207 fn reliability_display_matches_spec() {
208 assert_eq!(
209 alloc::format!("{}", ReliabilityKind::BestEffort),
210 "BEST_EFFORT"
211 );
212 assert_eq!(alloc::format!("{}", ReliabilityKind::Reliable), "RELIABLE");
213 }
214}