use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
use crate::sources::helpers::{extract_timing_entropy, mach_time};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::thread;
pub struct DVFSRaceSource;
static DVFS_RACE_INFO: SourceInfo = SourceInfo {
name: "dvfs_race",
description: "Cross-core DVFS frequency race between thread pairs",
physics: "Spawns two threads running tight counting loops on different cores. \
After a ~2\u{00b5}s race window, the absolute difference in iteration \
counts captures nondeterminism from: scheduler core placement (P-core vs \
E-core), cache coherence latency for the stop signal, interrupt jitter, \
and cross-core pipeline state differences. On Apple Silicon, P-core and \
E-core clusters have separate frequency domains, but the 2\u{00b5}s window is \
too short for DVFS transitions (~100\u{00b5}s-1ms); the primary entropy comes \
from scheduling and cache-coherence nondeterminism.",
category: SourceCategory::Microarch,
platform: Platform::MacOS,
requirements: &[],
entropy_rate_estimate: 3.0,
composite: false,
is_fast: true,
};
impl EntropySource for DVFSRaceSource {
fn info(&self) -> &SourceInfo {
&DVFS_RACE_INFO
}
fn is_available(&self) -> bool {
cfg!(target_os = "macos")
}
fn collect(&self, n_samples: usize) -> Vec<u8> {
let raw_count = (n_samples * 4 + 64).min(256);
let mut diffs: Vec<u64> = Vec::with_capacity(raw_count);
let window_ticks: u64 = 48;
for _ in 0..raw_count {
let stop = Arc::new(AtomicBool::new(false));
let count1 = Arc::new(AtomicU64::new(0));
let count2 = Arc::new(AtomicU64::new(0));
let ready1 = Arc::new(AtomicBool::new(false));
let ready2 = Arc::new(AtomicBool::new(false));
let s1 = stop.clone();
let c1 = count1.clone();
let r1 = ready1.clone();
let handle1 = thread::spawn(move || {
let mut local_count: u64 = 0;
r1.store(true, Ordering::Release);
while !s1.load(Ordering::Relaxed) {
local_count = local_count.wrapping_add(1);
}
c1.store(local_count, Ordering::Release);
});
let s2 = stop.clone();
let c2 = count2.clone();
let r2 = ready2.clone();
let handle2 = thread::spawn(move || {
let mut local_count: u64 = 0;
r2.store(true, Ordering::Release);
while !s2.load(Ordering::Relaxed) {
local_count = local_count.wrapping_add(1);
}
c2.store(local_count, Ordering::Release);
});
while !ready1.load(Ordering::Acquire) || !ready2.load(Ordering::Acquire) {
std::hint::spin_loop();
}
let t_start = mach_time();
let t_end = t_start.wrapping_add(window_ticks);
while mach_time() < t_end {
std::hint::spin_loop();
}
stop.store(true, Ordering::Release);
let _ = handle1.join();
let _ = handle2.join();
let v1 = count1.load(Ordering::Acquire);
let v2 = count2.load(Ordering::Acquire);
let diff = v1.abs_diff(v2);
diffs.push(diff);
}
extract_timing_entropy(&diffs, n_samples)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn info() {
let src = DVFSRaceSource;
assert_eq!(src.info().name, "dvfs_race");
assert!(matches!(src.info().category, SourceCategory::Microarch));
assert!(!src.info().composite);
}
#[test]
#[ignore] fn collects_bytes() {
let src = DVFSRaceSource;
assert!(src.is_available());
let data = src.collect(64);
assert!(!data.is_empty());
let unique: std::collections::HashSet<u8> = data.iter().copied().collect();
assert!(unique.len() > 1, "Expected variation in collected bytes");
}
}