openentropy_core/sources/
cross_domain.rs1use std::io::Write;
8
9use tempfile::NamedTempFile;
10
11use crate::source::{EntropySource, 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::CrossDomain,
28 platform_requirements: &[],
29 entropy_rate_estimate: 1500.0,
30 composite: false,
31};
32
33pub struct CPUIOBeatSource;
35
36impl EntropySource for CPUIOBeatSource {
37 fn info(&self) -> &SourceInfo {
38 &CPU_IO_BEAT_INFO
39 }
40
41 fn is_available(&self) -> bool {
42 true
43 }
44
45 fn collect(&self, n_samples: usize) -> Vec<u8> {
46 let mut tmpfile = match NamedTempFile::new() {
47 Ok(f) => f,
48 Err(_) => return Vec::new(),
49 };
50
51 let raw_count = n_samples * 10 + 64;
54 let mut timings: Vec<u64> = Vec::with_capacity(raw_count);
55
56 for i in 0..raw_count {
57 let t0 = mach_time();
58
59 let mut x: u64 = t0;
61 for _ in 0..50 {
62 x = x.wrapping_mul(6364136223846793005).wrapping_add(1);
63 }
64 std::hint::black_box(x);
65
66 let t1 = mach_time();
67
68 let buf = [i as u8; 64];
70 let _ = tmpfile.write_all(&buf);
71 if i % 16 == 0 {
72 let _ = tmpfile.flush();
73 }
74
75 let t2 = mach_time();
76
77 timings.push(t1.wrapping_sub(t0)); timings.push(t2.wrapping_sub(t1)); }
81
82 extract_timing_entropy(&timings, n_samples)
83 }
84}
85
86const MEM_BUFFER_SIZE: usize = 16 * 1024 * 1024;
92
93static CPU_MEMORY_BEAT_INFO: SourceInfo = SourceInfo {
94 name: "cpu_memory_beat",
95 description: "Cross-domain beat frequency between CPU computation and random memory access timing",
96 physics: "Interleaves CPU computation with random memory accesses to large arrays \
97 (>L2 cache). The memory controller runs on its own clock domain. Cache misses \
98 force the CPU to wait for the memory controller\u{2019}s arbitration, whose timing \
99 depends on: DRAM refresh state, competing DMA from GPU/ANE, and row buffer \
100 conflicts.",
101 category: SourceCategory::CrossDomain,
102 platform_requirements: &[],
103 entropy_rate_estimate: 2500.0,
104 composite: false,
105};
106
107pub struct CPUMemoryBeatSource;
110
111impl EntropySource for CPUMemoryBeatSource {
112 fn info(&self) -> &SourceInfo {
113 &CPU_MEMORY_BEAT_INFO
114 }
115
116 fn is_available(&self) -> bool {
117 true
118 }
119
120 fn collect(&self, n_samples: usize) -> Vec<u8> {
121 let mut buffer = vec![0u8; MEM_BUFFER_SIZE];
123
124 for (i, byte) in buffer.iter_mut().enumerate() {
126 *byte = i as u8;
127 }
128
129 let raw_count = n_samples * 10 + 64;
130 let mut timings: Vec<u64> = Vec::with_capacity(raw_count);
131
132 let mut lcg: u64 = mach_time() | 1;
134
135 for _ in 0..raw_count {
136 let t0 = mach_time();
137
138 let mut x: u64 = t0;
140 for _ in 0..50 {
141 x = x.wrapping_mul(6364136223846793005).wrapping_add(1);
142 }
143 std::hint::black_box(x);
144
145 let t1 = mach_time();
146
147 lcg = lcg
149 .wrapping_mul(6364136223846793005)
150 .wrapping_add(1442695040888963407);
151 let idx = (lcg as usize) % MEM_BUFFER_SIZE;
152 let val = unsafe { std::ptr::read_volatile(&buffer[idx]) };
154 std::hint::black_box(val);
155
156 let t2 = mach_time();
157
158 timings.push(t1.wrapping_sub(t0)); timings.push(t2.wrapping_sub(t1)); }
161
162 extract_timing_entropy(&timings, n_samples)
163 }
164}
165
166const MULTI_BUFFER_SIZE: usize = 4 * 1024 * 1024;
172
173static MULTI_DOMAIN_BEAT_INFO: SourceInfo = SourceInfo {
174 name: "multi_domain_beat",
175 description: "Composite beat frequency across CPU, memory, disk I/O, and kernel syscall clock domains",
176 physics: "Rapidly interleaves operations across 4 clock domains: CPU computation, memory \
177 access, disk I/O, and kernel syscalls. Each domain has its own PLL and \
178 arbitration logic. The composite timing captures interference patterns \
179 between all domains simultaneously.",
180 category: SourceCategory::CrossDomain,
181 platform_requirements: &[],
182 entropy_rate_estimate: 3000.0,
183 composite: false,
184};
185
186pub struct MultiDomainBeatSource;
189
190impl EntropySource for MultiDomainBeatSource {
191 fn info(&self) -> &SourceInfo {
192 &MULTI_DOMAIN_BEAT_INFO
193 }
194
195 fn is_available(&self) -> bool {
196 true
197 }
198
199 fn collect(&self, n_samples: usize) -> Vec<u8> {
200 let mut buffer = vec![0u8; MULTI_BUFFER_SIZE];
202 for (i, byte) in buffer.iter_mut().enumerate() {
203 *byte = i as u8;
204 }
205
206 let raw_count = n_samples * 10 + 64;
207 let mut timings: Vec<u64> = Vec::with_capacity(raw_count * 4);
208
209 let mut lcg: u64 = mach_time() | 1;
210
211 for _ in 0..raw_count {
212 let t0 = mach_time();
214 let mut x: u64 = t0;
215 for _ in 0..50 {
216 x = x.wrapping_mul(6364136223846793005).wrapping_add(1);
217 }
218 std::hint::black_box(x);
219 let t1 = mach_time();
220
221 lcg = lcg
223 .wrapping_mul(6364136223846793005)
224 .wrapping_add(1442695040888963407);
225 let idx = (lcg as usize) % MULTI_BUFFER_SIZE;
226 let val = unsafe { std::ptr::read_volatile(&buffer[idx]) };
228 std::hint::black_box(val);
229 let t2 = mach_time();
230
231 unsafe { libc::getpid() };
233 let t3 = mach_time();
234
235 timings.push(t1.wrapping_sub(t0)); timings.push(t2.wrapping_sub(t1)); timings.push(t3.wrapping_sub(t2)); }
240
241 extract_timing_entropy(&timings, n_samples)
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::super::helpers::extract_lsbs_u64;
248 use super::*;
249
250 #[test]
251 fn cpu_io_beat_info() {
252 let src = CPUIOBeatSource;
253 assert_eq!(src.name(), "cpu_io_beat");
254 assert_eq!(src.info().category, SourceCategory::CrossDomain);
255 assert!((src.info().entropy_rate_estimate - 1500.0).abs() < f64::EPSILON);
256 }
257
258 #[test]
259 #[ignore] fn cpu_io_beat_collects_bytes() {
261 let src = CPUIOBeatSource;
262 assert!(src.is_available());
263 let data = src.collect(64);
264 assert!(!data.is_empty());
265 assert!(data.len() <= 64);
266 }
267
268 #[test]
269 fn cpu_memory_beat_info() {
270 let src = CPUMemoryBeatSource;
271 assert_eq!(src.name(), "cpu_memory_beat");
272 assert_eq!(src.info().category, SourceCategory::CrossDomain);
273 assert!((src.info().entropy_rate_estimate - 2500.0).abs() < f64::EPSILON);
274 }
275
276 #[test]
277 #[ignore] fn cpu_memory_beat_collects_bytes() {
279 let src = CPUMemoryBeatSource;
280 assert!(src.is_available());
281 let data = src.collect(64);
282 assert!(!data.is_empty());
283 assert!(data.len() <= 64);
284 }
285
286 #[test]
287 fn multi_domain_beat_info() {
288 let src = MultiDomainBeatSource;
289 assert_eq!(src.name(), "multi_domain_beat");
290 assert_eq!(src.info().category, SourceCategory::CrossDomain);
291 assert!((src.info().entropy_rate_estimate - 3000.0).abs() < f64::EPSILON);
292 }
293
294 #[test]
295 #[ignore] fn multi_domain_beat_collects_bytes() {
297 let src = MultiDomainBeatSource;
298 assert!(src.is_available());
299 let data = src.collect(64);
300 assert!(!data.is_empty());
301 assert!(data.len() <= 64);
302 }
303
304 #[test]
305 fn extract_lsbs_basic() {
306 let deltas = vec![1u64, 2, 3, 4, 5, 6, 7, 8];
307 let bytes = extract_lsbs_u64(&deltas);
308 assert_eq!(bytes.len(), 1);
310 assert_eq!(bytes[0], 0xAA);
311 }
312}