openentropy_core/sources/
cross_domain.rs1use std::io::Write;
8
9use tempfile::NamedTempFile;
10
11use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
12
13use super::helpers::{extract_timing_entropy, mach_time};
14
15static CPU_IO_BEAT_INFO: SourceInfo = SourceInfo {
20 name: "cpu_io_beat",
21 description: "Cross-domain beat frequency between CPU computation and disk I/O timing",
22 physics: "Alternates CPU-bound computation with disk I/O operations and measures the \
23 transition timing. The CPU and I/O subsystem run on independent clock domains \
24 with separate PLLs. When operations cross domains, the beat frequency of their \
25 PLLs creates timing jitter. This is analogous to the acoustic beat frequency \
26 between two tuning forks.",
27 category: SourceCategory::Composite,
28 platform: Platform::Any,
29 requirements: &[],
30 entropy_rate_estimate: 1500.0,
31 composite: false,
32};
33
34pub struct CPUIOBeatSource;
36
37impl EntropySource for CPUIOBeatSource {
38 fn info(&self) -> &SourceInfo {
39 &CPU_IO_BEAT_INFO
40 }
41
42 fn is_available(&self) -> bool {
43 true
44 }
45
46 fn collect(&self, n_samples: usize) -> Vec<u8> {
47 let mut tmpfile = match NamedTempFile::new() {
48 Ok(f) => f,
49 Err(_) => return Vec::new(),
50 };
51
52 let raw_count = n_samples * 10 + 64;
55 let mut timings: Vec<u64> = Vec::with_capacity(raw_count);
56
57 for i in 0..raw_count {
58 let t0 = mach_time();
59
60 let mut x: u64 = t0;
62 for _ in 0..50 {
63 x = x.wrapping_mul(6364136223846793005).wrapping_add(1);
64 }
65 std::hint::black_box(x);
66
67 let t1 = mach_time();
68
69 let buf = [i as u8; 64];
71 let _ = tmpfile.write_all(&buf);
72 if i % 16 == 0 {
73 let _ = tmpfile.flush();
74 }
75
76 let t2 = mach_time();
77
78 timings.push(t1.wrapping_sub(t0)); timings.push(t2.wrapping_sub(t1)); }
82
83 extract_timing_entropy(&timings, n_samples)
84 }
85}
86
87const MEM_BUFFER_SIZE: usize = 16 * 1024 * 1024;
93
94static CPU_MEMORY_BEAT_INFO: SourceInfo = SourceInfo {
95 name: "cpu_memory_beat",
96 description: "Cross-domain beat frequency between CPU computation and random memory access timing",
97 physics: "Interleaves CPU computation with random memory accesses to large arrays \
98 (>L2 cache). The memory controller runs on its own clock domain. Cache misses \
99 force the CPU to wait for the memory controller\u{2019}s arbitration, whose timing \
100 depends on: DRAM refresh state, competing DMA from GPU/ANE, and row buffer \
101 conflicts.",
102 category: SourceCategory::Composite,
103 platform: Platform::Any,
104 requirements: &[],
105 entropy_rate_estimate: 2500.0,
106 composite: false,
107};
108
109pub struct CPUMemoryBeatSource;
112
113impl EntropySource for CPUMemoryBeatSource {
114 fn info(&self) -> &SourceInfo {
115 &CPU_MEMORY_BEAT_INFO
116 }
117
118 fn is_available(&self) -> bool {
119 true
120 }
121
122 fn collect(&self, n_samples: usize) -> Vec<u8> {
123 let mut buffer = vec![0u8; MEM_BUFFER_SIZE];
125
126 for (i, byte) in buffer.iter_mut().enumerate() {
128 *byte = i as u8;
129 }
130
131 let raw_count = n_samples * 10 + 64;
132 let mut timings: Vec<u64> = Vec::with_capacity(raw_count);
133
134 let mut lcg: u64 = mach_time() | 1;
136
137 for _ in 0..raw_count {
138 let t0 = mach_time();
139
140 let mut x: u64 = t0;
142 for _ in 0..50 {
143 x = x.wrapping_mul(6364136223846793005).wrapping_add(1);
144 }
145 std::hint::black_box(x);
146
147 let t1 = mach_time();
148
149 lcg = lcg
151 .wrapping_mul(6364136223846793005)
152 .wrapping_add(1442695040888963407);
153 let idx = (lcg as usize) % MEM_BUFFER_SIZE;
154 let val = unsafe { std::ptr::read_volatile(&buffer[idx]) };
156 std::hint::black_box(val);
157
158 let t2 = mach_time();
159
160 timings.push(t1.wrapping_sub(t0)); timings.push(t2.wrapping_sub(t1)); }
163
164 extract_timing_entropy(&timings, n_samples)
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::super::helpers::extract_lsbs_u64;
171 use super::*;
172
173 #[test]
174 fn cpu_io_beat_info() {
175 let src = CPUIOBeatSource;
176 assert_eq!(src.name(), "cpu_io_beat");
177 assert_eq!(src.info().category, SourceCategory::Composite);
178 assert!((src.info().entropy_rate_estimate - 1500.0).abs() < f64::EPSILON);
179 }
180
181 #[test]
182 #[ignore] fn cpu_io_beat_collects_bytes() {
184 let src = CPUIOBeatSource;
185 assert!(src.is_available());
186 let data = src.collect(64);
187 assert!(!data.is_empty());
188 assert!(data.len() <= 64);
189 }
190
191 #[test]
192 fn cpu_memory_beat_info() {
193 let src = CPUMemoryBeatSource;
194 assert_eq!(src.name(), "cpu_memory_beat");
195 assert_eq!(src.info().category, SourceCategory::Composite);
196 assert!((src.info().entropy_rate_estimate - 2500.0).abs() < f64::EPSILON);
197 }
198
199 #[test]
200 #[ignore] fn cpu_memory_beat_collects_bytes() {
202 let src = CPUMemoryBeatSource;
203 assert!(src.is_available());
204 let data = src.collect(64);
205 assert!(!data.is_empty());
206 assert!(data.len() <= 64);
207 }
208
209 #[test]
210 fn extract_lsbs_basic() {
211 let deltas = vec![1u64, 2, 3, 4, 5, 6, 7, 8];
212 let bytes = extract_lsbs_u64(&deltas);
213 assert_eq!(bytes.len(), 1);
215 assert_eq!(bytes[0], 0xAA);
216 }
217}