parlov_analysis/existence/
analyzer.rs1use parlov_core::{DifferentialSet, OracleClass, OracleResult, OracleVerdict, ProbeExchange};
4
5use crate::signals;
6use crate::{Analyzer, SampleDecision};
7
8use super::classifier::classify;
9
10pub struct ExistenceAnalyzer;
16
17impl Analyzer for ExistenceAnalyzer {
18 fn evaluate(&self, data: &DifferentialSet) -> SampleDecision {
19 let b0 = data.baseline[0].response.status;
20 let p0 = data.probe[0].response.status;
21
22 if b0 == p0 {
23 return SampleDecision::Complete(Box::new(build_result(data)));
24 }
25
26 if data.baseline.len() < 3 {
27 return SampleDecision::NeedMore;
28 }
29
30 let stable = is_consistent(&data.baseline) && is_consistent(&data.probe);
31 if stable {
32 SampleDecision::Complete(Box::new(build_result(data)))
33 } else {
34 SampleDecision::Complete(Box::new(unstable_result(data)))
35 }
36 }
37
38 fn oracle_class(&self) -> OracleClass {
39 OracleClass::Existence
40 }
41}
42
43fn build_result(data: &DifferentialSet) -> OracleResult {
45 let b0 = data.baseline[0].response.status;
46 let p0 = data.probe[0].response.status;
47
48 let signals = extract_all_signals(data);
49 classify(b0, p0, signals, &data.technique)
50}
51
52fn extract_all_signals(data: &DifferentialSet) -> Vec<parlov_core::Signal> {
54 let mut out = Vec::new();
55 out.extend(signals::status_code::extract(data));
56 out.extend(signals::header::extract(data));
57 out.extend(signals::metadata::extract(data));
58 out
59}
60
61fn is_consistent(exchanges: &[ProbeExchange]) -> bool {
63 exchanges
64 .iter()
65 .all(|e| e.response.status == exchanges[0].response.status)
66}
67
68fn unstable_result(data: &DifferentialSet) -> OracleResult {
69 let baseline_stable = is_consistent(&data.baseline);
70 let probe_stable = is_consistent(&data.probe);
71
72 let which = match (baseline_stable, probe_stable) {
73 (false, false) => "baseline and probe sides",
74 (false, true) => "baseline side",
75 (true, false) => "probe side",
76 (true, true) => unreachable!("unstable_result called when both sides are stable"),
77 };
78
79 OracleResult {
80 class: OracleClass::Existence,
81 verdict: OracleVerdict::NotPresent,
82 severity: None,
83 confidence: 0,
84 impact_class: None,
85 reasons: vec![],
86 signals: vec![parlov_core::Signal {
87 kind: parlov_core::SignalKind::StatusCodeDiff,
88 evidence: format!("unstable: {which}"),
89 rfc_basis: None,
90 }],
91 technique_id: Some(data.technique.id.to_string()),
92 vector: Some(data.technique.vector),
93 normative_strength: Some(data.technique.strength),
94 label: None,
95 leaks: None,
96 rfc_basis: None,
97 }
98}
99
100#[cfg(test)]
101#[path = "analyzer_tests.rs"]
102mod tests;