tacet_macros/lib.rs
1//! Proc macros for tacet.
2//!
3//! This crate provides the `timing_test!` and `timing_test_checked!` macros for
4//! writing timing side-channel tests with compile-time validation.
5//!
6//! See the `tacet` crate documentation for usage examples.
7
8use proc_macro::TokenStream;
9use quote::quote;
10use syn::parse_macro_input;
11
12mod parse;
13
14use parse::TimingTestInput;
15
16/// Create a timing test that returns `Outcome` for pattern matching.
17///
18/// This macro provides a declarative syntax for timing tests that prevents
19/// common mistakes through compile-time checks. Returns `Outcome` which can be
20/// `Pass`, `Fail`, `Inconclusive`, or `Unmeasurable`.
21///
22/// # Returns
23///
24/// Returns `Outcome` which is one of:
25/// - `Outcome::Pass { leak_probability, effect, samples_used, quality, diagnostics }`
26/// - `Outcome::Fail { leak_probability, effect, exploitability, samples_used, quality, diagnostics }`
27/// - `Outcome::Inconclusive { reason, leak_probability, effect, samples_used, quality, diagnostics }`
28/// - `Outcome::Unmeasurable { operation_ns, timer_resolution_ns, platform, recommendation }`
29///
30/// # Syntax
31///
32/// ```ignore
33/// timing_test! {
34/// // Optional: custom oracle configuration (defaults to AdjacentNetwork attacker model)
35/// oracle: TimingOracle::for_attacker(AttackerModel::AdjacentNetwork),
36///
37/// // Required: baseline input generator (closure returning the fixed/baseline value)
38/// baseline: || [0u8; 32],
39///
40/// // Required: sample input generator (closure returning random values)
41/// sample: || rand::random::<[u8; 32]>(),
42///
43/// // Required: measurement body (closure that receives input and performs the operation)
44/// measure: |input| {
45/// encrypt(&input);
46/// },
47/// }
48/// ```
49///
50/// # Example
51///
52/// ```ignore
53/// use tacet::{timing_test, Outcome};
54///
55/// fn main() {
56/// let outcome = timing_test! {
57/// baseline: || [0u8; 32],
58/// sample: || rand::random::<[u8; 32]>(),
59/// measure: |input| {
60/// let _ = std::hint::black_box(&input);
61/// },
62/// };
63///
64/// match outcome {
65/// Outcome::Pass { leak_probability, .. } => {
66/// println!("No leak detected (P={:.1}%)", leak_probability * 100.0);
67/// }
68/// Outcome::Fail { leak_probability, exploitability, .. } => {
69/// println!("Leak detected! P={:.1}%, {:?}", leak_probability * 100.0, exploitability);
70/// }
71/// Outcome::Inconclusive { reason, .. } => {
72/// println!("Inconclusive: {:?}", reason);
73/// }
74/// Outcome::Unmeasurable { recommendation, .. } => {
75/// println!("Operation too fast: {}", recommendation);
76/// }
77/// }
78/// }
79/// ```
80#[proc_macro]
81pub fn timing_test(input: TokenStream) -> TokenStream {
82 let input = parse_macro_input!(input as TimingTestInput);
83 expand_timing_test(input, false).into()
84}
85
86/// Create a timing test that returns `Outcome` for explicit handling.
87///
88/// This macro is identical to `timing_test!` - both return `Outcome`.
89/// It is kept for backwards compatibility.
90///
91/// # Returns
92///
93/// Returns `Outcome` which is one of `Pass`, `Fail`, `Inconclusive`, or `Unmeasurable`.
94///
95/// # Example
96///
97/// ```ignore
98/// use tacet::{timing_test_checked, Outcome};
99///
100/// fn main() {
101/// let outcome = timing_test_checked! {
102/// baseline: || [0u8; 32],
103/// sample: || rand::random::<[u8; 32]>(),
104/// measure: |input| {
105/// let _ = std::hint::black_box(&input);
106/// },
107/// };
108///
109/// match outcome {
110/// Outcome::Pass { leak_probability, .. } |
111/// Outcome::Fail { leak_probability, .. } |
112/// Outcome::Inconclusive { leak_probability, .. } => {
113/// println!("Leak probability: {:.1}%", leak_probability * 100.0);
114/// }
115/// Outcome::Unmeasurable { recommendation, .. } => {
116/// println!("Operation too fast: {}", recommendation);
117/// }
118/// }
119/// }
120/// ```
121#[proc_macro]
122pub fn timing_test_checked(input: TokenStream) -> TokenStream {
123 let input = parse_macro_input!(input as TimingTestInput);
124 expand_timing_test(input, true).into()
125}
126
127fn expand_timing_test(input: TimingTestInput, _checked: bool) -> proc_macro2::TokenStream {
128 let TimingTestInput {
129 oracle,
130 baseline,
131 sample,
132 measure,
133 } = input;
134
135 // Default oracle if not specified - use AdjacentNetwork attacker model with 30s time budget
136 let oracle_expr = oracle.unwrap_or_else(|| {
137 syn::parse_quote!(::tacet::TimingOracle::for_attacker(
138 ::tacet::AttackerModel::AdjacentNetwork
139 )
140 .time_budget(::std::time::Duration::from_secs(30)))
141 });
142
143 // Generate the timing test code - both macros now return Outcome directly
144 quote! {
145 {
146 // Create InputPair from baseline and sample closures
147 let __inputs = ::tacet::helpers::InputPair::new(
148 #baseline,
149 #sample,
150 );
151
152 // Run the test with the new API
153 #oracle_expr.test(__inputs, #measure)
154 }
155 }
156}