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 let info_plist_path = app_bundle_path.join("Info.plist");
59 let info_plist = if info_plist_path.exists() {
60 Some(InfoPlist::from_file(&info_plist_path)?)
61 } else {
62 None
63 };
64
65 let context = ArtifactContext::new(&app_bundle_path, info_plist.as_ref());
66
67 let mut results = Vec::new();
68
69 for rule in &self.rules {
70 let rule_started = Instant::now();
71 let res = rule.evaluate(&context);
72 results.push(EngineResult {
73 rule_id: rule.id(),
74 rule_name: rule.name(),
75 category: rule.category(),
76 severity: rule.severity(),
77 recommendation: rule.recommendation(),
78 report: res,
79 duration_ms: rule_started.elapsed().as_millis(),
80 });
81 }
82
83 Ok(EngineRun {
84 results,
85 total_duration_ms: run_started.elapsed().as_millis(),
86 cache_stats: context.cache_stats(),
87 })
88 }
89}
90
91impl Default for Engine {
92 fn default() -> Self {
93 Self::new()
94 }
95}