1use crate::attacks;
2use crate::differential;
3use crate::report::{AttackResult, AttackStatus, SimReport};
4use anyhow::Result;
5use assay_evidence::VerifyLimits;
6use std::path::PathBuf;
7use std::time::{Duration, Instant};
8
9#[derive(Debug, Clone)]
10pub enum SuiteTier {
11 Quick,
12 Nightly,
13 Stress,
14 Chaos,
15}
16
17#[derive(Debug, Clone)]
18pub struct SuiteConfig {
19 pub tier: SuiteTier,
20 pub target_bundle: PathBuf, pub seed: u64,
22 pub verify_limits: Option<VerifyLimits>,
23}
24
25#[derive(Debug, Clone)]
30pub struct TimeBudget {
31 start: Instant,
32 limit: Duration,
33}
34
35impl TimeBudget {
36 pub fn new(limit: Duration) -> Self {
37 Self {
38 start: Instant::now(),
39 limit,
40 }
41 }
42
43 pub fn default_suite() -> Self {
45 Self::new(Duration::from_secs(30))
46 }
47
48 pub fn exceeded(&self) -> bool {
49 self.start.elapsed() > self.limit
50 }
51
52 pub fn remaining(&self) -> Duration {
53 self.limit.saturating_sub(self.start.elapsed())
54 }
55}
56
57pub fn run_suite(cfg: SuiteConfig) -> Result<SimReport> {
58 let mut report = SimReport::new(&format!("{:?}", cfg.tier), cfg.seed);
59 let budget = TimeBudget::default_suite();
60
61 {
68 let seed = cfg.seed;
69 let start = Instant::now();
70 let mut inner_report = SimReport::new("integrity", seed);
71 match attacks::integrity::check_integrity_attacks(&mut inner_report, seed) {
72 Ok(()) => {
73 for r in inner_report.results {
74 report.add_result(r);
75 }
76 }
77 Err(e) => {
78 for r in inner_report.results {
79 report.add_result(r);
80 }
81 report.add_result(AttackResult {
82 name: "integrity_attacks".into(),
83 status: AttackStatus::Error,
84 error_class: None,
85 error_code: None,
86 message: Some(e.to_string()),
87 duration_ms: start.elapsed().as_millis() as u64,
88 });
89 }
90 }
91 }
92
93 if budget.exceeded() {
94 report.add_result(AttackResult {
95 name: "time_budget".into(),
96 status: AttackStatus::Error,
97 error_class: None,
98 error_code: None,
99 message: Some("time budget exceeded after integrity attacks".into()),
100 duration_ms: budget.start.elapsed().as_millis() as u64,
101 });
102 return Ok(report);
103 }
104
105 let iterations = match cfg.tier {
107 SuiteTier::Quick => 5,
108 SuiteTier::Nightly => 100,
109 SuiteTier::Stress => 1000,
110 SuiteTier::Chaos => 50,
111 };
112
113 {
114 let start = Instant::now();
115 let inner = differential::check_invariants(iterations, Some(cfg.seed));
116 let duration = start.elapsed().as_millis() as u64;
117 report.add_check("differential.invariants", inner, duration);
118 }
119
120 if budget.exceeded() {
121 report.add_result(AttackResult {
122 name: "time_budget".into(),
123 status: AttackStatus::Error,
124 error_class: None,
125 error_code: None,
126 message: Some("time budget exceeded after differential tests".into()),
127 duration_ms: budget.start.elapsed().as_millis() as u64,
128 });
129 return Ok(report);
130 }
131
132 if matches!(cfg.tier, SuiteTier::Chaos) {
134 run_chaos_phase(&mut report, cfg.seed, &budget);
135 }
136
137 Ok(report)
138}
139
140fn run_chaos_phase(report: &mut SimReport, seed: u64, budget: &TimeBudget) {
141 match attacks::chaos::check_chaos_attacks(seed) {
143 Ok(results) => {
144 for r in results {
145 report.add_result(r);
146 }
147 }
148 Err(e) => {
149 report.add_result(AttackResult {
150 name: "chaos.io_faults".into(),
151 status: AttackStatus::Error,
152 error_class: None,
153 error_code: None,
154 message: Some(format!("chaos attacks failed: {}", e)),
155 duration_ms: 0,
156 });
157 }
158 }
159
160 if budget.exceeded() {
161 report.add_result(AttackResult {
162 name: "time_budget".into(),
163 status: AttackStatus::Error,
164 error_class: None,
165 error_code: None,
166 message: Some("time budget exceeded during chaos phase".into()),
167 duration_ms: 0,
168 });
169 return;
170 }
171
172 match attacks::differential::check_differential_parity(seed) {
174 Ok(results) => {
175 for r in results {
176 report.add_result(r);
177 }
178 }
179 Err(e) => {
180 report.add_result(AttackResult {
181 name: "differential.parity".into(),
182 status: AttackStatus::Error,
183 error_class: None,
184 error_code: None,
185 message: Some(format!("differential parity failed: {}", e)),
186 duration_ms: 0,
187 });
188 }
189 }
190}