use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
use crate::sources::helpers::extract_timing_entropy;
static TIMER_COALESCING_INFO: SourceInfo = SourceInfo {
name: "timer_coalescing",
description: "OS timer coalescing wakeup jitter from system-wide timer queue state",
physics: "Calls nanosleep(1ns) and measures the actual wakeup latency. The OS \
batches timer wakeups across all processes; actual wakeup time depends on \
pending timers from every process, daemon, and kernel subsystem on the \
machine. Produces bimodal distribution (~3\u{00b5}s / ~13\u{00b5}s clusters on macOS) \
with CV >70%. Intra-cluster jitter encodes the phase of the hardware timer \
interrupt relative to our wakeup request — a system-wide aggregate noise \
source with no per-process equivalent.",
category: SourceCategory::Scheduling,
platform: Platform::Any,
requirements: &[],
entropy_rate_estimate: 2.0,
composite: false,
is_fast: false,
};
pub struct TimerCoalescingSource;
impl EntropySource for TimerCoalescingSource {
fn info(&self) -> &SourceInfo {
&TIMER_COALESCING_INFO
}
fn is_available(&self) -> bool {
cfg!(unix)
}
fn collect(&self, n_samples: usize) -> Vec<u8> {
#[cfg(unix)]
{
collect_unix(n_samples)
}
#[cfg(not(unix))]
{
let _ = n_samples;
Vec::new()
}
}
}
#[cfg(unix)]
fn collect_unix(n_samples: usize) -> Vec<u8> {
use std::time::Instant;
let raw_count = n_samples * 12 + 64;
let mut timings = Vec::with_capacity(raw_count);
let warmup_req = libc_timespec(0, 1);
for _ in 0..32 {
let mut rem = libc_timespec(0, 0);
unsafe { libc::nanosleep(&warmup_req, &mut rem) };
}
for _ in 0..raw_count {
let req = libc_timespec(0, 1); let mut rem = libc_timespec(0, 0);
let t0 = Instant::now();
unsafe { libc::nanosleep(&req, &mut rem) };
let elapsed_ns = t0.elapsed().as_nanos() as u64;
if elapsed_ns < 500_000_000 {
timings.push(elapsed_ns);
}
}
extract_timing_entropy(&timings, n_samples)
}
#[cfg(unix)]
#[inline]
fn libc_timespec(secs: i64, nsecs: i64) -> libc::timespec {
libc::timespec {
tv_sec: secs as libc::time_t,
tv_nsec: nsecs as libc::c_long,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn info() {
let src = TimerCoalescingSource;
assert_eq!(src.info().name, "timer_coalescing");
assert!(matches!(src.info().category, SourceCategory::Scheduling));
assert_eq!(src.info().platform, Platform::Any);
assert!(!src.info().composite);
}
#[test]
#[cfg(unix)]
fn is_available_on_unix() {
assert!(TimerCoalescingSource.is_available());
}
#[test]
#[ignore] fn collects_bytes_with_variation() {
let src = TimerCoalescingSource;
if !src.is_available() {
return;
}
let data = src.collect(32);
assert!(!data.is_empty(), "expected non-empty output");
let unique: std::collections::HashSet<u8> = data.iter().copied().collect();
assert!(
unique.len() > 2,
"expected byte variation from coalescing jitter"
);
}
}