openentropy_core/sources/frontier/
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::Thermal,
28 platform: Platform::Any,
29 requirements: &[],
30 entropy_rate_estimate: 300.0,
31 composite: false,
32};
33
34pub struct DenormalTimingSource;
36
37impl EntropySource for DenormalTimingSource {
38 fn info(&self) -> &SourceInfo {
39 &DENORMAL_TIMING_INFO
40 }
41
42 fn is_available(&self) -> bool {
43 true
44 }
45
46 fn collect(&self, n_samples: usize) -> Vec<u8> {
47 let raw_count = n_samples * 4 + 64;
48 let mut timings: Vec<u64> = Vec::with_capacity(raw_count);
49
50 let mut lcg: u64 = mach_time() | 1;
52 let mut denormals = [0.0f64; OPS_PER_SAMPLE];
53 for d in denormals.iter_mut() {
54 lcg = lcg.wrapping_mul(6364136223846793005).wrapping_add(1);
55 let bits = lcg & 0x000F_FFFF_FFFF_FFFF_u64;
57 *d = f64::from_bits(bits);
58 }
59
60 for _ in 0..raw_count {
61 lcg = lcg.wrapping_mul(6364136223846793005).wrapping_add(1);
63 let start_idx = (lcg >> 32) as usize % OPS_PER_SAMPLE;
64
65 let mut acc = denormals[start_idx];
66
67 let t0 = mach_time();
68 for i in 0..OPS_PER_SAMPLE {
69 let idx = (start_idx + i) % OPS_PER_SAMPLE;
70 acc *= denormals[idx];
71 acc += denormals[(idx + 1) % OPS_PER_SAMPLE];
72 }
73 let t1 = mach_time();
74
75 std::hint::black_box(acc);
77 timings.push(t1.wrapping_sub(t0));
78 }
79
80 extract_timing_entropy(&timings, n_samples)
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 #[test]
89 fn info() {
90 let src = DenormalTimingSource;
91 assert_eq!(src.name(), "denormal_timing");
92 assert_eq!(src.info().category, SourceCategory::Thermal);
93 assert!(!src.info().composite);
94 }
95
96 #[test]
97 #[ignore] fn collects_bytes() {
99 let src = DenormalTimingSource;
100 assert!(src.is_available());
101 let data = src.collect(64);
102 assert!(!data.is_empty());
103 assert!(data.len() <= 64);
104 }
105}