Skip to main content

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}