Skip to main content

openentropy_core/sources/timing/
clock_jitter.rs

1//! Clock jitter entropy source.
2//!
3//! Measures phase noise between two independent clock oscillators
4//! (`Instant` vs `SystemTime`).
5
6use std::time::{Instant, SystemTime};
7
8use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
9use crate::sources::helpers::extract_timing_entropy;
10
11/// Measures timing jitter between two clock readout paths (`Instant` vs
12/// `SystemTime`). On platforms where these use the same underlying oscillator,
13/// the entropy comes from variable OS abstraction overhead, cache state, and
14/// interrupt timing — not from independent PLLs.
15pub struct ClockJitterSource;
16
17static CLOCK_JITTER_INFO: SourceInfo = SourceInfo {
18    name: "clock_jitter",
19    description: "Timing jitter between Instant and SystemTime readout paths",
20    physics: "Reads both Instant (monotonic) and SystemTime (wall clock) in \
21              rapid succession and measures the time for the pair of reads. \
22              Jitter comes from variable OS clock-readout overhead, cache state, \
23              and interrupt timing between the two clock API calls.",
24    category: SourceCategory::Timing,
25    platform: Platform::Any,
26    requirements: &[],
27    entropy_rate_estimate: 0.5,
28    composite: false,
29    is_fast: true,
30};
31
32impl EntropySource for ClockJitterSource {
33    fn info(&self) -> &SourceInfo {
34        &CLOCK_JITTER_INFO
35    }
36
37    fn is_available(&self) -> bool {
38        true
39    }
40
41    fn collect(&self, n_samples: usize) -> Vec<u8> {
42        let raw_count = n_samples * 4 + 64;
43        let mut timings = Vec::with_capacity(raw_count);
44
45        for _ in 0..raw_count {
46            // Measure the time to read both clock sources back-to-back.
47            // The jitter comes from variable readout overhead and
48            // interrupt/cache interference between the two calls.
49            let t0 = Instant::now();
50
51            let _wall = SystemTime::now()
52                .duration_since(SystemTime::UNIX_EPOCH)
53                .unwrap_or_default();
54            std::hint::black_box(&_wall);
55
56            let elapsed = t0.elapsed().as_nanos() as u64;
57            timings.push(elapsed);
58        }
59
60        extract_timing_entropy(&timings, n_samples)
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67
68    #[test]
69    #[ignore] // Run with: cargo test -- --ignored
70    fn clock_jitter_collects_bytes() {
71        let src = ClockJitterSource;
72        assert!(src.is_available());
73        let data = src.collect(128);
74        assert!(!data.is_empty());
75        assert!(data.len() <= 128);
76        let first = data[0];
77        assert!(data.iter().any(|&b| b != first), "all bytes were identical");
78    }
79
80    #[test]
81    fn source_info_name() {
82        assert_eq!(ClockJitterSource.name(), "clock_jitter");
83    }
84
85    #[test]
86    fn source_info_category() {
87        assert_eq!(ClockJitterSource.info().category, SourceCategory::Timing);
88        assert!(!ClockJitterSource.info().composite);
89    }
90}