zerodds_security_runtime/
bundle.rs1use alloc::boxed::Box;
28use alloc::sync::Arc;
29
30use crate::profile::SecurityProfile;
31use zerodds_security::logging::LoggingPlugin;
32
33#[derive(Clone, Default)]
40pub struct SecurityBundle {
41 logging_plugin: Option<Arc<dyn LoggingPlugin>>,
42 profile: Option<Arc<SecurityProfile>>,
43}
44
45impl SecurityBundle {
46 #[must_use]
48 pub fn builder() -> SecurityBundleBuilder {
49 SecurityBundleBuilder::default()
50 }
51
52 #[must_use]
55 pub fn logging_plugin(&self) -> Option<Arc<dyn LoggingPlugin>> {
56 self.logging_plugin.clone()
57 }
58
59 #[must_use]
61 pub fn security_profile(&self) -> Option<Arc<SecurityProfile>> {
62 self.profile.clone()
63 }
64
65 #[must_use]
67 pub fn has_logging(&self) -> bool {
68 self.logging_plugin.is_some()
69 }
70
71 #[must_use]
73 pub fn has_profile(&self) -> bool {
74 self.profile.is_some()
75 }
76}
77
78#[derive(Default)]
80pub struct SecurityBundleBuilder {
81 logging_plugin: Option<Arc<dyn LoggingPlugin>>,
82 profile: Option<Arc<SecurityProfile>>,
83}
84
85impl SecurityBundleBuilder {
86 #[must_use]
89 pub fn logging_plugin(mut self, plugin: Box<dyn LoggingPlugin>) -> Self {
90 self.logging_plugin = Some(Arc::from(plugin));
91 self
92 }
93
94 #[must_use]
97 pub fn security_profile(mut self, profile: SecurityProfile) -> Self {
98 self.profile = Some(Arc::new(profile));
99 self
100 }
101
102 #[must_use]
104 pub fn build(self) -> SecurityBundle {
105 SecurityBundle {
106 logging_plugin: self.logging_plugin,
107 profile: self.profile,
108 }
109 }
110}
111
112#[cfg(test)]
113#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
114mod tests {
115 use super::*;
116 use alloc::sync::Arc;
117 use core::sync::atomic::{AtomicUsize, Ordering};
118 use zerodds_security::logging::LogLevel;
119
120 #[derive(Default)]
121 struct CountingLogger {
122 calls: Arc<AtomicUsize>,
123 }
124 impl LoggingPlugin for CountingLogger {
125 fn log(&self, _level: LogLevel, _participant: [u8; 16], _category: &str, _message: &str) {
126 self.calls.fetch_add(1, Ordering::SeqCst);
127 }
128 fn plugin_class_id(&self) -> &str {
129 "test:counting"
130 }
131 }
132
133 #[test]
134 fn empty_bundle_has_no_logging_or_profile() {
135 let b = SecurityBundle::builder().build();
136 assert!(!b.has_logging());
137 assert!(!b.has_profile());
138 assert!(b.logging_plugin().is_none());
139 }
140
141 #[test]
142 fn bundle_carries_the_logger_and_forwards_log_calls() {
143 let calls = Arc::new(AtomicUsize::new(0));
144 let logger = CountingLogger {
145 calls: Arc::clone(&calls),
146 };
147 let bundle = SecurityBundle::builder()
148 .logging_plugin(Box::new(logger))
149 .build();
150
151 assert!(bundle.has_logging());
152 let plugin = bundle.logging_plugin().expect("logger present");
153 plugin.log(LogLevel::Warning, [0u8; 16], "auth", "test event");
154 assert_eq!(calls.load(Ordering::SeqCst), 1);
155 }
156
157 #[test]
158 fn bundle_is_cloneable_and_shares_the_same_logger() {
159 let calls = Arc::new(AtomicUsize::new(0));
160 let bundle = SecurityBundle::builder()
161 .logging_plugin(Box::new(CountingLogger {
162 calls: Arc::clone(&calls),
163 }))
164 .build();
165 let clone = bundle.clone();
166 bundle
167 .logging_plugin()
168 .unwrap()
169 .log(LogLevel::Error, [0u8; 16], "crypto", "a");
170 clone
171 .logging_plugin()
172 .unwrap()
173 .log(LogLevel::Error, [0u8; 16], "crypto", "b");
174 assert_eq!(calls.load(Ordering::SeqCst), 2);
176 }
177}