use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::thread;
use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
use crate::sources::helpers::{extract_timing_entropy, mach_time};
static ICC_ATOMIC_CONTENTION_INFO: SourceInfo = SourceInfo {
name: "icc_atomic_contention",
description: "Apple Silicon ICC bus arbitration timing via cross-core atomic contention",
physics: "Two threads race to atomically increment the same cache line. Each \
increment requires the ICC coherency fabric to transfer the cache line \
between cores via MESI invalidation+transfer. The arbitration traverses \
the ICC bus, which carries all coherency traffic from all running processes \
on the chip. Measured: CV=191\u{2013}195%, range 0\u{2013}209 ticks (0ns\u{2013}8.7\u{00b5}s). \
LSB bias of 0.188 is a microarchitectural constant: ICC coherency transfers \
always complete in even hardware tick counts.",
category: SourceCategory::Microarch,
platform: Platform::MacOS,
requirements: &[],
entropy_rate_estimate: 2.5,
composite: false,
is_fast: false,
};
pub struct ICCAtomicContentionSource;
impl EntropySource for ICCAtomicContentionSource {
fn info(&self) -> &SourceInfo {
&ICC_ATOMIC_CONTENTION_INFO
}
fn is_available(&self) -> bool {
cfg!(target_os = "macos")
}
fn collect(&self, n_samples: usize) -> Vec<u8> {
let raw_per_thread = n_samples * 8 + 64;
let shared = Arc::new(AtomicU64::new(0));
let shared2 = shared.clone();
let ready = Arc::new(AtomicU64::new(0));
let ready2 = ready.clone();
let thread_timings: Arc<std::sync::Mutex<Vec<u64>>> =
Arc::new(std::sync::Mutex::new(Vec::with_capacity(raw_per_thread)));
let thread_timings2 = thread_timings.clone();
let raw = raw_per_thread;
let handle = thread::spawn(move || {
ready2.store(1, Ordering::Release);
let mut local: Vec<u64> = Vec::with_capacity(raw);
for _ in 0..32 {
shared2.fetch_add(1, Ordering::SeqCst);
}
for _ in 0..raw {
let t0 = mach_time();
shared2.fetch_add(1, Ordering::SeqCst);
let elapsed = mach_time().wrapping_sub(t0);
local.push(elapsed);
}
*thread_timings2.lock().unwrap() = local;
});
while ready.load(Ordering::Acquire) == 0 {
thread::yield_now();
}
let mut main_timings: Vec<u64> = Vec::with_capacity(raw_per_thread);
for _ in 0..32 {
shared.fetch_add(1, Ordering::SeqCst);
}
for _ in 0..raw_per_thread {
let t0 = mach_time();
shared.fetch_add(1, Ordering::SeqCst);
let elapsed = mach_time().wrapping_sub(t0);
if elapsed < 240_000 {
main_timings.push(elapsed);
}
}
let _ = handle.join();
let contender_timings = thread_timings.lock().unwrap_or_else(|e| e.into_inner());
let mut combined: Vec<u64> =
Vec::with_capacity(main_timings.len() + contender_timings.len());
let min_len = main_timings.len().min(contender_timings.len());
for i in 0..min_len {
combined.push(main_timings[i] ^ contender_timings[i]);
combined.push(main_timings[i].wrapping_add(contender_timings[i]));
}
extract_timing_entropy(&combined, n_samples)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn info() {
let src = ICCAtomicContentionSource;
assert_eq!(src.info().name, "icc_atomic_contention");
assert!(matches!(src.info().category, SourceCategory::Microarch));
assert_eq!(src.info().platform, Platform::MacOS);
assert!(!src.info().composite);
}
#[test]
fn is_available() {
assert!(ICCAtomicContentionSource.is_available());
}
#[test]
#[ignore] fn collects_bytes() {
let data = ICCAtomicContentionSource.collect(32);
assert!(!data.is_empty());
let unique: std::collections::HashSet<u8> = data.iter().copied().collect();
assert!(unique.len() > 4);
}
}