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 {
47 Self::new(Duration::from_secs(60))
48 }
49
50 pub fn exceeded(&self) -> bool {
51 self.start.elapsed() > self.limit
52 }
53
54 pub fn remaining(&self) -> Duration {
55 self.limit.saturating_sub(self.start.elapsed())
56 }
57}
58
59pub fn run_suite(cfg: SuiteConfig) -> Result<SimReport> {
60 let mut report = SimReport::new(&format!("{:?}", cfg.tier), cfg.seed);
61 let budget = TimeBudget::default_suite();
62
63 {
70 let seed = cfg.seed;
71 let start = Instant::now();
72 let mut inner_report = SimReport::new("integrity", seed);
73 match attacks::integrity::check_integrity_attacks(&mut inner_report, seed) {
74 Ok(()) => {
75 for r in inner_report.results {
76 report.add_result(r);
77 }
78 }
79 Err(e) => {
80 for r in inner_report.results {
81 report.add_result(r);
82 }
83 report.add_result(AttackResult {
84 name: "integrity_attacks".into(),
85 status: AttackStatus::Error,
86 error_class: None,
87 error_code: None,
88 message: Some(e.to_string()),
89 duration_ms: start.elapsed().as_millis() as u64,
90 });
91 }
92 }
93 }
94
95 if budget.exceeded() {
96 report.add_result(AttackResult {
97 name: "time_budget".into(),
98 status: AttackStatus::Error,
99 error_class: None,
100 error_code: None,
101 message: Some("time budget exceeded after integrity attacks".into()),
102 duration_ms: budget.start.elapsed().as_millis() as u64,
103 });
104 return Ok(report);
105 }
106
107 let iterations = match cfg.tier {
109 SuiteTier::Quick => 5,
110 SuiteTier::Nightly => 100,
111 SuiteTier::Stress => 1000,
112 SuiteTier::Chaos => 50,
113 };
114
115 {
116 let start = Instant::now();
117 let inner = differential::check_invariants(iterations, Some(cfg.seed));
118 let duration = start.elapsed().as_millis() as u64;
119 report.add_check("differential.invariants", inner, duration);
120 }
121
122 if budget.exceeded() {
123 report.add_result(AttackResult {
124 name: "time_budget".into(),
125 status: AttackStatus::Error,
126 error_class: None,
127 error_code: None,
128 message: Some("time budget exceeded after differential tests".into()),
129 duration_ms: budget.start.elapsed().as_millis() as u64,
130 });
131 return Ok(report);
132 }
133
134 if matches!(cfg.tier, SuiteTier::Chaos) {
136 run_chaos_phase(&mut report, cfg.seed, &budget);
137 }
138
139 Ok(report)
140}
141
142fn run_chaos_phase(report: &mut SimReport, seed: u64, budget: &TimeBudget) {
143 match attacks::chaos::check_chaos_attacks(seed) {
145 Ok(results) => {
146 for r in results {
147 report.add_result(r);
148 }
149 }
150 Err(e) => {
151 report.add_result(AttackResult {
152 name: "chaos.io_faults".into(),
153 status: AttackStatus::Error,
154 error_class: None,
155 error_code: None,
156 message: Some(format!("chaos attacks failed: {}", e)),
157 duration_ms: 0,
158 });
159 }
160 }
161
162 if budget.exceeded() {
163 report.add_result(AttackResult {
164 name: "time_budget".into(),
165 status: AttackStatus::Error,
166 error_class: None,
167 error_code: None,
168 message: Some("time budget exceeded during chaos phase".into()),
169 duration_ms: 0,
170 });
171 return;
172 }
173
174 match attacks::differential::check_differential_parity(seed) {
176 Ok(results) => {
177 for r in results {
178 report.add_result(r);
179 }
180 }
181 Err(e) => {
182 report.add_result(AttackResult {
183 name: "differential.parity".into(),
184 status: AttackStatus::Error,
185 error_class: None,
186 error_code: None,
187 message: Some(format!("differential parity failed: {}", e)),
188 duration_ms: 0,
189 });
190 }
191 }
192}