use num_bigint::BigUint;
use num_traits::{One, Zero};
use std::time::Duration;
use tacet::helpers::effect::{busy_wait_ns, init_effect_injection};
use tacet::helpers::InputPair;
use tacet::{
skip_if_unreliable, AttackerModel, Exploitability, InconclusiveReason, Outcome, TimingOracle,
};
mod helpers {
use super::*;
pub fn modpow_naive(base: &BigUint, exp: &BigUint, modulus: &BigUint) -> BigUint {
let mut result = BigUint::one();
let mut base = base.clone();
let mut exp = exp.clone();
while exp > BigUint::zero() {
if &exp & BigUint::one() == BigUint::one() {
result = (result * &base) % modulus;
}
base = (&base * &base) % modulus;
exp >>= 1;
}
result
}
pub fn generate_sbox() -> [u8; 256] {
let mut sbox = [0u8; 256];
for (i, item) in sbox.iter_mut().enumerate() {
let mut x = i as u8;
x = (x & 0xF0) >> 4 | (x & 0x0F) << 4;
x = (x & 0xCC) >> 2 | (x & 0x33) << 2;
x = (x & 0xAA) >> 1 | (x & 0x55) << 1;
*item = x ^ 0x63;
}
sbox
}
#[repr(align(64))]
#[allow(dead_code)]
pub struct CacheAligned<T> {
pub data: T,
}
#[allow(dead_code)]
impl<T> CacheAligned<T> {
pub fn new(data: T) -> Self {
Self { data }
}
}
}
#[test]
fn aes_sbox_timing_fast() {
let sbox = helpers::generate_sbox();
let secret_key = 0xABu8;
let indices = InputPair::new(|| secret_key, rand::random::<u8>);
let outcome = TimingOracle::for_attacker(AttackerModel::Research)
.pass_threshold(0.01)
.fail_threshold(0.85)
.time_budget(Duration::from_secs(30))
.test(indices, |idx| {
let val = std::hint::black_box(*idx);
std::hint::black_box(sbox[val as usize]);
});
let outcome = skip_if_unreliable!(outcome, "aes_sbox_timing_fast");
eprintln!("\n[aes_sbox_timing_fast]");
eprintln!("{}", tacet::output::format_outcome(&outcome));
let (leak_probability, effect) = match &outcome {
Outcome::Pass {
leak_probability,
effect,
..
} => (*leak_probability, effect),
Outcome::Fail {
leak_probability,
effect,
..
} => (*leak_probability, effect),
Outcome::Inconclusive {
leak_probability,
effect,
..
} => (*leak_probability, effect),
Outcome::Unmeasurable { .. } => return,
Outcome::Research(_) => return,
};
let has_significant_effect = effect.max_effect_ns > 5.0;
let has_leak_signal = leak_probability > 0.3 || has_significant_effect;
assert!(
has_leak_signal,
"Expected to detect some cache timing effect (got leak_probability={}, max_effect_ns={})",
leak_probability, effect.max_effect_ns
);
}
#[test]
#[ignore = "slow test - run with --ignored"]
fn aes_sbox_timing_thorough() {
let sbox = helpers::generate_sbox();
let secret_key = 0xABu8;
let indices = InputPair::new(|| secret_key, rand::random::<u8>);
let outcome = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.pass_threshold(0.01)
.fail_threshold(0.85)
.time_budget(Duration::from_secs(30))
.test(indices, |idx| {
let val = std::hint::black_box(*idx);
std::hint::black_box(sbox[val as usize]);
});
eprintln!("\n[aes_sbox_timing_thorough]");
eprintln!("{}", tacet::output::format_outcome(&outcome));
match &outcome {
Outcome::Fail {
leak_probability, ..
} => {
assert!(
*leak_probability > 0.5,
"Expected high leak probability with 100k samples (got {})",
leak_probability
);
}
Outcome::Pass {
leak_probability, ..
} => {
panic!(
"Expected Fail outcome for S-box timing leak, got Pass with leak_probability={}",
leak_probability
);
}
Outcome::Inconclusive {
leak_probability,
reason,
..
} => {
panic!(
"Expected Fail outcome for S-box timing leak, got Inconclusive: {:?}, leak_probability={}",
reason, leak_probability
);
}
Outcome::Unmeasurable { recommendation, .. } => {
eprintln!("Skipping: {}", recommendation);
}
&Outcome::Research(_) => {}
}
}
#[test]
fn cache_line_boundary_effects() {
let buffer = vec![0u8; 4096];
let secret_offset_same_line = 0usize;
let secret_offset_diff_line = 64usize;
let indices = InputPair::new(
|| secret_offset_same_line,
|| secret_offset_diff_line + (rand::random::<u32>() as usize % 4) * 64,
);
let outcome = TimingOracle::for_attacker(AttackerModel::Research)
.pass_threshold(0.01)
.fail_threshold(0.85)
.time_budget(Duration::from_secs(30))
.test(indices, |idx| {
let idx_val = std::hint::black_box(*idx);
std::hint::black_box(buffer[idx_val % buffer.len()]);
});
let outcome = skip_if_unreliable!(outcome, "cache_line_boundary_effects");
eprintln!("\n[cache_line_boundary_effects]");
eprintln!("{}", tacet::output::format_outcome(&outcome));
let (leak_probability, effect) = match &outcome {
Outcome::Pass {
leak_probability,
effect,
..
} => (*leak_probability, effect),
Outcome::Fail {
leak_probability,
effect,
..
} => (*leak_probability, effect),
Outcome::Inconclusive {
leak_probability,
effect,
..
} => (*leak_probability, effect),
Outcome::Unmeasurable { .. } => return,
Outcome::Research(_) => return,
};
let has_significant_effect = effect.max_effect_ns > 5.0;
let has_leak_signal = leak_probability > 0.2 || has_significant_effect;
assert!(
has_leak_signal,
"Expected to detect cache line boundary effects (got leak_probability={}, max_effect_ns={})",
leak_probability,
effect.max_effect_ns
);
}
#[test]
fn memory_access_pattern_leak() {
use std::cell::Cell;
let data = vec![rand::random::<u64>(); 1024];
let secret_pattern = [0usize, 64, 128, 192];
let data_len = data.len();
let pattern_idx = Cell::new(0usize);
let indices = InputPair::new(
|| {
let i = pattern_idx.get();
let access_idx = secret_pattern[i % secret_pattern.len()];
pattern_idx.set((i + 1) % secret_pattern.len());
access_idx
},
|| rand::random::<u32>() as usize % data_len,
);
let outcome = TimingOracle::for_attacker(AttackerModel::Research)
.pass_threshold(0.01)
.fail_threshold(0.85)
.time_budget(Duration::from_secs(30))
.test(indices, |access_idx| {
let access_idx_val = std::hint::black_box(*access_idx);
std::hint::black_box(data[access_idx_val]);
});
let outcome = skip_if_unreliable!(outcome, "memory_access_pattern_leak");
eprintln!("\n[memory_access_pattern_leak]");
eprintln!("{}", tacet::output::format_outcome(&outcome));
let (leak_probability, effect) = match &outcome {
Outcome::Pass {
leak_probability,
effect,
..
} => (*leak_probability, effect),
Outcome::Fail {
leak_probability,
effect,
..
} => (*leak_probability, effect),
Outcome::Inconclusive {
leak_probability,
effect,
..
} => (*leak_probability, effect),
Outcome::Unmeasurable { .. } => return,
Outcome::Research(_) => return,
};
let has_leak_signal = leak_probability > 0.2;
assert!(
has_leak_signal,
"Expected to detect memory access pattern timing differences (got leak_probability={})",
leak_probability
);
assert!(
effect.max_effect_ns > 5.0,
"Expected measurable timing effect for memory patterns (got {:.1}ns)",
effect.max_effect_ns
);
}
#[test]
fn modexp_square_and_multiply_timing() {
let base = BigUint::from(5u32);
let modulus = BigUint::from(1000000007u64);
let exp_high_hamming = BigUint::from(0xFFFFu32); let exp_low_hamming = BigUint::from(0x8001u32);
let exponents = InputPair::new(|| exp_high_hamming.clone(), || exp_low_hamming.clone());
let outcome = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.pass_threshold(0.01)
.fail_threshold(0.85)
.time_budget(Duration::from_secs(30))
.test(exponents, |exp| {
std::hint::black_box(helpers::modpow_naive(&base, exp, &modulus));
});
eprintln!("\n{}", tacet::output::format_outcome(&outcome));
match &outcome {
Outcome::Fail {
leak_probability,
effect,
..
} => {
assert!(
*leak_probability > 0.8,
"Expected high leak probability for modexp (got {})",
leak_probability
);
assert!(
effect.max_effect_ns > 50.0,
"Expected significant max_effect_ns magnitude (got {:.1})",
effect.max_effect_ns
);
}
Outcome::Pass {
leak_probability, ..
} => {
panic!(
"Expected Fail outcome for modexp timing leak, got Pass with leak_probability={}",
leak_probability
);
}
Outcome::Inconclusive {
reason: InconclusiveReason::ThresholdElevated { .. },
..
}
| Outcome::Inconclusive {
reason: InconclusiveReason::NotLearning { .. },
..
} => {
eprintln!("Accepted: Inconclusive (timer resolution insufficient)");
}
Outcome::Inconclusive {
leak_probability,
reason,
..
} => {
panic!(
"Expected Fail outcome for modexp timing leak, got Inconclusive: {:?}, leak_probability={}",
reason, leak_probability
);
}
Outcome::Unmeasurable { recommendation, .. } => {
eprintln!("Skipping: {}", recommendation);
}
&Outcome::Research(_) => {}
}
}
#[test]
fn modexp_bit_pattern_timing() {
let base = BigUint::from(7u32);
let modulus = BigUint::from(1000000007u64);
let exp_many_ones = BigUint::from(0xAAAAAAAAu32); let exp_few_ones = BigUint::from(0x80000001u32);
let exponents = InputPair::new(|| exp_many_ones.clone(), || exp_few_ones.clone());
let outcome = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.pass_threshold(0.01)
.fail_threshold(0.85)
.time_budget(Duration::from_secs(30))
.test(exponents, |exp| {
std::hint::black_box(helpers::modpow_naive(&base, exp, &modulus));
});
eprintln!("\n[modexp_bit_pattern_timing]");
eprintln!("{}", tacet::output::format_outcome(&outcome));
match &outcome {
Outcome::Fail {
leak_probability, ..
} => {
assert!(
*leak_probability > 0.8,
"Expected high leak probability for bit pattern timing (got {})",
leak_probability
);
}
Outcome::Pass {
leak_probability, ..
} => {
panic!(
"Expected Fail outcome for bit pattern timing, got Pass with leak_probability={}",
leak_probability
);
}
Outcome::Inconclusive {
reason: InconclusiveReason::ThresholdElevated { .. },
..
} => {
eprintln!("Accepted: ThresholdElevated (timer resolution insufficient)");
}
Outcome::Inconclusive {
leak_probability,
reason,
..
} => {
panic!(
"Expected Fail outcome for bit pattern timing, got Inconclusive: {:?}, leak_probability={}",
reason, leak_probability
);
}
Outcome::Unmeasurable { recommendation, .. } => {
eprintln!("Skipping: {}", recommendation);
}
&Outcome::Research(_) => {}
}
}
#[test]
fn table_lookup_small_l1() {
let table = [rand::random::<u64>(); 4];
let table_len = table.len();
let indices = InputPair::new(|| 0usize, || rand::random::<u32>() as usize % table_len);
let outcome = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.pass_threshold(0.15)
.fail_threshold(0.99)
.time_budget(Duration::from_secs(30))
.test(indices, |idx| {
let idx_val = std::hint::black_box(*idx);
std::hint::black_box(table[idx_val]);
});
let outcome = skip_if_unreliable!(outcome, "table_lookup_small_l1");
eprintln!("\n[table_lookup_small_l1]");
eprintln!("{}", tacet::output::format_outcome(&outcome));
match &outcome {
Outcome::Pass { .. } => {
}
Outcome::Fail { exploitability, .. } => {
assert!(
matches!(exploitability, Exploitability::SharedHardwareOnly),
"Expected SharedHardwareOnly exploitability for L1-resident table (got {:?})",
exploitability
);
}
Outcome::Inconclusive { .. } => {
}
Outcome::Unmeasurable { .. } => {
}
Outcome::Research(_) => {}
}
}
#[test]
fn table_lookup_medium_l2() {
let table = vec![rand::random::<u64>(); 32];
let table_len = table.len();
let indices = InputPair::new(|| 0usize, || rand::random::<u32>() as usize % table_len);
let outcome = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.pass_threshold(0.15)
.fail_threshold(0.99)
.time_budget(Duration::from_secs(30))
.test(indices, |idx| {
let idx_val = std::hint::black_box(*idx);
std::hint::black_box(table[idx_val]);
});
let outcome = skip_if_unreliable!(outcome, "table_lookup_medium_l2");
eprintln!("\n[table_lookup_medium_l2]");
eprintln!("{}", tacet::output::format_outcome(&outcome));
match &outcome {
Outcome::Pass { .. } => {
}
Outcome::Fail { exploitability, .. } => {
assert!(
matches!(
exploitability,
Exploitability::SharedHardwareOnly | Exploitability::Http2Multiplexing
),
"Expected low exploitability for medium table (got {:?})",
exploitability
);
}
Outcome::Inconclusive { .. } => {
}
Outcome::Unmeasurable { .. } => {
}
Outcome::Research(_) => {}
}
}
#[test]
fn table_lookup_large_cache_thrash() {
let table = vec![rand::random::<u64>(); 512];
let table_len = table.len();
let indices = InputPair::new(|| 0usize, || rand::random::<u32>() as usize % table_len);
let outcome = TimingOracle::for_attacker(AttackerModel::Research)
.pass_threshold(0.01)
.fail_threshold(0.85)
.time_budget(Duration::from_secs(30))
.test(indices, |idx| {
let idx_val = std::hint::black_box(*idx);
std::hint::black_box(table[idx_val]);
});
let outcome = skip_if_unreliable!(outcome, "table_lookup_large_cache_thrash");
eprintln!("\n[table_lookup_large_cache_thrash]");
eprintln!("{}", tacet::output::format_outcome(&outcome));
let (leak_probability, effect) = match &outcome {
Outcome::Pass {
leak_probability,
effect,
..
} => (*leak_probability, effect),
Outcome::Fail {
leak_probability,
effect,
..
} => (*leak_probability, effect),
Outcome::Inconclusive {
leak_probability,
effect,
..
} => (*leak_probability, effect),
Outcome::Unmeasurable { .. } => return,
Outcome::Research(_) => return,
};
let has_significant_effect = effect.max_effect_ns > 5.0;
let has_leak_signal = leak_probability > 0.4 || has_significant_effect;
assert!(
has_leak_signal,
"Expected cache effects for large table (got leak_probability={}, max_effect_ns={})",
leak_probability, effect.max_effect_ns
);
}
#[test]
fn effect_pattern_pure_uniform_shift() {
init_effect_injection();
const DELAY_NS: u64 = 2000;
let which_class = InputPair::new(|| 0, || 1);
let outcome = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.pass_threshold(0.01)
.fail_threshold(0.85)
.time_budget(Duration::from_secs(30))
.test(which_class, |class| {
if *class == 0 {
std::hint::black_box(42);
} else {
busy_wait_ns(DELAY_NS);
std::hint::black_box(42);
}
});
let outcome = skip_if_unreliable!(outcome, "effect_pattern_pure_uniform_shift");
eprintln!("\n[effect_pattern_pure_uniform_shift]");
eprintln!("{}", tacet::output::format_outcome(&outcome));
let effect = match &outcome {
Outcome::Pass { effect, .. } => effect,
Outcome::Fail { effect, .. } => effect,
Outcome::Inconclusive { effect, .. } => effect,
Outcome::Unmeasurable { .. } => return,
Outcome::Research(_) => return,
};
assert!(
(1_000.0..=3_000.0).contains(&effect.max_effect_ns),
"Expected max effect ~2μs for 2μs delay (got {:.1}ns) - effect estimation may be broken",
effect.max_effect_ns
);
}
#[test]
fn effect_pattern_pure_tail() {
init_effect_injection();
use std::cell::Cell;
const BASE_NS: u64 = 100; const EXPENSIVE_NS: u64 = 2000; const TAIL_PROBABILITY: f64 = 0.15;
const SAMPLES: usize = 10_000;
let spike_decisions: Vec<bool> = (0..SAMPLES)
.map(|_| rand::random::<f64>() < TAIL_PROBABILITY)
.collect();
let fixed_idx = Cell::new(0usize);
let random_idx = Cell::new(0usize);
let which_class = InputPair::new(|| 0, || 1);
let outcome = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.pass_threshold(0.01)
.fail_threshold(0.85)
.time_budget(Duration::from_secs(30))
.test(which_class, |class| {
if *class == 0 {
let i = fixed_idx.get();
fixed_idx.set(i.wrapping_add(1));
busy_wait_ns(BASE_NS);
let _ = spike_decisions[i % SAMPLES];
std::hint::black_box(42);
} else {
let i = random_idx.get();
random_idx.set(i.wrapping_add(1));
busy_wait_ns(BASE_NS);
if spike_decisions[i % SAMPLES] {
busy_wait_ns(EXPENSIVE_NS);
}
std::hint::black_box(42);
}
});
let outcome = skip_if_unreliable!(outcome, "effect_pattern_pure_tail");
eprintln!("\n[effect_pattern_pure_tail]");
eprintln!("{}", tacet::output::format_outcome(&outcome));
let effect = match &outcome {
Outcome::Pass { effect, .. } => effect,
Outcome::Fail { effect, .. } => effect,
Outcome::Inconclusive { effect, .. } => effect,
Outcome::Unmeasurable { .. } => return,
Outcome::Research(_) => return,
};
if effect.max_effect_ns < 10.0 {
eprintln!(
"Note: Effect not significant (max_effect_ns={:.1}ns)",
effect.max_effect_ns
);
} else {
assert!(
effect.max_effect_ns > 10.0,
"Expected significant effect from tail spike (got {:.1}ns)",
effect.max_effect_ns
);
}
}
#[test]
fn effect_pattern_mixed() {
init_effect_injection();
use std::cell::Cell;
const BASE_NS: u64 = 100; const SPIKE_NS: u64 = 500; const SPIKE_PROBABILITY: f64 = 0.15;
const SAMPLES: usize = 10_000;
let spike_decisions: Vec<bool> = (0..SAMPLES)
.map(|_| rand::random::<f64>() < SPIKE_PROBABILITY)
.collect();
let fixed_idx = Cell::new(0usize);
let random_idx = Cell::new(0usize);
let which_class = InputPair::new(|| 0, || 1);
let outcome = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.pass_threshold(0.01)
.fail_threshold(0.85)
.time_budget(Duration::from_secs(30))
.test(which_class, |class| {
if *class == 0 {
let i = fixed_idx.get();
fixed_idx.set(i.wrapping_add(1));
let _ = spike_decisions[i % SAMPLES];
std::hint::black_box(42);
} else {
let i = random_idx.get();
random_idx.set(i.wrapping_add(1));
busy_wait_ns(BASE_NS);
if spike_decisions[i % SAMPLES] {
busy_wait_ns(SPIKE_NS);
}
std::hint::black_box(42);
}
});
let outcome = skip_if_unreliable!(outcome, "effect_pattern_mixed");
eprintln!("\n[effect_pattern_mixed]");
eprintln!("{}", tacet::output::format_outcome(&outcome));
let effect = match &outcome {
Outcome::Pass { effect, .. } => effect,
Outcome::Fail { effect, .. } => effect,
Outcome::Inconclusive { effect, .. } => effect,
Outcome::Unmeasurable { .. } => return,
Outcome::Research(_) => return,
};
assert!(
effect.max_effect_ns > 3.0,
"Expected max_effect_ns > 3ns (got {:.1}ns)",
effect.max_effect_ns
);
}
#[test]
fn exploitability_negligible() {
use rand::Rng;
let inputs = InputPair::new(
|| [0u8; 32], || {
let mut rng = rand::rng();
let mut arr = [0u8; 32];
rng.fill(&mut arr);
arr
}, );
let outcome = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.pass_threshold(0.15)
.fail_threshold(0.99)
.time_budget(Duration::from_secs(30))
.test(inputs, |input| {
let mut diff = 0u8;
for (a, b) in input.iter().zip([0u8; 32].iter()) {
diff |= a ^ b;
}
std::hint::black_box(diff);
});
let outcome = skip_if_unreliable!(outcome, "exploitability_negligible");
eprintln!("\n[exploitability_negligible]");
eprintln!("{}", tacet::output::format_outcome(&outcome));
match &outcome {
Outcome::Pass {
leak_probability,
effect,
..
} => {
eprintln!(
"No timing leak detected (leak_probability = {:.4})",
leak_probability
);
assert!(
effect.total_effect_ns() < 100.0,
"XOR comparison should have effect < 100ns (got {:.1}ns)",
effect.total_effect_ns()
);
}
Outcome::Fail {
leak_probability,
effect,
exploitability,
..
} => {
eprintln!(
"Small timing detected but should be Negligible (leak_probability = {:.4})",
leak_probability
);
assert_eq!(
*exploitability,
Exploitability::SharedHardwareOnly,
"XOR comparison should have SharedHardwareOnly exploitability. leak_prob: {:.4}, max_effect={:.1}ns",
leak_probability,
effect.max_effect_ns
);
}
Outcome::Inconclusive {
leak_probability,
effect,
..
} => {
eprintln!(
"Inconclusive result (leak_probability = {:.4}, effect = {:.1}ns)",
leak_probability,
effect.total_effect_ns()
);
}
Outcome::Unmeasurable { .. } => {
}
Outcome::Research(_) => {}
}
}
#[test]
fn exploitability_standard_remote() {
init_effect_injection();
const MEDIUM_DELAY_NS: u64 = 250;
let which_class = InputPair::new(|| 0, || 1);
let outcome = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.pass_threshold(0.01)
.fail_threshold(0.85)
.time_budget(Duration::from_secs(30))
.test(which_class, |class| {
if *class == 0 {
std::hint::black_box(42);
} else {
busy_wait_ns(MEDIUM_DELAY_NS);
std::hint::black_box(42);
}
});
eprintln!("\n[exploitability_standard_remote]");
eprintln!("{}", tacet::output::format_outcome(&outcome));
match &outcome {
Outcome::Fail {
exploitability,
effect,
..
} => {
assert!(
matches!(
exploitability,
Exploitability::Http2Multiplexing | Exploitability::StandardRemote
),
"Expected Http2Multiplexing or StandardRemote for ~250ns delay (got {:?}) - effect estimation may be broken",
exploitability
);
assert!(
(100.0..=500.0).contains(&effect.max_effect_ns),
"Expected max effect ~250ns for 250ns delay (got {:.1}ns) - effect estimation may be broken",
effect.max_effect_ns
);
}
Outcome::Pass {
leak_probability, ..
} => {
panic!(
"Expected Fail outcome for medium delay, got Pass with leak_probability={}",
leak_probability
);
}
Outcome::Inconclusive {
reason: InconclusiveReason::ThresholdElevated { .. },
..
}
| Outcome::Inconclusive {
reason: InconclusiveReason::NotLearning { .. },
..
} => {
eprintln!("Accepted: Inconclusive (timer resolution insufficient)");
}
Outcome::Inconclusive {
leak_probability,
reason,
..
} => {
panic!(
"Expected Fail outcome for medium delay, got Inconclusive: {:?}, leak_probability={}",
reason, leak_probability
);
}
Outcome::Unmeasurable { recommendation, .. } => {
eprintln!("Skipping: {}", recommendation);
}
&Outcome::Research(_) => {}
}
}
#[test]
fn exploitability_standard_remote_large() {
init_effect_injection();
const LARGE_DELAY_NS: u64 = 2000;
let which_class = InputPair::new(|| 0, || 1);
let outcome = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.pass_threshold(0.01)
.fail_threshold(0.85)
.time_budget(Duration::from_secs(30))
.test(which_class, |class| {
if *class == 0 {
std::hint::black_box(42);
} else {
busy_wait_ns(LARGE_DELAY_NS);
std::hint::black_box(42);
}
});
eprintln!("\n[exploitability_standard_remote_large]");
eprintln!("{}", tacet::output::format_outcome(&outcome));
match &outcome {
Outcome::Fail {
exploitability,
effect,
..
} => {
assert!(
matches!(exploitability, Exploitability::StandardRemote),
"Expected StandardRemote exploitability for ~2μs delay (got {:?}) - effect estimation may be broken",
exploitability
);
assert!(
(1_000.0..=3_000.0).contains(&effect.max_effect_ns),
"Expected max effect ~2μs for 2μs delay (got {:.1}ns) - effect estimation may be broken",
effect.max_effect_ns
);
}
Outcome::Pass {
leak_probability, ..
} => {
panic!(
"Expected Fail outcome for large delay, got Pass with leak_probability={}",
leak_probability
);
}
Outcome::Inconclusive {
reason: InconclusiveReason::ThresholdElevated { .. },
..
} => {
eprintln!("Accepted: ThresholdElevated (timer resolution insufficient)");
}
Outcome::Inconclusive {
leak_probability,
reason,
..
} => {
panic!(
"Expected Fail outcome for large delay, got Inconclusive: {:?}, leak_probability={}",
reason, leak_probability
);
}
Outcome::Unmeasurable { recommendation, .. } => {
eprintln!("Skipping: {}", recommendation);
}
&Outcome::Research(_) => {}
}
}
#[test]
fn exploitability_obvious_leak() {
init_effect_injection();
const HUGE_DELAY_NS: u64 = 50_000;
let which_class = InputPair::new(|| 0, || 1);
let outcome = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.pass_threshold(0.01)
.fail_threshold(0.85)
.time_budget(Duration::from_secs(30))
.test(which_class, |class| {
if *class == 0 {
std::hint::black_box(42);
} else {
busy_wait_ns(HUGE_DELAY_NS);
std::hint::black_box(42);
}
});
eprintln!("\n[exploitability_obvious_leak]");
eprintln!("{}", tacet::output::format_outcome(&outcome));
match &outcome {
Outcome::Fail {
exploitability,
effect,
..
} => {
assert!(
matches!(exploitability, Exploitability::ObviousLeak),
"Expected ObviousLeak exploitability for ~50μs delay (got {:?})",
exploitability
);
assert!(
effect.max_effect_ns >= 8_000.0,
"Expected max effect > 8μs for ObviousLeak classification (got {:.1}ns)",
effect.max_effect_ns
);
}
Outcome::Pass {
leak_probability, ..
} => {
panic!(
"Expected Fail outcome for huge delay, got Pass with leak_probability={}",
leak_probability
);
}
Outcome::Inconclusive {
leak_probability,
reason,
..
} => {
panic!(
"Expected Fail outcome for huge delay, got Inconclusive: {:?}, leak_probability={}",
reason, leak_probability
);
}
Outcome::Unmeasurable { recommendation, .. } => {
eprintln!("Skipping: {}", recommendation);
}
&Outcome::Research(_) => {}
}
}