1extern crate alloc;
20
21use alloc::collections::BTreeMap;
22use alloc::vec::Vec;
23
24#[cfg(feature = "std")]
25use alloc::borrow::ToOwned;
26#[cfg(feature = "std")]
27use alloc::string::String;
28use core::sync::atomic::{AtomicU64, Ordering};
29
30use crate::access_control::{AccessControlPlugin, AccessDecision, PermissionsHandle};
31use crate::authentication::{
32 AuthenticationPlugin, HandshakeHandle, HandshakeStepOutcome, IdentityHandle, SharedSecretHandle,
33};
34use crate::data_tagging::{DataTag, DataTaggingPlugin};
35use crate::error::{SecurityError, SecurityErrorKind, SecurityResult};
36use crate::logging::{LogLevel, LoggingPlugin};
37use crate::properties::PropertyList;
38
39#[derive(Debug, Default)]
46pub struct MockAuthenticationPlugin {
47 next_handle: AtomicU64,
48 handshakes: BTreeMap<HandshakeHandle, SharedSecretHandle>,
49}
50
51impl MockAuthenticationPlugin {
52 #[must_use]
54 pub fn new() -> Self {
55 Self::default()
56 }
57
58 fn next_id(&self) -> u64 {
59 self.next_handle.fetch_add(1, Ordering::Relaxed) + 1
62 }
63}
64
65impl AuthenticationPlugin for MockAuthenticationPlugin {
66 fn validate_local_identity(
67 &mut self,
68 _props: &PropertyList,
69 _participant_guid: [u8; 16],
70 ) -> SecurityResult<IdentityHandle> {
71 Ok(IdentityHandle(self.next_id()))
72 }
73
74 fn validate_remote_identity(
75 &mut self,
76 _local: IdentityHandle,
77 _remote_participant_guid: [u8; 16],
78 _remote_auth_token: &[u8],
79 ) -> SecurityResult<IdentityHandle> {
80 Ok(IdentityHandle(self.next_id()))
81 }
82
83 fn begin_handshake_request(
84 &mut self,
85 _initiator: IdentityHandle,
86 _replier: IdentityHandle,
87 ) -> SecurityResult<(HandshakeHandle, HandshakeStepOutcome)> {
88 let h = HandshakeHandle(self.next_id());
89 Ok((
91 h,
92 HandshakeStepOutcome::SendMessage {
93 token: b"MOCK-REQUEST".to_vec(),
94 },
95 ))
96 }
97
98 fn begin_handshake_reply(
99 &mut self,
100 _replier: IdentityHandle,
101 _initiator: IdentityHandle,
102 request_token: &[u8],
103 ) -> SecurityResult<(HandshakeHandle, HandshakeStepOutcome)> {
104 if request_token != b"MOCK-REQUEST" {
105 return Err(SecurityError::new(
106 SecurityErrorKind::AuthenticationFailed,
107 "mock: unerwartetes Request-Token",
108 ));
109 }
110 let h = HandshakeHandle(self.next_id());
111 Ok((
114 h,
115 HandshakeStepOutcome::SendMessage {
116 token: b"MOCK-REPLY".to_vec(),
117 },
118 ))
119 }
120
121 fn process_handshake(
122 &mut self,
123 handshake: HandshakeHandle,
124 token: &[u8],
125 ) -> SecurityResult<HandshakeStepOutcome> {
126 if token == b"MOCK-REPLY" {
127 let secret = SharedSecretHandle(self.next_id());
128 self.handshakes.insert(handshake, secret);
129 return Ok(HandshakeStepOutcome::Complete { secret });
130 }
131 if token == b"MOCK-FINAL-ACK" {
132 let secret = self
134 .handshakes
135 .get(&handshake)
136 .copied()
137 .unwrap_or(SharedSecretHandle(self.next_id()));
138 return Ok(HandshakeStepOutcome::Complete { secret });
139 }
140 Err(SecurityError::new(
141 SecurityErrorKind::AuthenticationFailed,
142 "mock: unbekanntes handshake-token",
143 ))
144 }
145
146 fn shared_secret(&self, handshake: HandshakeHandle) -> SecurityResult<SharedSecretHandle> {
147 self.handshakes.get(&handshake).copied().ok_or_else(|| {
148 SecurityError::new(
149 SecurityErrorKind::BadArgument,
150 "mock: handshake-handle unbekannt",
151 )
152 })
153 }
154
155 fn plugin_class_id(&self) -> &str {
156 "DDS:Auth:Mock"
157 }
158}
159
160#[derive(Debug, Default)]
166pub struct MockAccessControlPlugin {
167 next_handle: AtomicU64,
168}
169
170impl MockAccessControlPlugin {
171 #[must_use]
173 pub fn new() -> Self {
174 Self::default()
175 }
176
177 fn next_id(&self) -> u64 {
178 self.next_handle.fetch_add(1, Ordering::Relaxed) + 1
179 }
180}
181
182impl AccessControlPlugin for MockAccessControlPlugin {
183 fn validate_local_permissions(
184 &mut self,
185 _local: IdentityHandle,
186 _participant_guid: [u8; 16],
187 _props: &PropertyList,
188 ) -> SecurityResult<PermissionsHandle> {
189 Ok(PermissionsHandle(self.next_id()))
190 }
191
192 fn validate_remote_permissions(
193 &mut self,
194 _local: IdentityHandle,
195 _remote: IdentityHandle,
196 _remote_permissions_token: &[u8],
197 _remote_credential: &[u8],
198 ) -> SecurityResult<PermissionsHandle> {
199 Ok(PermissionsHandle(self.next_id()))
200 }
201
202 fn check_create_datawriter(
203 &self,
204 _p: PermissionsHandle,
205 _topic: &str,
206 ) -> SecurityResult<AccessDecision> {
207 Ok(AccessDecision::Permit)
208 }
209
210 fn check_create_datareader(
211 &self,
212 _p: PermissionsHandle,
213 _topic: &str,
214 ) -> SecurityResult<AccessDecision> {
215 Ok(AccessDecision::Permit)
216 }
217
218 fn check_remote_datawriter_match(
219 &self,
220 _l: PermissionsHandle,
221 _r: PermissionsHandle,
222 _topic: &str,
223 ) -> SecurityResult<AccessDecision> {
224 Ok(AccessDecision::Permit)
225 }
226
227 fn check_remote_datareader_match(
228 &self,
229 _l: PermissionsHandle,
230 _r: PermissionsHandle,
231 _topic: &str,
232 ) -> SecurityResult<AccessDecision> {
233 Ok(AccessDecision::Permit)
234 }
235
236 fn plugin_class_id(&self) -> &str {
237 "DDS:Access:Mock"
238 }
239}
240
241#[cfg(feature = "std")]
247#[derive(Debug, Clone, PartialEq, Eq)]
248pub struct MockLogEntry {
249 pub level: LogLevel,
251 pub participant: [u8; 16],
253 pub category: String,
255 pub message: String,
257}
258
259#[cfg(feature = "std")]
261pub type MockLogSink = std::sync::Arc<std::sync::Mutex<Vec<MockLogEntry>>>;
262
263#[cfg(feature = "std")]
266pub struct MockLoggingPlugin {
267 sink: MockLogSink,
268}
269
270#[cfg(feature = "std")]
271impl MockLoggingPlugin {
272 #[must_use]
274 pub fn new(sink: MockLogSink) -> Self {
275 Self { sink }
276 }
277}
278
279#[cfg(feature = "std")]
280impl LoggingPlugin for MockLoggingPlugin {
281 fn log(&self, level: LogLevel, participant: [u8; 16], category: &str, message: &str) {
282 if let Ok(mut v) = self.sink.lock() {
283 v.push(MockLogEntry {
284 level,
285 participant,
286 category: category.to_owned(),
287 message: message.to_owned(),
288 });
289 }
290 }
291
292 fn plugin_class_id(&self) -> &str {
293 "DDS:Logging:Mock"
294 }
295}
296
297#[derive(Debug, Default)]
305pub struct MockDataTaggingPlugin {
306 tags: BTreeMap<[u8; 16], Vec<DataTag>>,
307}
308
309impl MockDataTaggingPlugin {
310 #[must_use]
312 pub fn new() -> Self {
313 Self::default()
314 }
315}
316
317impl DataTaggingPlugin for MockDataTaggingPlugin {
318 fn set_tags(&mut self, endpoint_guid: [u8; 16], tags: Vec<DataTag>) {
319 self.tags.insert(endpoint_guid, tags);
320 }
321
322 fn get_tags(&self, endpoint_guid: [u8; 16]) -> Vec<DataTag> {
323 self.tags.get(&endpoint_guid).cloned().unwrap_or_default()
324 }
325
326 fn plugin_class_id(&self) -> &str {
327 "DDS:Tagging:Mock"
328 }
329}
330
331#[cfg(test)]
336#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
337mod tests {
338 use super::*;
339
340 #[test]
341 fn mock_authentication_end_to_end_handshake() {
342 let mut alice = MockAuthenticationPlugin::new();
344 let mut bob = MockAuthenticationPlugin::new();
345
346 let alice_id = alice
347 .validate_local_identity(&PropertyList::new(), [0xAA; 16])
348 .expect("alice identity");
349 let bob_id = bob
350 .validate_local_identity(&PropertyList::new(), [0xBB; 16])
351 .expect("bob identity");
352
353 let bob_remote_at_alice = alice
355 .validate_remote_identity(alice_id, [0xBB; 16], b"mock-bob-token")
356 .expect("bob-remote");
357 let alice_remote_at_bob = bob
358 .validate_remote_identity(bob_id, [0xAA; 16], b"mock-alice-token")
359 .expect("alice-remote");
360
361 let (alice_h, outcome1) = alice
363 .begin_handshake_request(alice_id, bob_remote_at_alice)
364 .expect("request");
365 let request_token = match outcome1 {
366 HandshakeStepOutcome::SendMessage { token } => token,
367 other => panic!("erwartet SendMessage, got {other:?}"),
368 };
369
370 let (bob_h, outcome2) = bob
372 .begin_handshake_reply(bob_id, alice_remote_at_bob, &request_token)
373 .expect("reply");
374 let reply_token = match outcome2 {
375 HandshakeStepOutcome::SendMessage { token } => token,
376 other => panic!("erwartet SendMessage, got {other:?}"),
377 };
378
379 let outcome3 = alice
381 .process_handshake(alice_h, &reply_token)
382 .expect("proc");
383 let alice_secret = match outcome3 {
384 HandshakeStepOutcome::Complete { secret } => secret,
385 other => panic!("erwartet Complete, got {other:?}"),
386 };
387
388 let outcome4 = bob
390 .process_handshake(bob_h, b"MOCK-FINAL-ACK")
391 .expect("proc bob");
392 assert!(matches!(outcome4, HandshakeStepOutcome::Complete { .. }));
393
394 let fetched = alice.shared_secret(alice_h).expect("fetch");
396 assert_eq!(fetched, alice_secret);
397 }
398
399 #[test]
400 fn mock_access_control_permits_everything() {
401 let mut ac = MockAccessControlPlugin::new();
402 let local = IdentityHandle(1);
403 let perms = ac
404 .validate_local_permissions(local, [0xAA; 16], &PropertyList::new())
405 .expect("perms");
406 assert!(
407 ac.check_create_datawriter(perms, "Chatter")
408 .unwrap()
409 .is_permitted()
410 );
411 assert!(
412 ac.check_create_datareader(perms, "Chatter")
413 .unwrap()
414 .is_permitted()
415 );
416 }
417
418 #[test]
419 fn auth_plugin_can_be_boxed_as_trait_object() {
420 let plugin: Box<dyn AuthenticationPlugin> = Box::new(MockAuthenticationPlugin::new());
421 assert_eq!(plugin.plugin_class_id(), "DDS:Auth:Mock");
422 }
423
424 #[test]
425 fn mock_data_tagging_set_get_roundtrip() {
426 let mut tagger = MockDataTaggingPlugin::new();
427 let g = [0xAB; 16];
428 let tags = alloc::vec![DataTag {
429 name: "classification".into(),
430 value: "secret".into(),
431 }];
432 tagger.set_tags(g, tags.clone());
433 assert_eq!(tagger.get_tags(g), tags);
434 assert!(tagger.get_tags([0xCD; 16]).is_empty());
435 assert_eq!(tagger.plugin_class_id(), "DDS:Tagging:Mock");
436 }
437
438 #[cfg(feature = "std")]
439 #[test]
440 fn mock_logging_captures_events() {
441 use std::sync::{Arc, Mutex};
442 let sink = Arc::new(Mutex::new(Vec::new()));
443 let logger = MockLoggingPlugin::new(Arc::clone(&sink));
444 logger.log(LogLevel::Critical, [0u8; 16], "auth.failed", "bad cert");
445 let captured = sink.lock().unwrap();
446 assert_eq!(captured.len(), 1);
447 assert_eq!(captured[0].level, LogLevel::Critical);
448 assert_eq!(captured[0].category, "auth.failed");
449 assert_eq!(captured[0].message, "bad cert");
450 }
451}