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    pub xcode_project: Option<crate::parsers::xcode_parser::XcodeProject>,
39}
40
41impl Engine {
42    pub fn new() -> Self {
43        Self {
44            rules: Vec::new(),
45            xcode_project: None,
46        }
47    }
48
49    pub fn register_rule(&mut self, rule: Box<dyn AppStoreRule>) {
50        self.rules.push(rule);
51    }
52
53    pub fn run<P: AsRef<Path>>(&self, ipa_path: P) -> Result<EngineRun, OrchestratorError> {
54        let run_started = Instant::now();
55        let path = ipa_path.as_ref();
56
57        if path.is_dir() {
58            return self.run_on_bundle(path, run_started);
59        }
60
61        let extracted_ipa = extract_ipa(path)?;
62
63        let app_bundle_path = extracted_ipa
64            .get_app_bundle_path()
65            .map_err(|e| OrchestratorError::Extraction(ExtractionError::Io(e)))?
66            .ok_or(OrchestratorError::AppBundleNotFound)?;
67
68        self.run_on_bundle(&app_bundle_path, run_started)
69    }
70
71    pub fn run_on_bundle<P: AsRef<Path>>(
72        &self,
73        app_bundle_path: P,
74        run_started: Instant,
75    ) -> Result<EngineRun, OrchestratorError> {
76        let app_bundle_path = app_bundle_path.as_ref();
77        let info_plist_path = app_bundle_path.join("Info.plist");
78        let info_plist = if info_plist_path.exists() {
79            Some(InfoPlist::from_file(&info_plist_path)?)
80        } else {
81            None
82        };
83
84        let context = ArtifactContext::new(
85            app_bundle_path,
86            info_plist.as_ref(),
87            self.xcode_project.as_ref(),
88        );
89
90        let mut results = Vec::new();
91
92        for rule in &self.rules {
93            let rule_started = Instant::now();
94            let res = rule.evaluate(&context);
95            results.push(EngineResult {
96                rule_id: rule.id(),
97                rule_name: rule.name(),
98                category: rule.category(),
99                severity: rule.severity(),
100                recommendation: rule.recommendation(),
101                report: res,
102                duration_ms: rule_started.elapsed().as_millis(),
103            });
104        }
105
106        Ok(EngineRun {
107            results,
108            total_duration_ms: run_started.elapsed().as_millis(),
109            cache_stats: context.cache_stats(),
110        })
111    }
112}
113
114impl Default for Engine {
115    fn default() -> Self {
116        Self::new()
117    }
118}