use alloc::boxed::Box;
use alloc::sync::Arc;
use crate::profile::SecurityProfile;
use zerodds_security::logging::LoggingPlugin;
#[derive(Clone, Default)]
pub struct SecurityBundle {
logging_plugin: Option<Arc<dyn LoggingPlugin>>,
profile: Option<Arc<SecurityProfile>>,
}
impl SecurityBundle {
#[must_use]
pub fn builder() -> SecurityBundleBuilder {
SecurityBundleBuilder::default()
}
#[must_use]
pub fn logging_plugin(&self) -> Option<Arc<dyn LoggingPlugin>> {
self.logging_plugin.clone()
}
#[must_use]
pub fn security_profile(&self) -> Option<Arc<SecurityProfile>> {
self.profile.clone()
}
#[must_use]
pub fn has_logging(&self) -> bool {
self.logging_plugin.is_some()
}
#[must_use]
pub fn has_profile(&self) -> bool {
self.profile.is_some()
}
}
#[derive(Default)]
pub struct SecurityBundleBuilder {
logging_plugin: Option<Arc<dyn LoggingPlugin>>,
profile: Option<Arc<SecurityProfile>>,
}
impl SecurityBundleBuilder {
#[must_use]
pub fn logging_plugin(mut self, plugin: Box<dyn LoggingPlugin>) -> Self {
self.logging_plugin = Some(Arc::from(plugin));
self
}
#[must_use]
pub fn security_profile(mut self, profile: SecurityProfile) -> Self {
self.profile = Some(Arc::new(profile));
self
}
#[must_use]
pub fn build(self) -> SecurityBundle {
SecurityBundle {
logging_plugin: self.logging_plugin,
profile: self.profile,
}
}
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
use alloc::sync::Arc;
use core::sync::atomic::{AtomicUsize, Ordering};
use zerodds_security::logging::LogLevel;
#[derive(Default)]
struct CountingLogger {
calls: Arc<AtomicUsize>,
}
impl LoggingPlugin for CountingLogger {
fn log(&self, _level: LogLevel, _participant: [u8; 16], _category: &str, _message: &str) {
self.calls.fetch_add(1, Ordering::SeqCst);
}
fn plugin_class_id(&self) -> &str {
"test:counting"
}
}
#[test]
fn empty_bundle_has_no_logging_or_profile() {
let b = SecurityBundle::builder().build();
assert!(!b.has_logging());
assert!(!b.has_profile());
assert!(b.logging_plugin().is_none());
}
#[test]
fn bundle_carries_the_logger_and_forwards_log_calls() {
let calls = Arc::new(AtomicUsize::new(0));
let logger = CountingLogger {
calls: Arc::clone(&calls),
};
let bundle = SecurityBundle::builder()
.logging_plugin(Box::new(logger))
.build();
assert!(bundle.has_logging());
let plugin = bundle.logging_plugin().expect("logger present");
plugin.log(LogLevel::Warning, [0u8; 16], "auth", "test event");
assert_eq!(calls.load(Ordering::SeqCst), 1);
}
#[test]
fn bundle_is_cloneable_and_shares_the_same_logger() {
let calls = Arc::new(AtomicUsize::new(0));
let bundle = SecurityBundle::builder()
.logging_plugin(Box::new(CountingLogger {
calls: Arc::clone(&calls),
}))
.build();
let clone = bundle.clone();
bundle
.logging_plugin()
.unwrap()
.log(LogLevel::Error, [0u8; 16], "crypto", "a");
clone
.logging_plugin()
.unwrap()
.log(LogLevel::Error, [0u8; 16], "crypto", "b");
assert_eq!(calls.load(Ordering::SeqCst), 2);
}
}