extern crate alloc;
use alloc::collections::BTreeMap;
use alloc::vec::Vec;
#[cfg(feature = "std")]
use alloc::borrow::ToOwned;
#[cfg(feature = "std")]
use alloc::string::String;
use core::sync::atomic::{AtomicU64, Ordering};
use crate::access_control::{AccessControlPlugin, AccessDecision, PermissionsHandle};
use crate::authentication::{
AuthenticationPlugin, HandshakeHandle, HandshakeStepOutcome, IdentityHandle, SharedSecretHandle,
};
use crate::data_tagging::{DataTag, DataTaggingPlugin};
use crate::error::{SecurityError, SecurityErrorKind, SecurityResult};
use crate::logging::{LogLevel, LoggingPlugin};
use crate::properties::PropertyList;
#[derive(Debug, Default)]
pub struct MockAuthenticationPlugin {
next_handle: AtomicU64,
handshakes: BTreeMap<HandshakeHandle, SharedSecretHandle>,
}
impl MockAuthenticationPlugin {
#[must_use]
pub fn new() -> Self {
Self::default()
}
fn next_id(&self) -> u64 {
self.next_handle.fetch_add(1, Ordering::Relaxed) + 1
}
}
impl AuthenticationPlugin for MockAuthenticationPlugin {
fn validate_local_identity(
&mut self,
_props: &PropertyList,
_participant_guid: [u8; 16],
) -> SecurityResult<IdentityHandle> {
Ok(IdentityHandle(self.next_id()))
}
fn validate_remote_identity(
&mut self,
_local: IdentityHandle,
_remote_participant_guid: [u8; 16],
_remote_auth_token: &[u8],
) -> SecurityResult<IdentityHandle> {
Ok(IdentityHandle(self.next_id()))
}
fn begin_handshake_request(
&mut self,
_initiator: IdentityHandle,
_replier: IdentityHandle,
) -> SecurityResult<(HandshakeHandle, HandshakeStepOutcome)> {
let h = HandshakeHandle(self.next_id());
Ok((
h,
HandshakeStepOutcome::SendMessage {
token: b"MOCK-REQUEST".to_vec(),
},
))
}
fn begin_handshake_reply(
&mut self,
_replier: IdentityHandle,
_initiator: IdentityHandle,
request_token: &[u8],
) -> SecurityResult<(HandshakeHandle, HandshakeStepOutcome)> {
if request_token != b"MOCK-REQUEST" {
return Err(SecurityError::new(
SecurityErrorKind::AuthenticationFailed,
"mock: unerwartetes Request-Token",
));
}
let h = HandshakeHandle(self.next_id());
Ok((
h,
HandshakeStepOutcome::SendMessage {
token: b"MOCK-REPLY".to_vec(),
},
))
}
fn process_handshake(
&mut self,
handshake: HandshakeHandle,
token: &[u8],
) -> SecurityResult<HandshakeStepOutcome> {
if token == b"MOCK-REPLY" {
let secret = SharedSecretHandle(self.next_id());
self.handshakes.insert(handshake, secret);
return Ok(HandshakeStepOutcome::Complete { secret });
}
if token == b"MOCK-FINAL-ACK" {
let secret = self
.handshakes
.get(&handshake)
.copied()
.unwrap_or(SharedSecretHandle(self.next_id()));
return Ok(HandshakeStepOutcome::Complete { secret });
}
Err(SecurityError::new(
SecurityErrorKind::AuthenticationFailed,
"mock: unbekanntes handshake-token",
))
}
fn shared_secret(&self, handshake: HandshakeHandle) -> SecurityResult<SharedSecretHandle> {
self.handshakes.get(&handshake).copied().ok_or_else(|| {
SecurityError::new(
SecurityErrorKind::BadArgument,
"mock: handshake-handle unbekannt",
)
})
}
fn plugin_class_id(&self) -> &str {
"DDS:Auth:Mock"
}
}
#[derive(Debug, Default)]
pub struct MockAccessControlPlugin {
next_handle: AtomicU64,
}
impl MockAccessControlPlugin {
#[must_use]
pub fn new() -> Self {
Self::default()
}
fn next_id(&self) -> u64 {
self.next_handle.fetch_add(1, Ordering::Relaxed) + 1
}
}
impl AccessControlPlugin for MockAccessControlPlugin {
fn validate_local_permissions(
&mut self,
_local: IdentityHandle,
_participant_guid: [u8; 16],
_props: &PropertyList,
) -> SecurityResult<PermissionsHandle> {
Ok(PermissionsHandle(self.next_id()))
}
fn validate_remote_permissions(
&mut self,
_local: IdentityHandle,
_remote: IdentityHandle,
_remote_permissions_token: &[u8],
_remote_credential: &[u8],
) -> SecurityResult<PermissionsHandle> {
Ok(PermissionsHandle(self.next_id()))
}
fn check_create_datawriter(
&self,
_p: PermissionsHandle,
_topic: &str,
) -> SecurityResult<AccessDecision> {
Ok(AccessDecision::Permit)
}
fn check_create_datareader(
&self,
_p: PermissionsHandle,
_topic: &str,
) -> SecurityResult<AccessDecision> {
Ok(AccessDecision::Permit)
}
fn check_remote_datawriter_match(
&self,
_l: PermissionsHandle,
_r: PermissionsHandle,
_topic: &str,
) -> SecurityResult<AccessDecision> {
Ok(AccessDecision::Permit)
}
fn check_remote_datareader_match(
&self,
_l: PermissionsHandle,
_r: PermissionsHandle,
_topic: &str,
) -> SecurityResult<AccessDecision> {
Ok(AccessDecision::Permit)
}
fn plugin_class_id(&self) -> &str {
"DDS:Access:Mock"
}
}
#[cfg(feature = "std")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MockLogEntry {
pub level: LogLevel,
pub participant: [u8; 16],
pub category: String,
pub message: String,
}
#[cfg(feature = "std")]
pub type MockLogSink = std::sync::Arc<std::sync::Mutex<Vec<MockLogEntry>>>;
#[cfg(feature = "std")]
pub struct MockLoggingPlugin {
sink: MockLogSink,
}
#[cfg(feature = "std")]
impl MockLoggingPlugin {
#[must_use]
pub fn new(sink: MockLogSink) -> Self {
Self { sink }
}
}
#[cfg(feature = "std")]
impl LoggingPlugin for MockLoggingPlugin {
fn log(&self, level: LogLevel, participant: [u8; 16], category: &str, message: &str) {
if let Ok(mut v) = self.sink.lock() {
v.push(MockLogEntry {
level,
participant,
category: category.to_owned(),
message: message.to_owned(),
});
}
}
fn plugin_class_id(&self) -> &str {
"DDS:Logging:Mock"
}
}
#[derive(Debug, Default)]
pub struct MockDataTaggingPlugin {
tags: BTreeMap<[u8; 16], Vec<DataTag>>,
}
impl MockDataTaggingPlugin {
#[must_use]
pub fn new() -> Self {
Self::default()
}
}
impl DataTaggingPlugin for MockDataTaggingPlugin {
fn set_tags(&mut self, endpoint_guid: [u8; 16], tags: Vec<DataTag>) {
self.tags.insert(endpoint_guid, tags);
}
fn get_tags(&self, endpoint_guid: [u8; 16]) -> Vec<DataTag> {
self.tags.get(&endpoint_guid).cloned().unwrap_or_default()
}
fn plugin_class_id(&self) -> &str {
"DDS:Tagging:Mock"
}
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
#[test]
fn mock_authentication_end_to_end_handshake() {
let mut alice = MockAuthenticationPlugin::new();
let mut bob = MockAuthenticationPlugin::new();
let alice_id = alice
.validate_local_identity(&PropertyList::new(), [0xAA; 16])
.expect("alice identity");
let bob_id = bob
.validate_local_identity(&PropertyList::new(), [0xBB; 16])
.expect("bob identity");
let bob_remote_at_alice = alice
.validate_remote_identity(alice_id, [0xBB; 16], b"mock-bob-token")
.expect("bob-remote");
let alice_remote_at_bob = bob
.validate_remote_identity(bob_id, [0xAA; 16], b"mock-alice-token")
.expect("alice-remote");
let (alice_h, outcome1) = alice
.begin_handshake_request(alice_id, bob_remote_at_alice)
.expect("request");
let request_token = match outcome1 {
HandshakeStepOutcome::SendMessage { token } => token,
other => panic!("erwartet SendMessage, got {other:?}"),
};
let (bob_h, outcome2) = bob
.begin_handshake_reply(bob_id, alice_remote_at_bob, &request_token)
.expect("reply");
let reply_token = match outcome2 {
HandshakeStepOutcome::SendMessage { token } => token,
other => panic!("erwartet SendMessage, got {other:?}"),
};
let outcome3 = alice
.process_handshake(alice_h, &reply_token)
.expect("proc");
let alice_secret = match outcome3 {
HandshakeStepOutcome::Complete { secret } => secret,
other => panic!("erwartet Complete, got {other:?}"),
};
let outcome4 = bob
.process_handshake(bob_h, b"MOCK-FINAL-ACK")
.expect("proc bob");
assert!(matches!(outcome4, HandshakeStepOutcome::Complete { .. }));
let fetched = alice.shared_secret(alice_h).expect("fetch");
assert_eq!(fetched, alice_secret);
}
#[test]
fn mock_access_control_permits_everything() {
let mut ac = MockAccessControlPlugin::new();
let local = IdentityHandle(1);
let perms = ac
.validate_local_permissions(local, [0xAA; 16], &PropertyList::new())
.expect("perms");
assert!(
ac.check_create_datawriter(perms, "Chatter")
.unwrap()
.is_permitted()
);
assert!(
ac.check_create_datareader(perms, "Chatter")
.unwrap()
.is_permitted()
);
}
#[test]
fn auth_plugin_can_be_boxed_as_trait_object() {
let plugin: Box<dyn AuthenticationPlugin> = Box::new(MockAuthenticationPlugin::new());
assert_eq!(plugin.plugin_class_id(), "DDS:Auth:Mock");
}
#[test]
fn mock_data_tagging_set_get_roundtrip() {
let mut tagger = MockDataTaggingPlugin::new();
let g = [0xAB; 16];
let tags = alloc::vec![DataTag {
name: "classification".into(),
value: "secret".into(),
}];
tagger.set_tags(g, tags.clone());
assert_eq!(tagger.get_tags(g), tags);
assert!(tagger.get_tags([0xCD; 16]).is_empty());
assert_eq!(tagger.plugin_class_id(), "DDS:Tagging:Mock");
}
#[cfg(feature = "std")]
#[test]
fn mock_logging_captures_events() {
use std::sync::{Arc, Mutex};
let sink = Arc::new(Mutex::new(Vec::new()));
let logger = MockLoggingPlugin::new(Arc::clone(&sink));
logger.log(LogLevel::Critical, [0u8; 16], "auth.failed", "bad cert");
let captured = sink.lock().unwrap();
assert_eq!(captured.len(), 1);
assert_eq!(captured[0].level, LogLevel::Critical);
assert_eq!(captured[0].category, "auth.failed");
assert_eq!(captured[0].message, "bad cert");
}
}