#![allow(clippy::redundant_closure)]
use std::time::Duration;
use tacet::{timing_test, timing_test_checked, AttackerModel, Outcome, TimingOracle};
#[test]
fn macro_minimal_syntax() {
let result = timing_test_checked! {
baseline: || 42u64,
sample: || rand::random::<u64>(),
measure: |input| {
std::hint::black_box(input);
},
};
assert!(
result.is_measurable() || matches!(result, Outcome::Unmeasurable { .. }),
"Macro should produce valid Outcome"
);
}
#[test]
fn macro_with_oracle() {
let result = timing_test_checked! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)),
baseline: || 0u8,
sample: || rand::random::<u8>(),
measure: |input| {
std::hint::black_box(input);
},
};
assert!(result.is_measurable() || matches!(result, Outcome::Unmeasurable { .. }));
}
#[test]
fn macro_with_pre_setup() {
let key = [0u8; 16];
for _ in 0..10 {
std::hint::black_box(&key);
}
let result = timing_test_checked! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)),
baseline: || [0u8; 16],
sample: || rand::random::<[u8; 16]>(),
measure: |input| {
std::hint::black_box((&key, input));
},
};
assert!(result.is_measurable() || matches!(result, Outcome::Unmeasurable { .. }));
}
#[test]
fn macro_with_captures() {
let multiplier = 42u64;
let result = timing_test_checked! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)),
baseline: || 1u64,
sample: || rand::random::<u64>(),
measure: |input| {
let _ = std::hint::black_box(input.wrapping_mul(multiplier));
},
};
assert!(result.is_measurable() || matches!(result, Outcome::Unmeasurable { .. }));
}
#[test]
fn macro_all_fields() {
let secret = [0xFFu8; 32];
let result = timing_test_checked! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)).max_samples(1_000),
baseline: || [0u8; 32],
sample: || rand::random::<[u8; 32]>(),
measure: |input| {
let mut acc = 0u8;
for i in 0..32 {
acc |= secret[i] ^ input[i];
}
std::hint::black_box(acc);
},
};
assert!(result.is_measurable() || matches!(result, Outcome::Unmeasurable { .. }));
}
#[test]
fn macro_field_order_oracle_last() {
let result = timing_test_checked! {
baseline: || 0u8,
sample: || rand::random::<u8>(),
measure: |input| {
std::hint::black_box(input);
},
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)),
};
assert!(result.is_measurable() || matches!(result, Outcome::Unmeasurable { .. }));
}
#[test]
fn macro_field_order_measure_first() {
let result = timing_test_checked! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)),
measure: |input| {
std::hint::black_box(input);
},
baseline: || 0u8,
sample: || rand::random::<u8>(),
};
assert!(result.is_measurable() || matches!(result, Outcome::Unmeasurable { .. }));
}
#[test]
#[ignore] fn macro_tuple_input() {
let result = timing_test_checked! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)),
baseline: || ([0u8; 12], [0u8; 64]),
sample: || (rand::random::<[u8; 12]>(), rand::random::<[u8; 64]>()),
measure: |(nonce, plaintext)| {
std::hint::black_box((nonce, plaintext));
},
};
assert!(result.is_measurable() || matches!(result, Outcome::Unmeasurable { .. }));
}
#[test]
#[ignore] fn macro_vec_input() {
let result = timing_test_checked! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)),
baseline: || vec![0u8; 64],
sample: || (0..64).map(|_| rand::random::<u8>()).collect::<Vec<_>>(),
measure: |input| {
std::hint::black_box(&input[..]);
},
};
assert!(result.is_measurable() || matches!(result, Outcome::Unmeasurable { .. }));
}
#[test]
fn macro_string_input() {
let result = timing_test_checked! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)),
baseline: || String::from("constant_password"),
sample: || format!("random_{:016x}", rand::random::<u64>()),
measure: |input| {
std::hint::black_box(input.len());
},
};
assert!(result.is_measurable() || matches!(result, Outcome::Unmeasurable { .. }));
}
#[test]
fn macro_primitive_u8() {
let result = timing_test_checked! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)),
baseline: || 0u8,
sample: || rand::random::<u8>(),
measure: |input| {
std::hint::black_box(*input);
},
};
assert!(result.is_measurable() || matches!(result, Outcome::Unmeasurable { .. }));
}
#[test]
#[ignore] fn macro_large_array() {
let result = timing_test_checked! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)),
baseline: || [0u8; 256],
sample: || {
let mut arr = [0u8; 256];
for b in &mut arr {
*b = rand::random();
}
arr
},
measure: |input| {
std::hint::black_box(&input[..]);
},
};
assert!(result.is_measurable() || matches!(result, Outcome::Unmeasurable { .. }));
}
#[test]
fn macro_oracle_balanced() {
let result = timing_test_checked! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(30)),
baseline: || 0u64,
sample: || rand::random::<u64>(),
measure: |input| {
std::hint::black_box(input);
},
};
assert!(result.is_measurable() || matches!(result, Outcome::Unmeasurable { .. }));
}
#[test]
fn macro_oracle_calibration() {
let result = timing_test_checked! {
oracle: TimingOracle::for_attacker(AttackerModel::Research).time_budget(Duration::from_secs(5)),
baseline: || 0u64,
sample: || rand::random::<u64>(),
measure: |input| {
std::hint::black_box(input);
},
};
assert!(result.is_measurable() || matches!(result, Outcome::Unmeasurable { .. }));
}
#[test]
fn macro_oracle_chained() {
let result = timing_test_checked! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)).max_samples(2_000).warmup(100),
baseline: || 0u64,
sample: || rand::random::<u64>(),
measure: |input| {
std::hint::black_box(input);
},
};
assert!(result.is_measurable() || matches!(result, Outcome::Unmeasurable { .. }));
}
#[test]
fn macro_complex_measure_body() {
let result = timing_test_checked! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)),
baseline: || [0u8; 32],
sample: || rand::random::<[u8; 32]>(),
measure: |input| {
let mut sum = 0u64;
for byte in input.iter() {
sum = sum.wrapping_add(*byte as u64);
}
let result = sum % 256;
std::hint::black_box(result);
},
};
assert!(result.is_measurable() || matches!(result, Outcome::Unmeasurable { .. }));
}
#[test]
fn macro_measure_with_early_return() {
let result = timing_test_checked! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)),
baseline: || 0u64,
sample: || rand::random::<u64>(),
measure: |input| {
if *input == 0 {
return;
}
std::hint::black_box(input);
},
};
assert!(result.is_measurable() || matches!(result, Outcome::Unmeasurable { .. }));
}
#[test]
fn macro_nested_closures() {
let result = timing_test_checked! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)),
baseline: || [0u8; 16],
sample: || rand::random::<[u8; 16]>(),
measure: |input| {
let process = |data: &[u8]| -> u64 {
data.iter().map(|&b| b as u64).sum()
};
std::hint::black_box(process(input));
},
};
assert!(result.is_measurable() || matches!(result, Outcome::Unmeasurable { .. }));
}
#[test]
fn macro_result_access() {
let outcome = timing_test_checked! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)),
baseline: || 0u64,
sample: || rand::random::<u64>(),
measure: |input| {
std::hint::black_box(input);
},
};
match outcome {
Outcome::Pass {
leak_probability,
effect,
..
}
| Outcome::Fail {
leak_probability,
effect,
..
}
| Outcome::Inconclusive {
leak_probability,
effect,
..
} => {
let _leak_prob = leak_probability;
let _effect = &effect;
assert!(
(0.0..=1.0).contains(&leak_probability),
"Leak probability should be between 0 and 1"
);
}
Outcome::Unmeasurable { recommendation, .. } => {
assert!(!recommendation.is_empty());
}
Outcome::Research(_) => {}
}
}
#[test]
fn macro_no_trailing_comma() {
let result = timing_test_checked! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)),
baseline: || 0u8,
sample: || rand::random::<u8>(),
measure: |input| {
std::hint::black_box(input);
}
};
assert!(result.is_measurable() || matches!(result, Outcome::Unmeasurable { .. }));
}
#[test]
fn macro_move_closure() {
let seed = 42u64;
let result = timing_test_checked! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)),
baseline: || 0u64,
sample: move || rand::random::<u64>().wrapping_add(seed),
measure: |input| {
std::hint::black_box(input);
},
};
assert!(result.is_measurable() || matches!(result, Outcome::Unmeasurable { .. }));
}
#[test]
fn macro_complex_sample_generator() {
let result = timing_test_checked! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)),
baseline: || [0u8; 32],
sample: || {
let mut arr = [0u8; 32];
for (i, byte) in arr.iter_mut().enumerate() {
*byte = ((i as u8).wrapping_mul(17)).wrapping_add(rand::random::<u8>());
}
arr
},
measure: |input| {
std::hint::black_box(input);
},
};
assert!(result.is_measurable() || matches!(result, Outcome::Unmeasurable { .. }));
}
#[test]
fn macro_mutable_state_captured() {
use std::cell::Cell;
let counter = Cell::new(0u64);
let result = timing_test_checked! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)),
baseline: || 1u64,
sample: || rand::random::<u64>(),
measure: |input| {
counter.set(counter.get().wrapping_add(*input));
std::hint::black_box(counter.get());
},
};
assert!(result.is_measurable() || matches!(result, Outcome::Unmeasurable { .. }));
}
#[test]
fn macro_unit_type() {
let result = timing_test_checked! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)),
baseline: || (),
sample: || (),
measure: |_input| {
std::hint::black_box(42);
},
};
assert!(result.is_measurable() || matches!(result, Outcome::Unmeasurable { .. }));
}
#[test]
fn macro_bool_type() {
let result = timing_test_checked! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)),
baseline: || false,
sample: || rand::random::<bool>(),
measure: |input| {
std::hint::black_box(*input);
},
};
assert!(result.is_measurable() || matches!(result, Outcome::Unmeasurable { .. }));
}
#[test]
#[ignore] fn timing_test_returns_outcome() {
let outcome = timing_test! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)),
baseline: || [0u8; 128],
sample: || {
let mut arr = [0u8; 128];
for byte in &mut arr {
*byte = rand::random();
}
arr
},
measure: |arr| {
let mut acc = 0u32;
for byte in arr {
acc = acc.wrapping_mul(31).wrapping_add(*byte as u32);
}
std::hint::black_box(acc);
},
};
if let Some(leak_prob) = outcome.leak_probability() {
assert!((0.0..=1.0).contains(&leak_prob));
}
if let Some(samples) = outcome.samples_used() {
assert!(samples > 0);
}
}
#[test]
fn timing_test_no_pattern_matching_needed() {
let outcome = timing_test! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)),
baseline: || [0u8; 64],
sample: || {
let mut arr = [0u8; 64];
for byte in &mut arr {
*byte = rand::random();
}
arr
},
measure: |arr| {
let mut val = 0u8;
for byte in arr {
val = val.wrapping_add(*byte).wrapping_mul(31);
}
std::hint::black_box(val);
},
};
if let Some(leak_prob) = outcome.leak_probability() {
println!("Leak probability: {:.2}%", leak_prob * 100.0);
}
assert!(outcome.is_measurable() || matches!(outcome, Outcome::Unmeasurable { .. }));
}
#[test]
fn timing_test_checked_returns_outcome() {
let outcome: Outcome = timing_test_checked! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)),
baseline: || 1u64,
sample: || rand::random::<u64>(),
measure: |x| {
std::hint::black_box(*x);
},
};
match outcome {
Outcome::Pass {
leak_probability, ..
}
| Outcome::Fail {
leak_probability, ..
}
| Outcome::Inconclusive {
leak_probability, ..
} => {
assert!(leak_probability >= 0.0);
}
Outcome::Unmeasurable { .. } => {
}
Outcome::Research(_) => {}
}
}
#[test]
fn timing_test_checked_explicit_unmeasurable_handling() {
let outcome = timing_test_checked! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)),
baseline: || (),
sample: || (),
measure: |_| {
},
};
match outcome {
Outcome::Pass {
leak_probability, ..
}
| Outcome::Fail {
leak_probability, ..
}
| Outcome::Inconclusive {
leak_probability, ..
} => {
println!("Measurable: {:.3}", leak_probability);
}
Outcome::Unmeasurable { recommendation, .. } => {
println!("Unmeasurable: {}", recommendation);
}
Outcome::Research(_) => {}
};
}
#[test]
#[ignore] fn both_macros_with_identical_config() {
let outcome_direct = timing_test! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)),
baseline: || [100u8; 64],
sample: || {
let mut arr = [0u8; 64];
for byte in &mut arr {
*byte = rand::random();
}
arr
},
measure: |arr| {
let mut val = 0u32;
for byte in arr {
val = val.wrapping_mul(2).wrapping_add(*byte as u32);
}
std::hint::black_box(val);
},
};
let outcome_checked = timing_test_checked! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)),
baseline: || [100u8; 64],
sample: || {
let mut arr = [0u8; 64];
for byte in &mut arr {
*byte = rand::random();
}
arr
},
measure: |arr| {
let mut val = 0u32;
for byte in arr {
val = val.wrapping_mul(2).wrapping_add(*byte as u32);
}
std::hint::black_box(val);
},
};
if let Some(prob) = outcome_direct.leak_probability() {
assert!(prob >= 0.0);
}
if let Some(prob) = outcome_checked.leak_probability() {
assert!(prob >= 0.0);
}
}
#[test]
fn timing_test_all_optional_fields() {
let multiplier = 7u32;
std::hint::black_box(multiplier);
let outcome = timing_test! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)).max_samples(1000),
baseline: || [10u8; 64],
sample: || {
let mut arr = [0u8; 64];
for byte in &mut arr {
*byte = rand::random();
}
arr
},
measure: |arr| {
let mut val = multiplier;
for byte in arr {
val = val.wrapping_mul(*byte as u32);
}
std::hint::black_box(val);
},
};
if let Some(samples) = outcome.samples_used() {
assert!(samples > 0);
}
}
#[test]
fn timing_test_checked_all_optional_fields() {
let divisor = 3u64;
std::hint::black_box(divisor);
let outcome = timing_test_checked! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)).max_samples(500),
baseline: || 99u64,
sample: || rand::random::<u64>() | 1, measure: |x| {
std::hint::black_box(x.wrapping_div(*x.max(&1)));
},
};
assert!(outcome.is_measurable() || matches!(outcome, Outcome::Unmeasurable { .. }));
}
#[test]
fn timing_test_complex_array_type() {
let outcome = timing_test! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)),
baseline: || [0u8; 64], sample: || {
let mut arr = [0u8; 64];
for byte in &mut arr {
*byte = rand::random();
}
arr
},
measure: |arr| {
let mut acc = 0u8;
for byte in arr {
acc ^= byte;
acc = acc.wrapping_mul(7).wrapping_add(13);
}
std::hint::black_box(acc);
},
};
if let Some(prob) = outcome.leak_probability() {
assert!(prob <= 1.0);
}
}
#[test]
fn timing_test_checked_complex_vec_type() {
let outcome = timing_test_checked! {
oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).time_budget(Duration::from_secs(10)),
baseline: || vec![0u32; 10],
sample: || {
(0..10).map(|_| rand::random::<u32>()).collect()
},
measure: |v| {
let sum: u32 = v.iter().fold(0u32, |acc, x| acc.wrapping_add(*x));
std::hint::black_box(sum);
},
};
assert!(outcome.is_reliable() || !outcome.is_reliable()); }