Skip to main content

verifyos_cli/core/
engine.rs

1use crate::parsers::plist_reader::{InfoPlist, PlistError};
2use crate::parsers::zip_extractor::{extract_ipa, ExtractionError};
3use crate::rules::core::{
4    AppStoreRule, ArtifactCacheStats, ArtifactContext, RuleCategory, RuleError, RuleReport,
5    Severity,
6};
7use std::path::Path;
8use std::time::Instant;
9
10#[derive(Debug, thiserror::Error)]
11pub enum OrchestratorError {
12    #[error("Extraction failed: {0}")]
13    Extraction(#[from] ExtractionError),
14    #[error("Failed to parse Info.plist: {0}")]
15    PlistParse(#[from] PlistError),
16    #[error("Could not locate App Bundle (.app) inside IPAPayload")]
17    AppBundleNotFound,
18}
19
20pub struct EngineResult {
21    pub rule_id: &'static str,
22    pub rule_name: &'static str,
23    pub category: RuleCategory,
24    pub severity: Severity,
25    pub recommendation: &'static str,
26    pub report: Result<RuleReport, RuleError>,
27    pub duration_ms: u128,
28}
29
30pub struct EngineRun {
31    pub results: Vec<EngineResult>,
32    pub total_duration_ms: u128,
33    pub cache_stats: ArtifactCacheStats,
34}
35
36pub struct Engine {
37    rules: Vec<Box<dyn AppStoreRule>>,
38}
39
40impl Engine {
41    pub fn new() -> Self {
42        Self { rules: Vec::new() }
43    }
44
45    pub fn register_rule(&mut self, rule: Box<dyn AppStoreRule>) {
46        self.rules.push(rule);
47    }
48
49    pub fn run<P: AsRef<Path>>(&self, ipa_path: P) -> Result<EngineRun, OrchestratorError> {
50        let run_started = Instant::now();
51        let extracted_ipa = extract_ipa(ipa_path)?;
52
53        let app_bundle_path = extracted_ipa
54            .get_app_bundle_path()
55            .map_err(|e| OrchestratorError::Extraction(ExtractionError::Io(e)))?
56            .ok_or(OrchestratorError::AppBundleNotFound)?;
57
58        self.run_on_bundle(&app_bundle_path, run_started)
59    }
60
61    pub fn run_on_bundle<P: AsRef<Path>>(
62        &self,
63        app_bundle_path: P,
64        run_started: Instant,
65    ) -> Result<EngineRun, OrchestratorError> {
66        let app_bundle_path = app_bundle_path.as_ref();
67        let info_plist_path = app_bundle_path.join("Info.plist");
68        let info_plist = if info_plist_path.exists() {
69            Some(InfoPlist::from_file(&info_plist_path)?)
70        } else {
71            None
72        };
73
74        let context = ArtifactContext::new(app_bundle_path, info_plist.as_ref());
75
76        let mut results = Vec::new();
77
78        for rule in &self.rules {
79            let rule_started = Instant::now();
80            let res = rule.evaluate(&context);
81            results.push(EngineResult {
82                rule_id: rule.id(),
83                rule_name: rule.name(),
84                category: rule.category(),
85                severity: rule.severity(),
86                recommendation: rule.recommendation(),
87                report: res,
88                duration_ms: rule_started.elapsed().as_millis(),
89            });
90        }
91
92        Ok(EngineRun {
93            results,
94            total_duration_ms: run_started.elapsed().as_millis(),
95            cache_stats: context.cache_stats(),
96        })
97    }
98}
99
100impl Default for Engine {
101    fn default() -> Self {
102        Self::new()
103    }
104}