confium_test_harness/
runner.rs1use std::time::Instant;
23
24use confium_tc::Message;
25use confium_tc::Party;
26use confium_tc::PartyList;
27use confium_tc::Session;
28use confium_tc::SessionParams;
29
30use crate::ByzantineTransport;
31use crate::DeterministicEnv;
32use crate::Result;
33use crate::TestResult;
34use crate::TestVector;
35use crate::error::SessionDidNotCompleteSnafu;
36
37const MAX_ROUNDS: u8 = 64;
41
42pub struct VectorRunner;
44
45impl VectorRunner {
46 pub fn run(vector: &TestVector) -> Result<TestResult> {
53 let started = Instant::now();
54
55 let _env = DeterministicEnv::from_seed(vector.seed_u64()?);
59 let mut tport = ByzantineTransport::from_specs(vector.behavior_specs());
60
61 let parties = build_party_list(vector);
62 let message_bytes = vector.test.message_bytes();
63
64 let mut sessions: Vec<Session> = Vec::with_capacity(parties.len());
67 for idx in 0..parties.len() {
68 let params = SessionParams {
69 scheme: vector.scheme.name.clone(),
70 parties: parties.clone(),
71 threshold: vector.test.threshold,
72 this_party_idx: idx,
73 local_share: None,
74 message: Some(message_bytes.clone()),
75 };
76 let session = Session::create(¶ms)?;
77 sessions.push(session);
78 }
79
80 let mut total_messages: u64 = 0;
81 let mut total_bytes: u64 = 0;
82 let mut round: u8 = 0;
83 let last_output;
84
85 let mut incoming: Vec<Vec<Message>> = vec![Vec::new(); sessions.len()];
88
89 loop {
90 round = round
91 .checked_add(1)
92 .ok_or_else(|| SessionDidNotCompleteSnafu { rounds: round }.build())?;
93 if round > MAX_ROUNDS {
94 return Err(SessionDidNotCompleteSnafu { rounds: round }.build());
95 }
96
97 let mut outgoing_all: Vec<Message> = Vec::new();
99 let mut all_complete = true;
100 for (idx, session) in sessions.iter_mut().enumerate() {
101 if session.is_complete() {
102 continue;
103 }
104 let my_id = parties.get(idx)?.id.clone();
105 let incoming_for_me = incoming[idx]
106 .iter()
107 .filter(|m| m.is_for(&my_id) && m.from_party_id != my_id)
108 .cloned()
109 .collect::<Vec<_>>();
110 let rr = match session.round_step(&incoming_for_me) {
111 Ok(rr) => rr,
112 Err(scheme_err) => {
113 let elapsed = started.elapsed();
121 return Ok(TestResult::aborted(
122 vector,
123 format!(
124 "scheme '{}' aborted at round {}: {}",
125 vector.scheme.name, round, scheme_err
126 ),
127 round,
128 elapsed,
129 ));
130 }
131 };
132 for msg in &rr.outgoing {
133 total_messages += 1;
134 total_bytes += msg.payload.len() as u64;
135 }
136 outgoing_all.extend(rr.outgoing);
137 if !session.is_complete() {
138 all_complete = false;
139 }
140 }
141
142 let delivered = tport.route(&outgoing_all);
144
145 for bucket in incoming.iter_mut() {
148 bucket.clear();
149 }
150 for msg in delivered {
151 let recipients: Vec<usize> = match &msg.to_party_id {
152 None => (0..parties.len()).collect(),
153 Some(to) => parties
154 .parties()
155 .iter()
156 .position(|p| &p.id == to)
157 .into_iter()
158 .collect(),
159 };
160 for ridx in recipients {
161 if let Some(bucket) = incoming.get_mut(ridx) {
162 bucket.push(msg.clone());
163 }
164 }
165 }
166
167 if all_complete {
168 last_output = sessions
171 .first()
172 .map(|s| s.result().unwrap_or_default())
173 .unwrap_or_default();
174 break;
175 }
176 }
177
178 let elapsed = started.elapsed();
179 Ok(TestResult::from_run(
180 vector,
181 last_output,
182 total_messages,
183 total_bytes,
184 round,
185 elapsed,
186 ))
187 }
188
189 pub fn run_path(path: &std::path::Path) -> Result<TestResult> {
191 let vector = TestVector::from_path(path)?;
192 Self::run(&vector)
193 }
194}
195
196fn build_party_list(vector: &TestVector) -> PartyList {
200 let n = vector.test.parties as usize;
201 if vector.peer_behavior.len() == n {
202 let parties = vector
203 .peer_behavior
204 .iter()
205 .map(|e| Party::inproc(e.party_id.clone()))
206 .collect();
207 PartyList::from_parties(parties)
208 } else if !vector.peer_behavior.is_empty() {
209 let mut parties: Vec<Party> = vector
211 .peer_behavior
212 .iter()
213 .map(|e| Party::inproc(e.party_id.clone()))
214 .collect();
215 for i in vector.peer_behavior.len()..n {
216 parties.push(Party::inproc(format!("p{i}")));
217 }
218 PartyList::from_parties(parties)
219 } else {
220 let parties = (0..n).map(|i| Party::inproc(format!("p{i}"))).collect();
221 PartyList::from_parties(parties)
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228 use crate::vector::SchemeSpec;
229 use crate::vector::TestVectorTest;
230 use confium_tc::Message;
231 use confium_tc::error;
232 use confium_tc::registry::{RoundResult, SessionImpl, TcScheme, TcSchemeKind};
233
234 struct RunnerMockScheme;
239
240 impl TcScheme for RunnerMockScheme {
241 fn name(&self) -> &'static str {
242 "runner-mock"
243 }
244 fn kind(&self) -> TcSchemeKind {
245 TcSchemeKind::Signature
246 }
247 fn create_session(
248 &self,
249 params: &SessionParams,
250 ) -> confium_tc::Result<Box<dyn SessionImpl>> {
251 Ok(Box::new(RunnerMockSession {
252 msg: params.message.clone().unwrap_or_default(),
253 done: false,
254 }))
255 }
256 }
257
258 struct RunnerMockSession {
259 msg: Vec<u8>,
260 done: bool,
261 }
262
263 impl SessionImpl for RunnerMockSession {
264 fn round(&mut self, _incoming: &[Message]) -> confium_tc::Result<RoundResult> {
265 self.done = true;
266 Ok(RoundResult::done())
267 }
268 fn result(&self) -> confium_tc::Result<Vec<u8>> {
269 if !self.done {
270 return Err(error::SessionNotCompleteSnafu {}.build());
271 }
272 Ok(self.msg.clone())
273 }
274 fn destroy(&mut self) {
275 self.msg.fill(0);
276 }
277 }
278
279 inventory::submit! {
280 confium_tc::registry::RegisteredScheme {
281 scheme: &RunnerMockScheme as &dyn TcScheme
282 }
283 }
284
285 fn sample_vector(expected: Option<&str>) -> TestVector {
286 TestVector {
287 scheme: SchemeSpec {
288 name: "runner-mock".into(),
289 version: "test".into(),
290 },
291 test: TestVectorTest {
292 parties: 3,
293 threshold: 2,
294 message: "hello".into(),
295 seed: "0x42".into(),
296 expected_signature_hex: expected.unwrap_or("").to_string(),
297 },
298 peer_behavior: vec![],
299 conformance_level: Default::default(),
300 reference: None,
301 expected_round_count: None,
302 share_material: None,
303 }
304 }
305
306 fn hex_str(bytes: &[u8]) -> String {
307 let mut s = String::from("0x");
308 for b in bytes {
309 s.push_str(&format!("{b:02x}"));
310 }
311 s
312 }
313
314 #[test]
315 fn runner_completes_mock_scheme_and_passes() {
316 let expected = hex_str(b"hello");
317 let vector = sample_vector(Some(&expected));
318 let result = VectorRunner::run(&vector).expect("run succeeds");
319 assert_eq!(result.outcome, crate::Outcome::Pass);
320 assert_eq!(result.output, b"hello");
321 assert_eq!(result.rounds, 1);
322 }
323
324 #[test]
325 fn runner_passes_without_expected_bytes() {
326 let vector = sample_vector(None);
327 let result = VectorRunner::run(&vector).unwrap();
328 assert_eq!(result.outcome, crate::Outcome::Pass);
329 }
330
331 #[test]
332 fn runner_records_zero_messages_for_noop_scheme() {
333 let vector = sample_vector(None);
334 let result = VectorRunner::run(&vector).unwrap();
335 assert_eq!(result.messages_exchanged, 0);
336 assert_eq!(result.bytes_exchanged, 0);
337 }
338
339 #[test]
340 fn runner_fails_when_output_mismatches_expected() {
341 let vector = sample_vector(Some("0xdeadbeef"));
342 let result = VectorRunner::run(&vector).unwrap();
343 assert_eq!(result.outcome, crate::Outcome::Fail);
344 }
345}