verifyos_cli/core/
engine.rs1use 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}