Skip to main content

lib_q_aead/
plugin.rs

1//! Plugin Architecture for AEAD Algorithms
2//!
3//! This module provides a plugin system that allows dynamic loading and registration
4//! of AEAD algorithms at runtime.
5
6use 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/// Plugin dependency information
24#[derive(Debug, Clone, PartialEq)]
25pub struct PluginDependency {
26    /// Name of the dependency
27    pub name: String,
28    /// Required version range (e.g., ">=1.0.0,<2.0.0")
29    pub version_range: String,
30    /// Whether the dependency is optional
31    pub optional: bool,
32}
33
34/// Plugin metadata with versioning and dependency information
35#[derive(Debug, Clone)]
36pub struct PluginInfo {
37    /// Plugin name
38    pub name: String,
39    /// Plugin version
40    pub version: String,
41    /// Plugin description
42    pub description: String,
43    /// Plugin dependencies
44    pub dependencies: Vec<PluginDependency>,
45    /// Plugin author
46    pub author: Option<String>,
47    /// Plugin license
48    pub license: Option<String>,
49    /// Plugin repository URL
50    pub repository: Option<String>,
51}
52
53/// Version comparison result
54#[derive(Debug, Clone, PartialEq)]
55pub enum VersionComparison {
56    /// Versions are equal
57    Equal,
58    /// First version is greater than second
59    Greater,
60    /// First version is less than second
61    Less,
62    /// Versions cannot be compared (invalid format)
63    Incompatible,
64}
65
66/// Plugin trait for AEAD algorithms
67pub trait AeadPlugin: Send + Sync {
68    /// Get the algorithm identifier for this plugin
69    fn algorithm(&self) -> Algorithm;
70
71    /// Create a new AEAD instance
72    fn create(&self) -> Result<Box<dyn AeadWithMetadata>>;
73
74    /// Get algorithm metadata
75    fn metadata(&self) -> &'static AeadMetadata;
76
77    /// Get plugin name
78    fn name(&self) -> &'static str;
79
80    /// Get plugin version
81    fn version(&self) -> &'static str;
82
83    /// Get plugin description
84    fn description(&self) -> &'static str;
85
86    /// Get detailed plugin information including dependencies
87    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    /// Check if plugin dependencies are satisfied
100    fn check_dependencies(&self, _available_plugins: &BTreeMap<String, String>) -> Result<()> {
101        // Default implementation: no dependencies
102        Ok(())
103    }
104}
105
106/// Version utility functions
107impl PluginInfo {
108    /// Compare two semantic versions
109    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    /// Check if a version satisfies a version range
129    pub fn version_satisfies_range(version: &str, range: &str) -> bool {
130        // Simple version range checking (supports >=, <=, ==, >, <)
131        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            // Default to exact match
158            matches!(
159                Self::compare_versions(version, range),
160                VersionComparison::Equal
161            )
162        }
163    }
164}
165
166/// Registry for AEAD plugins with enhanced dependency management
167pub struct PluginRegistry {
168    plugins: Vec<Box<dyn AeadPlugin>>,
169    plugin_versions: BTreeMap<String, String>,
170}
171
172impl PluginRegistry {
173    /// Create a new plugin registry
174    pub fn new() -> Self {
175        Self {
176            plugins: Vec::new(),
177            plugin_versions: BTreeMap::new(),
178        }
179    }
180
181    /// Register a plugin with dependency checking
182    pub fn register_plugin(&mut self, plugin: Box<dyn AeadPlugin>) -> Result<()> {
183        // Check for duplicate algorithms
184        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        // Check plugin dependencies
193        plugin.check_dependencies(&self.plugin_versions)?;
194
195        // Register the plugin
196        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    /// Get plugin information by name
206    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    /// List all registered plugins with their versions
214    pub fn list_plugins(&self) -> Vec<PluginInfo> {
215        self.plugins.iter().map(|p| p.info()).collect()
216    }
217
218    /// Check if a plugin version is compatible
219    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    /// Get a plugin by algorithm
228    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    /// Create an AEAD instance using a plugin
236    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    /// Get all registered algorithms
247    pub fn available_algorithms(&self) -> Vec<Algorithm> {
248        self.plugins.iter().map(|p| p.algorithm()).collect()
249    }
250
251    /// Get all plugins
252    pub fn plugins(&self) -> &[Box<dyn AeadPlugin>] {
253        &self.plugins
254    }
255
256    /// Check if an algorithm is available
257    pub fn is_available(&self, algorithm: Algorithm) -> bool {
258        self.plugins.iter().any(|p| p.algorithm() == algorithm)
259    }
260
261    /// Get plugin metadata for an algorithm
262    pub fn get_metadata(&self, algorithm: Algorithm) -> Option<&'static AeadMetadata> {
263        self.get_plugin(algorithm).map(|p| p.metadata())
264    }
265
266    /// Get all plugin metadata
267    pub fn get_all_metadata(&self) -> Vec<&'static AeadMetadata> {
268        self.plugins.iter().map(|p| p.metadata()).collect()
269    }
270
271    /// Remove a plugin
272    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    /// Clear all plugins
286    pub fn clear(&mut self) {
287        self.plugins.clear();
288    }
289
290    /// Get plugin count
291    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 to create a plugin implementation
303#[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    // Mock plugin for testing
346    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    /// Minimal AEAD stub for plugin/registry unit tests (**Layer A only**).
383    /// Does not implement [`lib_q_core::AeadDecryptSemantic`]; use real algorithm types for Layer B tests.
384    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}