1use alloc::boxed::Box;
7use alloc::collections::BTreeMap;
8use alloc::string::{
9 String,
10 ToString,
11};
12use alloc::vec::Vec;
13
14use lib_q_core::{
15 Algorithm,
16 Error,
17 Result,
18};
19
20use crate::AeadWithMetadata;
21use crate::metadata::AeadMetadata;
22
23#[derive(Debug, Clone, PartialEq)]
25pub struct PluginDependency {
26 pub name: String,
28 pub version_range: String,
30 pub optional: bool,
32}
33
34#[derive(Debug, Clone)]
36pub struct PluginInfo {
37 pub name: String,
39 pub version: String,
41 pub description: String,
43 pub dependencies: Vec<PluginDependency>,
45 pub author: Option<String>,
47 pub license: Option<String>,
49 pub repository: Option<String>,
51}
52
53#[derive(Debug, Clone, PartialEq)]
55pub enum VersionComparison {
56 Equal,
58 Greater,
60 Less,
62 Incompatible,
64}
65
66pub trait AeadPlugin: Send + Sync {
68 fn algorithm(&self) -> Algorithm;
70
71 fn create(&self) -> Result<Box<dyn AeadWithMetadata>>;
73
74 fn metadata(&self) -> &'static AeadMetadata;
76
77 fn name(&self) -> &'static str;
79
80 fn version(&self) -> &'static str;
82
83 fn description(&self) -> &'static str;
85
86 fn info(&self) -> PluginInfo {
88 PluginInfo {
89 name: self.name().to_string(),
90 version: self.version().to_string(),
91 description: self.description().to_string(),
92 dependencies: Vec::new(),
93 author: None,
94 license: None,
95 repository: None,
96 }
97 }
98
99 fn check_dependencies(&self, _available_plugins: &BTreeMap<String, String>) -> Result<()> {
101 Ok(())
103 }
104}
105
106impl PluginInfo {
108 pub fn compare_versions(version1: &str, version2: &str) -> VersionComparison {
110 let v1_parts: Vec<u32> = version1.split('.').filter_map(|s| s.parse().ok()).collect();
111 let v2_parts: Vec<u32> = version2.split('.').filter_map(|s| s.parse().ok()).collect();
112
113 if v1_parts.len() != 3 || v2_parts.len() != 3 {
114 return VersionComparison::Incompatible;
115 }
116
117 for (a, b) in v1_parts.iter().zip(v2_parts.iter()) {
118 match a.cmp(b) {
119 core::cmp::Ordering::Less => return VersionComparison::Less,
120 core::cmp::Ordering::Greater => return VersionComparison::Greater,
121 core::cmp::Ordering::Equal => continue,
122 }
123 }
124
125 VersionComparison::Equal
126 }
127
128 pub fn version_satisfies_range(version: &str, range: &str) -> bool {
130 if let Some(required) = range.strip_prefix(">=") {
132 matches!(
133 Self::compare_versions(version, required),
134 VersionComparison::Greater | VersionComparison::Equal
135 )
136 } else if let Some(required) = range.strip_prefix("<=") {
137 matches!(
138 Self::compare_versions(version, required),
139 VersionComparison::Less | VersionComparison::Equal
140 )
141 } else if let Some(required) = range.strip_prefix(">") {
142 matches!(
143 Self::compare_versions(version, required),
144 VersionComparison::Greater
145 )
146 } else if let Some(required) = range.strip_prefix("<") {
147 matches!(
148 Self::compare_versions(version, required),
149 VersionComparison::Less
150 )
151 } else if let Some(required) = range.strip_prefix("==") {
152 matches!(
153 Self::compare_versions(version, required),
154 VersionComparison::Equal
155 )
156 } else {
157 matches!(
159 Self::compare_versions(version, range),
160 VersionComparison::Equal
161 )
162 }
163 }
164}
165
166pub struct PluginRegistry {
168 plugins: Vec<Box<dyn AeadPlugin>>,
169 plugin_versions: BTreeMap<String, String>,
170}
171
172impl PluginRegistry {
173 pub fn new() -> Self {
175 Self {
176 plugins: Vec::new(),
177 plugin_versions: BTreeMap::new(),
178 }
179 }
180
181 pub fn register_plugin(&mut self, plugin: Box<dyn AeadPlugin>) -> Result<()> {
183 let algorithm = plugin.algorithm();
185 if self.plugins.iter().any(|p| p.algorithm() == algorithm) {
186 return Err(Error::InvalidState {
187 operation: "register_plugin".to_string(),
188 reason: "Algorithm already registered".to_string(),
189 });
190 }
191
192 plugin.check_dependencies(&self.plugin_versions)?;
194
195 let plugin_name = plugin.name().to_string();
197 let plugin_version = plugin.version().to_string();
198 self.plugin_versions
199 .insert(plugin_name.clone(), plugin_version);
200 self.plugins.push(plugin);
201
202 Ok(())
203 }
204
205 pub fn get_plugin_info(&self, name: &str) -> Option<PluginInfo> {
207 self.plugins
208 .iter()
209 .find(|p| p.name() == name)
210 .map(|p| p.info())
211 }
212
213 pub fn list_plugins(&self) -> Vec<PluginInfo> {
215 self.plugins.iter().map(|p| p.info()).collect()
216 }
217
218 pub fn is_plugin_compatible(&self, name: &str, required_version: &str) -> bool {
220 if let Some(version) = self.plugin_versions.get(name) {
221 PluginInfo::version_satisfies_range(version, required_version)
222 } else {
223 false
224 }
225 }
226
227 pub fn get_plugin(&self, algorithm: Algorithm) -> Option<&dyn AeadPlugin> {
229 self.plugins
230 .iter()
231 .find(|p| p.algorithm() == algorithm)
232 .map(|p| p.as_ref())
233 }
234
235 pub fn create_aead(&self, algorithm: Algorithm) -> Result<Box<dyn AeadWithMetadata>> {
237 let plugin = self
238 .get_plugin(algorithm)
239 .ok_or_else(|| Error::UnsupportedAlgorithm {
240 algorithm: "Plugin not found".to_string(),
241 })?;
242
243 plugin.create()
244 }
245
246 pub fn available_algorithms(&self) -> Vec<Algorithm> {
248 self.plugins.iter().map(|p| p.algorithm()).collect()
249 }
250
251 pub fn plugins(&self) -> &[Box<dyn AeadPlugin>] {
253 &self.plugins
254 }
255
256 pub fn is_available(&self, algorithm: Algorithm) -> bool {
258 self.plugins.iter().any(|p| p.algorithm() == algorithm)
259 }
260
261 pub fn get_metadata(&self, algorithm: Algorithm) -> Option<&'static AeadMetadata> {
263 self.get_plugin(algorithm).map(|p| p.metadata())
264 }
265
266 pub fn get_all_metadata(&self) -> Vec<&'static AeadMetadata> {
268 self.plugins.iter().map(|p| p.metadata()).collect()
269 }
270
271 pub fn remove_plugin(&mut self, algorithm: Algorithm) -> Result<()> {
273 let initial_len = self.plugins.len();
274 self.plugins.retain(|p| p.algorithm() != algorithm);
275
276 if self.plugins.len() == initial_len {
277 Err(Error::UnsupportedAlgorithm {
278 algorithm: "Plugin not found".to_string(),
279 })
280 } else {
281 Ok(())
282 }
283 }
284
285 pub fn clear(&mut self) {
287 self.plugins.clear();
288 }
289
290 pub fn plugin_count(&self) -> usize {
292 self.plugins.len()
293 }
294}
295
296impl Default for PluginRegistry {
297 fn default() -> Self {
298 Self::new()
299 }
300}
301
302#[macro_export]
304macro_rules! impl_aead_plugin {
305 ($struct_name:ident, $algorithm:expr, $name:expr, $version:expr, $description:expr) => {
306 impl $crate::plugin::AeadPlugin for $struct_name {
307 fn algorithm(&self) -> lib_q_core::Algorithm {
308 $algorithm
309 }
310
311 fn create(&self) -> lib_q_core::Result<alloc::boxed::Box<dyn lib_q_core::Aead>> {
312 Ok(alloc::boxed::Box::new(Self::new()))
313 }
314
315 fn metadata(&self) -> &'static $crate::metadata::AeadMetadata {
316 $crate::metadata::get_metadata($algorithm)
317 .expect("Metadata not found for algorithm")
318 }
319
320 fn name(&self) -> &'static str {
321 $name
322 }
323
324 fn version(&self) -> &'static str {
325 $version
326 }
327
328 fn description(&self) -> &'static str {
329 $description
330 }
331 }
332 };
333}
334
335#[cfg(test)]
336mod tests {
337 use lib_q_core::{
338 Aead,
339 AeadKey,
340 Nonce,
341 };
342
343 use super::*;
344
345 struct MockPlugin {
347 algorithm: Algorithm,
348 }
349
350 impl MockPlugin {
351 fn new(algorithm: Algorithm) -> Self {
352 Self { algorithm }
353 }
354 }
355
356 impl AeadPlugin for MockPlugin {
357 fn algorithm(&self) -> Algorithm {
358 self.algorithm
359 }
360
361 fn create(&self) -> Result<Box<dyn AeadWithMetadata>> {
362 Ok(Box::new(MockAead))
363 }
364
365 fn metadata(&self) -> &'static AeadMetadata {
366 crate::metadata::get_metadata(self.algorithm).expect("Metadata not found")
367 }
368
369 fn name(&self) -> &'static str {
370 "Mock Plugin"
371 }
372
373 fn version(&self) -> &'static str {
374 "1.0.0"
375 }
376
377 fn description(&self) -> &'static str {
378 "Mock plugin for testing"
379 }
380 }
381
382 struct MockAead;
385
386 impl Aead for MockAead {
387 fn encrypt(
388 &self,
389 _key: &AeadKey,
390 _nonce: &Nonce,
391 _plaintext: &[u8],
392 _associated_data: Option<&[u8]>,
393 ) -> Result<Vec<u8>> {
394 Ok(alloc::vec![1, 2, 3, 4])
395 }
396
397 fn decrypt(
398 &self,
399 _key: &AeadKey,
400 _nonce: &Nonce,
401 _ciphertext: &[u8],
402 _associated_data: Option<&[u8]>,
403 ) -> Result<Vec<u8>> {
404 Ok(alloc::vec![5, 6, 7, 8])
405 }
406 }
407
408 impl AeadWithMetadata for MockAead {
409 fn metadata(&self) -> &'static AeadMetadata {
410 crate::metadata::get_metadata(Algorithm::Saturnin).expect("Metadata not found")
411 }
412
413 fn supports_semantic_decrypt(&self) -> bool {
414 false
415 }
416 }
417
418 #[test]
419 fn test_plugin_registry_creation() {
420 let registry = PluginRegistry::new();
421 assert_eq!(registry.plugin_count(), 0);
422 assert!(registry.available_algorithms().is_empty());
423 }
424
425 #[test]
426 fn test_plugin_registration() {
427 let mut registry = PluginRegistry::new();
428
429 let plugin = Box::new(MockPlugin::new(Algorithm::Saturnin));
430 let result = registry.register_plugin(plugin);
431
432 assert!(result.is_ok());
433 assert_eq!(registry.plugin_count(), 1);
434 assert!(registry.is_available(Algorithm::Saturnin));
435 assert!(
436 registry
437 .available_algorithms()
438 .contains(&Algorithm::Saturnin)
439 );
440 }
441
442 #[test]
443 fn test_duplicate_plugin_registration() {
444 let mut registry = PluginRegistry::new();
445
446 let plugin1 = Box::new(MockPlugin::new(Algorithm::Saturnin));
447 let plugin2 = Box::new(MockPlugin::new(Algorithm::Saturnin));
448
449 registry.register_plugin(plugin1).unwrap();
450 let result = registry.register_plugin(plugin2);
451
452 assert!(result.is_err());
453 if let Err(Error::InvalidState { operation, reason }) = result {
454 assert_eq!(operation, "register_plugin");
455 assert!(reason.contains("already registered"));
456 } else {
457 panic!("Expected InvalidState error");
458 }
459 }
460
461 #[test]
462 fn test_plugin_creation() {
463 let mut registry = PluginRegistry::new();
464
465 let plugin = Box::new(MockPlugin::new(Algorithm::Saturnin));
466 registry.register_plugin(plugin).unwrap();
467
468 let aead = registry.create_aead(Algorithm::Saturnin);
469 assert!(aead.is_ok());
470 }
471
472 #[test]
473 fn test_mock_aead_disclaims_semantic_decrypt_capability() {
474 let mut registry = PluginRegistry::new();
475 registry
476 .register_plugin(Box::new(MockPlugin::new(Algorithm::Saturnin)))
477 .unwrap();
478 let aead = registry.create_aead(Algorithm::Saturnin).unwrap();
479 assert!(
480 !aead.supports_semantic_decrypt(),
481 "Layer A test stub must not claim Layer B via metadata defaults"
482 );
483 }
484
485 #[test]
486 fn test_plugin_metadata() {
487 let mut registry = PluginRegistry::new();
488
489 let plugin = Box::new(MockPlugin::new(Algorithm::Saturnin));
490 registry.register_plugin(plugin).unwrap();
491
492 let metadata = registry.get_metadata(Algorithm::Saturnin);
493 assert!(metadata.is_some());
494
495 if let Some(meta) = metadata {
496 assert_eq!(meta.algorithm, Algorithm::Saturnin);
497 }
498 }
499
500 #[test]
501 fn test_plugin_removal() {
502 let mut registry = PluginRegistry::new();
503
504 let plugin = Box::new(MockPlugin::new(Algorithm::Saturnin));
505 registry.register_plugin(plugin).unwrap();
506
507 assert_eq!(registry.plugin_count(), 1);
508
509 let result = registry.remove_plugin(Algorithm::Saturnin);
510 assert!(result.is_ok());
511 assert_eq!(registry.plugin_count(), 0);
512 assert!(!registry.is_available(Algorithm::Saturnin));
513 }
514
515 #[test]
516 fn test_plugin_clear() {
517 let mut registry = PluginRegistry::new();
518
519 let plugin1 = Box::new(MockPlugin::new(Algorithm::Saturnin));
520 let plugin2 = Box::new(MockPlugin::new(Algorithm::Shake256Aead));
521
522 registry.register_plugin(plugin1).unwrap();
523 registry.register_plugin(plugin2).unwrap();
524
525 assert_eq!(registry.plugin_count(), 2);
526
527 registry.clear();
528 assert_eq!(registry.plugin_count(), 0);
529 assert!(registry.available_algorithms().is_empty());
530 }
531
532 #[test]
533 fn test_unsupported_algorithm() {
534 let registry = PluginRegistry::new();
535
536 let result = registry.create_aead(Algorithm::Saturnin);
537 assert!(result.is_err());
538
539 if let Err(Error::UnsupportedAlgorithm { algorithm }) = result {
540 assert!(algorithm.contains("not found"));
541 } else {
542 panic!("Expected UnsupportedAlgorithm error");
543 }
544 }
545}