openentropy_core/sources/frontier/
pdn_resonance.rs1use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
12use crate::sources::helpers::{extract_timing_entropy, mach_time};
13
14const MEASUREMENT_ITERS: usize = 100;
16
17static PDN_RESONANCE_INFO: SourceInfo = SourceInfo {
18 name: "pdn_resonance",
19 description: "Power delivery network resonance from cross-core voltage noise",
20 physics: "Runs stress workloads on background threads (memory thrashing, ALU, FPU) \
21 while measuring timing of a fixed small workload on the current thread. \
22 The timing perturbation captures power delivery network (PDN) voltage noise: \
23 LC resonances in PCB power planes, voltage droop from bursty current draw, \
24 and cross-core power supply coupling. Each measurement thread combination \
25 creates a different current profile exciting different PDN modes.",
26 category: SourceCategory::Thermal,
27 platform: Platform::Any,
28 requirements: &[],
29 entropy_rate_estimate: 500.0,
30 composite: false,
31};
32
33pub struct PDNResonanceSource;
35
36impl EntropySource for PDNResonanceSource {
37 fn info(&self) -> &SourceInfo {
38 &PDN_RESONANCE_INFO
39 }
40
41 fn is_available(&self) -> bool {
42 true
43 }
44
45 fn collect(&self, n_samples: usize) -> Vec<u8> {
46 let raw_count = n_samples * 4 + 64;
47 let mut timings: Vec<u64> = Vec::with_capacity(raw_count);
48
49 let running = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(true));
51
52 let mut handles = Vec::new();
53
54 {
56 let running = running.clone();
57 handles.push(std::thread::spawn(move || {
58 let mut buf = vec![0u64; 512 * 1024]; while running.load(std::sync::atomic::Ordering::Relaxed) {
60 for i in (0..buf.len()).step_by(64) {
61 buf[i] = buf[i].wrapping_add(1);
62 }
63 std::hint::black_box(&buf);
64 }
65 }));
66 }
67
68 {
70 let running = running.clone();
71 handles.push(std::thread::spawn(move || {
72 let mut a: u64 = mach_time() | 1;
73 while running.load(std::sync::atomic::Ordering::Relaxed) {
74 for _ in 0..10000 {
75 a = a
76 .wrapping_mul(6364136223846793005)
77 .wrapping_add(1442695040888963407);
78 }
79 std::hint::black_box(a);
80 }
81 }));
82 }
83
84 std::thread::sleep(std::time::Duration::from_millis(1));
86
87 for _ in 0..raw_count {
89 let t0 = mach_time();
90 let mut acc: u64 = 0;
91 for j in 0..MEASUREMENT_ITERS as u64 {
92 acc = acc.wrapping_add(j);
93 }
94 let t1 = mach_time();
95 std::hint::black_box(acc);
96 timings.push(t1.wrapping_sub(t0));
97 }
98
99 running.store(false, std::sync::atomic::Ordering::Relaxed);
101 for h in handles {
102 let _ = h.join();
103 }
104
105 extract_timing_entropy(&timings, n_samples)
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn info() {
115 let src = PDNResonanceSource;
116 assert_eq!(src.name(), "pdn_resonance");
117 assert_eq!(src.info().category, SourceCategory::Thermal);
118 assert!(!src.info().composite);
119 }
120
121 #[test]
122 #[ignore] fn collects_bytes() {
124 let src = PDNResonanceSource;
125 assert!(src.is_available());
126 let data = src.collect(64);
127 assert!(!data.is_empty());
128 assert!(data.len() <= 64);
129 }
130}