parlov_analysis/existence/
analyzer.rs1use parlov_core::{DifferentialSet, OracleClass, OracleResult, OracleVerdict, ProbeExchange, Vector};
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 if is_relevant_differential(data) {
33 SampleDecision::Complete(Box::new(build_result(data)))
34 } else {
35 SampleDecision::Complete(Box::new(not_fired_result(data)))
36 }
37 } else {
38 SampleDecision::Complete(Box::new(unstable_result(data)))
39 }
40 }
41
42 fn oracle_class(&self) -> OracleClass {
43 OracleClass::Existence
44 }
45}
46
47fn build_result(data: &DifferentialSet) -> OracleResult {
49 let b0 = data.baseline[0].response.status;
50 let p0 = data.probe[0].response.status;
51
52 let signals = extract_all_signals(data);
53 classify(b0, p0, signals, &data.technique)
54}
55
56fn extract_all_signals(data: &DifferentialSet) -> Vec<parlov_core::Signal> {
58 let mut out = Vec::new();
59 out.extend(signals::status_code::extract(data));
60 out.extend(signals::header::extract(data));
61 out.extend(signals::metadata::extract(data));
62 out.extend(signals::body::extract(data));
63 out
64}
65
66fn is_consistent(exchanges: &[ProbeExchange]) -> bool {
68 exchanges
69 .iter()
70 .all(|e| e.response.status == exchanges[0].response.status)
71}
72
73fn unstable_result(data: &DifferentialSet) -> OracleResult {
74 let baseline_stable = is_consistent(&data.baseline);
75 let probe_stable = is_consistent(&data.probe);
76
77 let which = match (baseline_stable, probe_stable) {
78 (false, false) => "baseline and probe sides",
79 (false, true) => "baseline side",
80 (true, false) => "probe side",
81 (true, true) => unreachable!("unstable_result called when both sides are stable"),
82 };
83
84 OracleResult {
85 class: OracleClass::Existence,
86 verdict: OracleVerdict::NotPresent,
87 severity: None,
88 confidence: 0,
89 impact_class: None,
90 reasons: vec![],
91 signals: vec![parlov_core::Signal {
92 kind: parlov_core::SignalKind::StatusCodeDiff,
93 evidence: format!("unstable: {which}"),
94 rfc_basis: None,
95 }],
96 technique_id: Some(data.technique.id.to_string()),
97 vector: Some(data.technique.vector),
98 normative_strength: Some(data.technique.strength),
99 label: None,
100 leaks: None,
101 rfc_basis: None,
102 }
103}
104
105fn is_relevant_differential(data: &DifferentialSet) -> bool {
112 let b0 = data.baseline[0].response.status;
113 let p0 = data.probe[0].response.status;
114
115 match data.technique.vector {
116 Vector::RedirectDiff => b0.is_redirection() || p0.is_redirection(),
117 Vector::StatusCodeDiff
119 | Vector::CacheProbing
120 | Vector::ErrorMessageGranularity => true,
121 }
122}
123
124fn not_fired_result(data: &DifferentialSet) -> OracleResult {
130 OracleResult {
131 class: OracleClass::Existence,
132 verdict: OracleVerdict::NotPresent,
133 severity: None,
134 confidence: 0,
135 impact_class: None,
136 reasons: vec![],
137 signals: vec![parlov_core::Signal {
138 kind: parlov_core::SignalKind::StatusCodeDiff,
139 evidence: "technique did not fire: no 3xx status in differential".to_owned(),
140 rfc_basis: None,
141 }],
142 technique_id: Some(data.technique.id.to_string()),
143 vector: Some(data.technique.vector),
144 normative_strength: Some(data.technique.strength),
145 label: None,
146 leaks: None,
147 rfc_basis: None,
148 }
149}
150
151#[cfg(test)]
152#[path = "analyzer_tests.rs"]
153mod tests;