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 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}