openentropy_core/sources/microarch/
denormal_timing.rs1use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
13use crate::sources::helpers::{extract_timing_entropy, mach_time};
14
15const OPS_PER_SAMPLE: usize = 100;
17
18static DENORMAL_TIMING_INFO: SourceInfo = SourceInfo {
19 name: "denormal_timing",
20 description: "Floating-point denormal multiply-accumulate timing jitter",
21 physics: "Times blocks of floating-point operations on denormalized values \
22 (magnitudes between 0 and f64::MIN_POSITIVE). Denormals may trigger \
23 microcode assists on some architectures, creating data-dependent timing. \
24 Even on Apple Silicon where denormal handling is fast in hardware, \
25 residual timing jitter comes from FPU pipeline state, cache line \
26 alignment, and memory controller arbitration.",
27 category: SourceCategory::Microarch,
28 platform: Platform::Any,
29 requirements: &[],
30 entropy_rate_estimate: 0.5,
31 composite: false,
32 is_fast: false,
33};
34
35pub struct DenormalTimingSource;
37
38impl EntropySource for DenormalTimingSource {
39 fn info(&self) -> &SourceInfo {
40 &DENORMAL_TIMING_INFO
41 }
42
43 fn is_available(&self) -> bool {
44 true
45 }
46
47 fn collect(&self, n_samples: usize) -> Vec<u8> {
48 let raw_count = n_samples * 4 + 64;
49 let mut timings: Vec<u64> = Vec::with_capacity(raw_count);
50
51 let mut lcg: u64 = mach_time() | 1;
53 let mut denormals = [0.0f64; OPS_PER_SAMPLE];
54 for d in denormals.iter_mut() {
55 lcg = lcg.wrapping_mul(6364136223846793005).wrapping_add(1);
56 let bits = lcg & 0x000F_FFFF_FFFF_FFFF_u64;
58 *d = f64::from_bits(bits);
59 }
60
61 for _ in 0..raw_count {
62 lcg = lcg.wrapping_mul(6364136223846793005).wrapping_add(1);
64 let start_idx = (lcg >> 32) as usize % OPS_PER_SAMPLE;
65
66 let mut acc = denormals[start_idx];
67
68 let t0 = mach_time();
69 for i in 0..OPS_PER_SAMPLE {
70 let idx = (start_idx + i) % OPS_PER_SAMPLE;
71 acc *= denormals[idx];
72 acc += denormals[(idx + 1) % OPS_PER_SAMPLE];
73 }
74 let t1 = mach_time();
75
76 std::hint::black_box(acc);
78 timings.push(t1.wrapping_sub(t0));
79 }
80
81 extract_timing_entropy(&timings, n_samples)
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn info() {
91 let src = DenormalTimingSource;
92 assert_eq!(src.name(), "denormal_timing");
93 assert_eq!(src.info().category, SourceCategory::Microarch);
94 assert!(!src.info().composite);
95 }
96
97 #[test]
98 #[ignore] fn collects_bytes() {
100 let src = DenormalTimingSource;
101 assert!(src.is_available());
102 let data = src.collect(64);
103 assert!(!data.is_empty());
104 assert!(data.len() <= 64);
105 }
106}