openentropy_core/sources/signal/
compression_timing.rs1use std::io::Write;
11use std::time::Instant;
12
13use flate2::Compression;
14use flate2::write::ZlibEncoder;
15
16use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
17
18use crate::sources::helpers::{extract_timing_entropy, mach_time};
19
20static COMPRESSION_TIMING_INFO: SourceInfo = SourceInfo {
25 name: "compression_timing",
26 description: "Zlib compression timing jitter from data-dependent branch prediction",
27 physics: "Compresses varying data with zlib and measures per-operation timing. \
28 Compression algorithms have heavily data-dependent branches (Huffman tree \
29 traversal, LZ77 match finding). The CPU\u{2019}s branch predictor state from \
30 ALL running code affects prediction accuracy for these branches. Pipeline \
31 stalls from mispredictions create timing variation.",
32 category: SourceCategory::Signal,
33 platform: Platform::Any,
34 requirements: &[],
35 entropy_rate_estimate: 2.0,
36 composite: false,
37 is_fast: true,
38};
39
40pub struct CompressionTimingSource;
42
43impl EntropySource for CompressionTimingSource {
44 fn info(&self) -> &SourceInfo {
45 &COMPRESSION_TIMING_INFO
46 }
47
48 fn is_available(&self) -> bool {
49 true
50 }
51
52 fn collect(&self, n_samples: usize) -> Vec<u8> {
53 let raw_count = n_samples * 4 + 64;
55 let mut timings: Vec<u64> = Vec::with_capacity(raw_count);
56
57 let mut lcg: u64 = mach_time() | 1;
59
60 for i in 0..raw_count {
61 let data_len = 128 + (lcg as usize % 385);
63 let mut data = vec![0u8; data_len];
64
65 let third = data_len / 3;
67 for byte in data[..third].iter_mut() {
68 lcg = lcg.wrapping_mul(6364136223846793005).wrapping_add(1);
69 *byte = (lcg >> 32) as u8;
70 }
71
72 for (j, byte) in data[third..third * 2].iter_mut().enumerate() {
74 *byte = (j % 4) as u8;
75 }
76
77 for byte in data[third * 2..].iter_mut() {
79 lcg = lcg.wrapping_mul(6364136223846793005).wrapping_add(i as u64);
80 *byte = (lcg >> 32) as u8;
81 }
82
83 let t0 = Instant::now();
84 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
85 let _ = encoder.write_all(&data);
86 let _ = encoder.finish();
87 let elapsed_ns = t0.elapsed().as_nanos() as u64;
88 timings.push(elapsed_ns);
89 }
90
91 extract_timing_entropy(&timings, n_samples)
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn compression_timing_info() {
101 let src = CompressionTimingSource;
102 assert_eq!(src.name(), "compression_timing");
103 assert_eq!(src.info().category, SourceCategory::Signal);
104 assert!(!src.info().composite);
105 }
106
107 #[test]
108 #[ignore] fn compression_timing_collects_bytes() {
110 let src = CompressionTimingSource;
111 assert!(src.is_available());
112 let data = src.collect(64);
113 assert!(!data.is_empty());
114 }
115}