/ ferris-proof-core / src / plugins.rs
plugins.rs
  1  use crate::types::*;
  2  use anyhow::{anyhow, Result};
  3  use semver::Version;
  4  use std::collections::{HashMap, HashSet};
  5  use std::path::{Path, PathBuf};
  6  use std::sync::{Arc, RwLock};
  7  use tracing::{debug, error, info, warn};
  8  use uuid::Uuid;
  9  
 10  /// Stable ABI trait for verification plugins
 11  ///
 12  /// This trait defines the interface that all verification plugins must implement.
 13  /// The ABI is designed to be stable across minor version updates to ensure
 14  /// plugin compatibility.
 15  pub trait VerificationPlugin: Send + Sync {
 16      /// Plugin name and identifier
 17      fn name(&self) -> &str;
 18  
 19      /// Plugin version (semantic versioning)
 20      fn version(&self) -> &str;
 21  
 22      /// List of verification techniques supported by this plugin
 23      fn supported_techniques(&self) -> Vec<Technique>;
 24  
 25      /// Minimum and maximum supported FerrisProof versions
 26      fn supported_versions(&self) -> VersionRange;
 27  
 28      /// Check if the tool is available and properly configured
 29      fn check_availability(&self) -> Result<ToolInfo>;
 30  
 31      /// Execute verification with given input
 32      fn verify(&self, input: VerificationInput) -> Result<VerificationOutput>;
 33  
 34      /// Parse tool output into structured results
 35      fn parse_output(&self, raw_output: &str) -> Result<StructuredResult>;
 36  
 37      /// Get plugin metadata and capabilities
 38      fn metadata(&self) -> PluginMetadata;
 39  
 40      /// Initialize plugin with configuration
 41      fn initialize(&mut self, config: &serde_json::Value) -> Result<()>;
 42  
 43      /// Cleanup plugin resources
 44      fn cleanup(&mut self) -> Result<()>;
 45  }
 46  
 47  #[derive(Debug, Clone)]
 48  pub struct ToolValidationResult {
 49      pub plugin_name: String,
 50      pub tool_info: Option<ToolInfo>,
 51      pub status: ValidationStatus,
 52      pub issues: Vec<String>,
 53  }
 54  
 55  #[derive(Debug, Clone, PartialEq, Eq)]
 56  pub enum ValidationStatus {
 57      Valid,
 58      Unavailable,
 59      VersionIncompatible,
 60      Error,
 61  }
 62  
 63  #[derive(Debug, Clone)]
 64  pub struct ToolInfo {
 65      pub name: String,
 66      pub version: String,
 67      pub path: PathBuf,
 68      pub available: bool,
 69      pub capabilities: Vec<String>,
 70  }
 71  
 72  #[derive(Debug, Clone)]
 73  pub struct VerificationInput {
 74      pub target: crate::verification::Target,
 75      pub config: EffectiveConfig,
 76      pub context: VerificationContext,
 77  }
 78  
 79  #[derive(Debug, Clone)]
 80  pub struct VerificationOutput {
 81      pub status: Status,
 82      pub violations: Vec<Violation>,
 83      pub artifacts: Vec<Artifact>,
 84      pub tool_output: ToolOutput,
 85      pub metrics: VerificationMetrics,
 86  }
 87  
 88  #[derive(Debug, Clone)]
 89  pub struct EffectiveConfig {
 90      pub level: VerificationLevel,
 91      pub enforcement: EnforcementMode,
 92      pub enabled_techniques: Vec<Technique>,
 93      pub tool_config: serde_json::Value,
 94  }
 95  
 96  #[derive(Debug, Clone)]
 97  pub struct VerificationContext {
 98      pub session_id: String,
 99      pub working_dir: PathBuf,
100      pub cache_dir: PathBuf,
101      pub timeout: Option<std::time::Duration>,
102      pub parallel_id: Option<u32>,
103  }
104  
105  #[derive(Debug, Clone)]
106  pub struct PluginMetadata {
107      pub name: String,
108      pub version: String,
109      pub description: String,
110      pub author: String,
111      pub license: String,
112      pub homepage: Option<String>,
113      pub techniques: Vec<Technique>,
114      pub supported_platforms: Vec<String>,
115      pub dependencies: Vec<String>,
116  }
117  
118  #[derive(Debug, Clone)]
119  pub struct VersionRange {
120      pub min: Option<Version>,
121      pub max: Option<Version>,
122      pub requires_exact: Option<Version>,
123  }
124  
125  #[derive(Debug, Clone)]
126  pub struct StructuredResult {
127      pub status: Status,
128      pub violations: Vec<Violation>,
129      pub statistics: serde_json::Value,
130      pub performance: PerformanceMetrics,
131  }
132  
133  #[derive(Debug, Clone)]
134  pub struct PerformanceMetrics {
135      pub execution_time: std::time::Duration,
136      pub memory_usage: u64,
137      pub cpu_usage: f64,
138      pub cache_hits: u32,
139  }
140  
141  pub struct PluginManager {
142      plugins: HashMap<String, Arc<RwLock<Box<dyn VerificationPlugin>>>>,
143      plugin_registry: PluginRegistry,
144      version_checker: VersionChecker,
145      discovery: PluginDiscovery,
146  }
147  
148  #[derive(Debug, Default)]
149  struct PluginRegistry {
150      registered_plugins: HashMap<String, PluginRegistration>,
151      loaded_plugins: HashSet<String>,
152  }
153  
154  #[derive(Debug, Clone)]
155  #[allow(dead_code)]
156  struct PluginRegistration {
157      metadata: PluginMetadata,
158      library_path: PathBuf,
159      version_compatible: bool,
160  }
161  
162  #[derive(Debug)]
163  struct VersionChecker {
164      current_version: Version,
165  }
166  
167  #[derive(Debug)]
168  struct PluginDiscovery {
169      search_paths: Vec<PathBuf>,
170  }
171  
172  impl PluginManager {
173      pub fn new() -> Self {
174          let current_version = Version::parse(env!("CARGO_PKG_VERSION"))
175              .expect("Failed to parse current FerrisProof version");
176  
177          Self {
178              plugins: HashMap::new(),
179              plugin_registry: PluginRegistry::default(),
180              version_checker: VersionChecker::new(current_version),
181              discovery: PluginDiscovery::new(),
182          }
183      }
184  
185      /// Register a plugin instance
186      pub fn register_plugin(&mut self, plugin: Box<dyn VerificationPlugin>) -> Result<()> {
187          let name = plugin.name().to_string();
188          let version = plugin.version().to_string();
189  
190          // Check version compatibility
191          let version_range = plugin.supported_versions();
192          if !self.version_checker.is_compatible(&version_range) {
193              return Err(anyhow!(
194                  "Plugin {} version {} is not compatible with FerrisProof {}",
195                  name,
196                  version,
197                  self.version_checker.current_version
198              ));
199          }
200  
201          let plugin_arc = Arc::new(RwLock::new(plugin));
202          self.plugins.insert(name.clone(), plugin_arc);
203  
204          info!("Registered plugin: {} v{}", name, version);
205          Ok(())
206      }
207  
208      /// Discover and load plugins from search paths
209      pub fn discover_plugins(&mut self) -> Result<usize> {
210          info!("Discovering plugins in search paths");
211  
212          let mut discovered_count = 0;
213  
214          // Collect search paths to avoid borrowing issues
215          let search_paths: Vec<PathBuf> = self.discovery.search_paths.clone();
216  
217          for search_path in &search_paths {
218              if search_path.exists() {
219                  discovered_count += self.discover_in_directory(search_path)?;
220              }
221          }
222  
223          info!("Discovered {} plugins", discovered_count);
224          Ok(discovered_count)
225      }
226  
227      /// Discover plugins in a specific directory
228      fn discover_in_directory(&mut self, dir: &Path) -> Result<usize> {
229          let mut discovered_count = 0;
230  
231          for entry in std::fs::read_dir(dir)? {
232              let entry = entry?;
233              let path = entry.path();
234  
235              // Look for plugin manifest files
236              if path.extension().and_then(|s| s.to_str()) == Some("json") {
237                  match self.load_plugin_from_manifest(&path) {
238                      Ok(_) => discovered_count += 1,
239                      Err(e) => {
240                          warn!("Failed to load plugin from manifest {:?}: {}", path, e);
241                      }
242                  }
243              }
244          }
245  
246          Ok(discovered_count)
247      }
248  
249      /// Load plugin from manifest file
250      fn load_plugin_from_manifest(&mut self, manifest_path: &Path) -> Result<()> {
251          let manifest_content = std::fs::read_to_string(manifest_path)?;
252          let manifest: serde_json::Value = serde_json::from_str(&manifest_content)?;
253  
254          let metadata = PluginMetadata::from_json(&manifest)?;
255  
256          // Check if already loaded
257          if self.plugin_registry.loaded_plugins.contains(&metadata.name) {
258              return Ok(());
259          }
260  
261          // For now, just register the metadata
262          // TODO: Implement dynamic loading with libloading
263          let registration = PluginRegistration {
264              metadata: metadata.clone(),
265              library_path: manifest_path
266                  .parent()
267                  .unwrap_or_else(|| Path::new("."))
268                  .join(format!("lib{}.so", metadata.name)),
269              version_compatible: self.version_checker.is_metadata_compatible(&metadata),
270          };
271  
272          self.plugin_registry
273              .registered_plugins
274              .insert(metadata.name.clone(), registration);
275  
276          info!("Discovered plugin: {} v{}", metadata.name, metadata.version);
277          Ok(())
278      }
279  
280      /// Get all plugins that support a specific technique
281      pub fn plugins_for_technique(
282          &self,
283          technique: &Technique,
284      ) -> Vec<Arc<RwLock<Box<dyn VerificationPlugin>>>> {
285          self.plugins
286              .values()
287              .filter(|plugin| {
288                  let plugin_guard = plugin.read().ok();
289                  if let Some(plugin) = plugin_guard {
290                      plugin.supported_techniques().contains(technique)
291                  } else {
292                      false
293                  }
294              })
295              .cloned()
296              .collect()
297      }
298  
299      /// Validate all registered tools and their versions
300      pub fn validate_tools(&self) -> Result<Vec<ToolValidationResult>> {
301          let mut validation_results = Vec::new();
302  
303          for (name, plugin) in &self.plugins {
304              match plugin.read() {
305                  Ok(plugin) => {
306                      let validation_result = match plugin.check_availability() {
307                          Ok(tool_info) => {
308                              // Validate tool version if plugin specifies requirements
309                              let version_validation =
310                                  self.version_checker.validate_tool_version(&tool_info, None);
311  
312                              match version_validation {
313                                  Ok(_) => {
314                                      debug!("Tool {} is available and compatible", name);
315                                      ToolValidationResult {
316                                          plugin_name: name.clone(),
317                                          tool_info: Some(tool_info),
318                                          status: ValidationStatus::Valid,
319                                          issues: Vec::new(),
320                                      }
321                                  }
322                                  Err(e) => {
323                                      warn!("Tool {} version incompatible: {}", name, e);
324                                      ToolValidationResult {
325                                          plugin_name: name.clone(),
326                                          tool_info: Some(tool_info),
327                                          status: ValidationStatus::VersionIncompatible,
328                                          issues: vec![e.to_string()],
329                                      }
330                                  }
331                              }
332                          }
333                          Err(e) => {
334                              warn!("Tool {} not available: {}", name, e);
335                              ToolValidationResult {
336                                  plugin_name: name.clone(),
337                                  tool_info: None,
338                                  status: ValidationStatus::Unavailable,
339                                  issues: vec![e.to_string()],
340                              }
341                          }
342                      };
343  
344                      validation_results.push(validation_result);
345                  }
346                  Err(e) => {
347                      error!("Failed to lock plugin {}: {}", name, e);
348                      validation_results.push(ToolValidationResult {
349                          plugin_name: name.clone(),
350                          tool_info: None,
351                          status: ValidationStatus::Error,
352                          issues: vec![format!("Plugin lock error: {}", e)],
353                      });
354                  }
355              }
356          }
357  
358          Ok(validation_results)
359      }
360  
361      /// Execute verification using appropriate plugin
362      pub async fn verify(
363          &self,
364          technique: &Technique,
365          input: VerificationInput,
366      ) -> Result<VerificationOutput> {
367          let plugins = self.plugins_for_technique(technique);
368  
369          if plugins.is_empty() {
370              return Err(anyhow!(
371                  "No plugins available for technique: {:?}",
372                  technique
373              ));
374          }
375  
376          // Use first available plugin
377          // TODO: Implement plugin selection strategy
378          if let Some(plugin_arc) = plugins.first() {
379              let plugin = plugin_arc
380                  .read()
381                  .map_err(|e| anyhow!("Failed to acquire plugin lock: {}", e))?;
382  
383              // Create verification context
384              let context = VerificationContext {
385                  session_id: Uuid::new_v4().to_string(),
386                  working_dir: std::env::current_dir().unwrap_or_else(|_| PathBuf::from("/tmp")),
387                  cache_dir: std::env::temp_dir(),
388                  timeout: Some(std::time::Duration::from_secs(300)), // 5 minutes default
389                  parallel_id: None,
390              };
391  
392              let enhanced_input = VerificationInput {
393                  target: input.target,
394                  config: input.config,
395                  context,
396              };
397  
398              plugin.verify(enhanced_input)
399          } else {
400              Err(anyhow!(
401                  "No plugin available for technique: {:?}",
402                  technique
403              ))
404          }
405      }
406  
407      /// Get plugin metadata
408      pub fn plugin_metadata(&self, name: &str) -> Option<PluginMetadata> {
409          if let Some(plugin_arc) = self.plugins.get(name) {
410              match plugin_arc.read() {
411                  Ok(plugin) => Some(plugin.metadata()),
412                  Err(_) => None,
413              }
414          } else {
415              self.plugin_registry
416                  .registered_plugins
417                  .get(name)
418                  .map(|registration| registration.metadata.clone())
419          }
420      }
421  
422      /// List all registered plugins
423      pub fn list_plugins(&self) -> Vec<PluginMetadata> {
424          let mut metadata_list = Vec::new();
425  
426          // Add loaded plugins
427          for (name, plugin_arc) in &self.plugins {
428              match plugin_arc.read() {
429                  Ok(plugin) => {
430                      metadata_list.push(plugin.metadata());
431                  }
432                  Err(_) => {
433                      warn!("Failed to read plugin metadata for: {}", name);
434                  }
435              }
436          }
437  
438          // Add discovered but not loaded plugins
439          for registration in self.plugin_registry.registered_plugins.values() {
440              if !self
441                  .plugin_registry
442                  .loaded_plugins
443                  .contains(&registration.metadata.name)
444              {
445                  metadata_list.push(registration.metadata.clone());
446              }
447          }
448  
449          metadata_list.sort_by(|a, b| a.name.cmp(&b.name));
450          metadata_list
451      }
452  
453      /// Initialize all plugins with configuration
454      pub fn initialize_plugins(&mut self, config: &serde_json::Value) -> Result<()> {
455          for (name, plugin_arc) in &self.plugins {
456              match plugin_arc.write() {
457                  Ok(mut plugin) => {
458                      if let Some(plugin_config) = config.get(name) {
459                          if let Err(e) = plugin.initialize(plugin_config) {
460                              warn!("Failed to initialize plugin {}: {}", name, e);
461                          }
462                      }
463                  }
464                  Err(e) => {
465                      error!("Failed to acquire write lock for plugin {}: {}", name, e);
466                  }
467              }
468          }
469          Ok(())
470      }
471  
472      /// Cleanup all plugins
473      pub fn cleanup_plugins(&mut self) -> Result<()> {
474          for (name, plugin_arc) in &self.plugins {
475              match plugin_arc.write() {
476                  Ok(mut plugin) => {
477                      if let Err(e) = plugin.cleanup() {
478                          warn!("Failed to cleanup plugin {}: {}", name, e);
479                      }
480                  }
481                  Err(e) => {
482                      error!("Failed to acquire write lock for plugin {}: {}", name, e);
483                  }
484              }
485          }
486          Ok(())
487      }
488  }
489  
490  impl PluginMetadata {
491      fn from_json(value: &serde_json::Value) -> Result<Self> {
492          Ok(Self {
493              name: value
494                  .get("name")
495                  .and_then(|v| v.as_str())
496                  .ok_or_else(|| anyhow!("Missing plugin name"))?
497                  .to_string(),
498              version: value
499                  .get("version")
500                  .and_then(|v| v.as_str())
501                  .ok_or_else(|| anyhow!("Missing plugin version"))?
502                  .to_string(),
503              description: value
504                  .get("description")
505                  .and_then(|v| v.as_str())
506                  .unwrap_or("No description available")
507                  .to_string(),
508              author: value
509                  .get("author")
510                  .and_then(|v| v.as_str())
511                  .unwrap_or("Unknown")
512                  .to_string(),
513              license: value
514                  .get("license")
515                  .and_then(|v| v.as_str())
516                  .unwrap_or("Unknown")
517                  .to_string(),
518              homepage: value
519                  .get("homepage")
520                  .and_then(|v| v.as_str())
521                  .map(|s| s.to_string()),
522              techniques: value
523                  .get("techniques")
524                  .and_then(|v| v.as_array())
525                  .map(|arr| {
526                      arr.iter()
527                          .filter_map(|v| v.as_str())
528                          .filter_map(|s| match s {
529                              "TypeSafety" => Some(Technique::TypeSafety),
530                              "PropertyTests" => Some(Technique::PropertyTests),
531                              "SessionTypes" => Some(Technique::SessionTypes),
532                              "RefinementTypes" => Some(Technique::RefinementTypes),
533                              "ConcurrencyTesting" => Some(Technique::ConcurrencyTesting),
534                              "FormalSpecs" => Some(Technique::FormalSpecs),
535                              "ModelChecking" => Some(Technique::ModelChecking),
536                              _ => None,
537                          })
538                          .collect()
539                  })
540                  .unwrap_or_default(),
541              supported_platforms: value
542                  .get("platforms")
543                  .and_then(|v| v.as_array())
544                  .map(|arr| {
545                      arr.iter()
546                          .filter_map(|v| v.as_str())
547                          .map(|s| s.to_string())
548                          .collect()
549                  })
550                  .unwrap_or_default(),
551              dependencies: value
552                  .get("dependencies")
553                  .and_then(|v| v.as_array())
554                  .map(|arr| {
555                      arr.iter()
556                          .filter_map(|v| v.as_str())
557                          .map(|s| s.to_string())
558                          .collect()
559                  })
560                  .unwrap_or_default(),
561          })
562      }
563  }
564  
565  impl PluginRegistry {
566      #[allow(dead_code)]
567      fn register(&mut self, name: String, registration: PluginRegistration) {
568          self.registered_plugins.insert(name, registration);
569      }
570  }
571  
572  impl VersionChecker {
573      fn new(current_version: Version) -> Self {
574          Self { current_version }
575      }
576  
577      /// Check if a plugin's version range is compatible with current FerrisProof version
578      fn is_compatible(&self, version_range: &VersionRange) -> bool {
579          if let Some(exact_version) = &version_range.requires_exact {
580              return &self.current_version == exact_version;
581          }
582  
583          if let Some(min_version) = &version_range.min {
584              if self.current_version < *min_version {
585                  return false;
586              }
587          }
588  
589          if let Some(max_version) = &version_range.max {
590              if self.current_version > *max_version {
591                  return false;
592              }
593          }
594  
595          true
596      }
597  
598      /// Check if plugin metadata indicates compatibility
599      fn is_metadata_compatible(&self, metadata: &PluginMetadata) -> bool {
600          // Check plugin metadata version compatibility
601          if let Ok(plugin_version) = metadata.version.parse::<Version>() {
602              // Check if plugin version is within reasonable bounds
603              // Allow plugins that are at most 1 major version behind or ahead
604              let major_diff = (self.current_version.major as i64) - (plugin_version.major as i64);
605              major_diff.abs() <= 1
606          } else {
607              false
608          }
609      }
610  
611      /// Validate tool version compatibility
612      fn validate_tool_version(
613          &self,
614          tool_info: &ToolInfo,
615          expected_range: Option<&VersionRange>,
616      ) -> Result<()> {
617          if let Some(range) = expected_range {
618              if let Ok(tool_version) = tool_info.version.parse::<Version>() {
619                  if let Some(min_version) = &range.min {
620                      if tool_version < *min_version {
621                          return Err(anyhow!(
622                              "Tool {} version {} is below minimum required version {}",
623                              tool_info.name,
624                              tool_version,
625                              min_version
626                          ));
627                      }
628                  }
629  
630                  if let Some(max_version) = &range.max {
631                      if tool_version > *max_version {
632                          return Err(anyhow!(
633                              "Tool {} version {} is above maximum supported version {}",
634                              tool_info.name,
635                              tool_version,
636                              max_version
637                          ));
638                      }
639                  }
640  
641                  if let Some(exact_version) = &range.requires_exact {
642                      if tool_version != *exact_version {
643                          return Err(anyhow!(
644                              "Tool {} version {} does not match required exact version {}",
645                              tool_info.name,
646                              tool_version,
647                              exact_version
648                          ));
649                      }
650                  }
651              } else {
652                  warn!("Could not parse tool version: {}", tool_info.version);
653              }
654          }
655  
656          Ok(())
657      }
658  }
659  
660  impl PluginDiscovery {
661      fn new() -> Self {
662          let mut search_paths = Vec::new();
663  
664          // Add default search paths
665          if let Some(home_dir) = dirs::home_dir() {
666              search_paths.push(home_dir.join(".ferris-proof/plugins"));
667              search_paths.push(home_dir.join(".local/share/ferris-proof/plugins"));
668          }
669  
670          if let Ok(current_dir) = std::env::current_dir() {
671              search_paths.push(current_dir.join(".ferris-proof/plugins"));
672          }
673  
674          // Add system-wide paths
675          search_paths.push(PathBuf::from("/usr/local/lib/ferris-proof/plugins"));
676          search_paths.push(PathBuf::from("/usr/lib/ferris-proof/plugins"));
677  
678          Self { search_paths }
679      }
680  }
681  
682  impl Default for PluginManager {
683      fn default() -> Self {
684          Self::new()
685      }
686  }