zerodds_security_runtime/
endpoint.rs1use zerodds_rtps::endpoint_security_info::{EndpointSecurityInfo, attrs, plugin_attrs};
26
27use crate::policy::ProtectionLevel;
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub struct EndpointProtection {
33 pub level: ProtectionLevel,
35}
36
37impl EndpointProtection {
38 pub const PLAIN: Self = Self {
40 level: ProtectionLevel::None,
41 };
42
43 #[must_use]
45 pub const fn new(level: ProtectionLevel) -> Self {
46 Self { level }
47 }
48
49 #[must_use]
57 pub fn from_info(info: Option<&EndpointSecurityInfo>) -> Self {
58 let Some(info) = info else {
59 return Self::PLAIN;
60 };
61 if !info.is_valid() {
62 return Self::PLAIN;
65 }
66 if info.is_payload_encrypted() || info.is_submessage_encrypted() {
67 return Self::new(ProtectionLevel::Encrypt);
68 }
69 if info.is_submessage_protected() || info.is_payload_protected() {
70 return Self::new(ProtectionLevel::Sign);
71 }
72 Self::PLAIN
73 }
74
75 #[must_use]
78 pub fn to_info(self) -> EndpointSecurityInfo {
79 let mut endpoint = attrs::IS_VALID;
80 let mut plugin = plugin_attrs::IS_VALID;
81 match self.level {
82 ProtectionLevel::None => {}
83 ProtectionLevel::Sign => {
84 endpoint |= attrs::IS_SUBMESSAGE_PROTECTED;
85 }
86 ProtectionLevel::Encrypt => {
87 endpoint |= attrs::IS_SUBMESSAGE_PROTECTED | attrs::IS_PAYLOAD_PROTECTED;
88 plugin |=
89 plugin_attrs::IS_SUBMESSAGE_ENCRYPTED | plugin_attrs::IS_PAYLOAD_ENCRYPTED;
90 }
91 }
92 EndpointSecurityInfo {
93 endpoint_security_attributes: endpoint,
94 plugin_endpoint_security_attributes: plugin,
95 }
96 }
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101pub enum EndpointMatch {
102 Accept(ProtectionLevel),
105 Reject(MatchRejectReason),
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub enum MatchRejectReason {
112 LegacyPeerVsProtection,
116}
117
118#[must_use]
129pub fn match_endpoints(
130 writer: &EndpointProtection,
131 reader: &EndpointProtection,
132 writer_has_info: bool,
133 reader_has_info: bool,
134) -> EndpointMatch {
135 let writer_wants_protection = !matches!(writer.level, ProtectionLevel::None);
138 let reader_wants_protection = !matches!(reader.level, ProtectionLevel::None);
139 if writer_wants_protection && !reader_has_info {
140 return EndpointMatch::Reject(MatchRejectReason::LegacyPeerVsProtection);
141 }
142 if reader_wants_protection && !writer_has_info {
143 return EndpointMatch::Reject(MatchRejectReason::LegacyPeerVsProtection);
144 }
145 EndpointMatch::Accept(writer.level.stronger(reader.level))
146}
147
148#[cfg(test)]
153#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
154mod tests {
155 use super::*;
156
157 #[test]
160 fn from_info_none_is_plain() {
161 assert_eq!(
162 EndpointProtection::from_info(None),
163 EndpointProtection::PLAIN
164 );
165 }
166
167 #[test]
168 fn from_info_invalid_valid_bit_is_plain() {
169 let info = EndpointSecurityInfo {
172 endpoint_security_attributes: attrs::IS_SUBMESSAGE_PROTECTED,
173 plugin_endpoint_security_attributes: plugin_attrs::IS_SUBMESSAGE_ENCRYPTED,
174 };
175 assert_eq!(
176 EndpointProtection::from_info(Some(&info)),
177 EndpointProtection::PLAIN
178 );
179 }
180
181 #[test]
182 fn from_info_plain_legacy_is_plain() {
183 let info = EndpointSecurityInfo::plain();
184 assert_eq!(
185 EndpointProtection::from_info(Some(&info)),
186 EndpointProtection::PLAIN
187 );
188 }
189
190 #[test]
191 fn from_info_submessage_protected_without_encrypt_is_sign() {
192 let info = EndpointSecurityInfo {
193 endpoint_security_attributes: attrs::IS_VALID | attrs::IS_SUBMESSAGE_PROTECTED,
194 plugin_endpoint_security_attributes: plugin_attrs::IS_VALID,
195 };
196 assert_eq!(
197 EndpointProtection::from_info(Some(&info)).level,
198 ProtectionLevel::Sign
199 );
200 }
201
202 #[test]
203 fn from_info_submessage_encrypted_is_encrypt() {
204 let info = EndpointSecurityInfo {
205 endpoint_security_attributes: attrs::IS_VALID | attrs::IS_SUBMESSAGE_PROTECTED,
206 plugin_endpoint_security_attributes: plugin_attrs::IS_VALID
207 | plugin_attrs::IS_SUBMESSAGE_ENCRYPTED,
208 };
209 assert_eq!(
210 EndpointProtection::from_info(Some(&info)).level,
211 ProtectionLevel::Encrypt
212 );
213 }
214
215 #[test]
216 fn from_info_payload_encrypted_is_encrypt() {
217 let info = EndpointSecurityInfo {
218 endpoint_security_attributes: attrs::IS_VALID | attrs::IS_PAYLOAD_PROTECTED,
219 plugin_endpoint_security_attributes: plugin_attrs::IS_VALID
220 | plugin_attrs::IS_PAYLOAD_ENCRYPTED,
221 };
222 assert_eq!(
223 EndpointProtection::from_info(Some(&info)).level,
224 ProtectionLevel::Encrypt
225 );
226 }
227
228 #[test]
231 fn to_info_none_sets_only_valid_bits() {
232 let info = EndpointProtection::PLAIN.to_info();
233 assert!(info.is_valid());
234 assert!(!info.is_submessage_protected());
235 assert!(!info.is_payload_protected());
236 assert!(!info.is_submessage_encrypted());
237 }
238
239 #[test]
240 fn to_info_sign_sets_submessage_protected_without_encryption() {
241 let info = EndpointProtection::new(ProtectionLevel::Sign).to_info();
242 assert!(info.is_valid());
243 assert!(info.is_submessage_protected());
244 assert!(!info.is_submessage_encrypted());
245 assert!(!info.is_payload_encrypted());
246 }
247
248 #[test]
249 fn to_info_encrypt_sets_both_protection_and_encryption() {
250 let info = EndpointProtection::new(ProtectionLevel::Encrypt).to_info();
251 assert!(info.is_valid());
252 assert!(info.is_submessage_protected());
253 assert!(info.is_payload_protected());
254 assert!(info.is_submessage_encrypted());
255 assert!(info.is_payload_encrypted());
256 }
257
258 #[test]
259 fn to_info_from_info_roundtrip_for_all_levels() {
260 for lvl in [
261 ProtectionLevel::None,
262 ProtectionLevel::Sign,
263 ProtectionLevel::Encrypt,
264 ] {
265 let ep = EndpointProtection::new(lvl);
266 let info = ep.to_info();
267 let back = EndpointProtection::from_info(Some(&info));
268 assert_eq!(back, ep, "roundtrip scheitert fuer {lvl:?}");
269 }
270 }
271
272 #[test]
275 fn dod_writer_encrypt_reader_no_plugin_is_reject() {
276 let w = EndpointProtection::new(ProtectionLevel::Encrypt);
277 let r = EndpointProtection::PLAIN;
278 let result = match_endpoints(
279 &w, &r, true, false,
280 );
281 assert_eq!(
282 result,
283 EndpointMatch::Reject(MatchRejectReason::LegacyPeerVsProtection)
284 );
285 }
286
287 #[test]
288 fn dod_writer_sign_reader_encrypt_accepts_with_encrypt() {
289 let w = EndpointProtection::new(ProtectionLevel::Sign);
290 let r = EndpointProtection::new(ProtectionLevel::Encrypt);
291 let result = match_endpoints(&w, &r, true, true);
292 assert_eq!(result, EndpointMatch::Accept(ProtectionLevel::Encrypt));
293 }
294
295 #[test]
296 fn writer_none_reader_none_both_info_accepts_none() {
297 let w = EndpointProtection::PLAIN;
298 let r = EndpointProtection::PLAIN;
299 let result = match_endpoints(&w, &r, true, true);
300 assert_eq!(result, EndpointMatch::Accept(ProtectionLevel::None));
301 }
302
303 #[test]
304 fn writer_none_reader_none_both_legacy_accepts_none() {
305 let w = EndpointProtection::PLAIN;
307 let r = EndpointProtection::PLAIN;
308 let result = match_endpoints(&w, &r, false, false);
309 assert_eq!(result, EndpointMatch::Accept(ProtectionLevel::None));
310 }
311
312 #[test]
313 fn writer_encrypt_reader_encrypt_accepts_encrypt() {
314 let w = EndpointProtection::new(ProtectionLevel::Encrypt);
315 let r = EndpointProtection::new(ProtectionLevel::Encrypt);
316 let result = match_endpoints(&w, &r, true, true);
317 assert_eq!(result, EndpointMatch::Accept(ProtectionLevel::Encrypt));
318 }
319
320 #[test]
321 fn writer_plain_reader_encrypt_rejects_if_writer_legacy() {
322 let w = EndpointProtection::PLAIN;
324 let r = EndpointProtection::new(ProtectionLevel::Encrypt);
325 let result = match_endpoints(&w, &r, false, true);
326 assert_eq!(
327 result,
328 EndpointMatch::Reject(MatchRejectReason::LegacyPeerVsProtection)
329 );
330 }
331
332 #[test]
333 fn writer_encrypt_reader_sign_accepts_with_encrypt() {
334 let w = EndpointProtection::new(ProtectionLevel::Encrypt);
337 let r = EndpointProtection::new(ProtectionLevel::Sign);
338 let result = match_endpoints(&w, &r, true, true);
339 assert_eq!(result, EndpointMatch::Accept(ProtectionLevel::Encrypt));
340 }
341
342 #[test]
343 fn writer_sign_reader_sign_accepts_with_sign() {
344 let w = EndpointProtection::new(ProtectionLevel::Sign);
345 let r = EndpointProtection::new(ProtectionLevel::Sign);
346 let result = match_endpoints(&w, &r, true, true);
347 assert_eq!(result, EndpointMatch::Accept(ProtectionLevel::Sign));
348 }
349
350 #[test]
351 fn writer_none_with_info_reader_sign_rejects_only_if_writer_cant() {
352 let w = EndpointProtection::PLAIN;
357 let r = EndpointProtection::new(ProtectionLevel::Sign);
358 let result = match_endpoints(&w, &r, true, true);
359 assert_eq!(result, EndpointMatch::Accept(ProtectionLevel::Sign));
360 }
361}