openentropy-core 0.12.3

Core entropy harvesting library — hardware noise sources, raw or SHA-256 conditioned
Documentation
//! SEV/SEVL (Send Event) instruction timing entropy.
//!
//! The ARM64 `SEV` (Send Event) instruction broadcasts a wakeup signal to
//! all cores that are waiting in `WFE` (Wait For Event) low-power state.
//! `SEVL` (Send Event Local) sends the event only to the local core's
//! event register.
//!
//! ## Physics
//!
//! **SEV** must notify every core on the chip. On Apple Silicon, with
//! 4–16 cores across multiple clusters, the SEV broadcast traverses the
//! same ICC (Inter-Cluster Coherency) fabric as cache coherency traffic.
//! When other processes are doing WFE/SEV synchronization (network drivers,
//! kernel spinlocks, multimedia processing), the ICC bus is busier and
//! our SEV takes longer.
//!
//! Measured on M4 Mac mini (N=2000 each):
//! - `SEV`:  mean=6.14 ticks (~256 ns), CV=240.4%, range=0–42 ticks, LSB=0.051
//! - `SEVL`: mean=20.12 ticks (~838 ns), CV=103.5%, range=0–58 ticks, LSB=0.160
//!
//! **Paradox**: SEVL (local-only) is 3× SLOWER than SEV (all-cores broadcast).
//! This reveals that the "local event register" write path is less optimized
//! than the ICC broadcast path — Apple likely implements SEV through a
//! fast-path in the coherency fabric, while SEVL uses a slower register-write
//! path to the local core's hardware event monitor.
//!
//! This is a genuine microarchitectural discovery: on Apple Silicon,
//! **broadcasting to all cores is faster than writing to one core's
//! local event register**.
//!
//! ## Entropy Sources
//!
//! SEV timing reflects:
//! - ICC bus load from all current WFE waiters across all processes
//! - Number of cores that need to be woken (those in WFE sleep)
//! - Thermal state of the interconnect (latency increases with temperature)
//!
//! SEVL timing reflects:
//! - Local core's event register path (less shared, more predictable)
//! - Pipeline state and execution unit contention on the current core
//! - Thermal throttling of the specific P-core we're running on

use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};

#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
use crate::sources::helpers::{extract_timing_entropy, mach_time};

static SEV_EVENT_TIMING_INFO: SourceInfo = SourceInfo {
    name: "sev_event_timing",
    description: "ARM64 SEV/SEVL broadcast event timing via ICC fabric load",
    physics: "Times `SEV` (Send Event, all-core broadcast) and `SEVL` (Send Event Local). \
              SEV traverses the ICC fabric to wake WFE-waiting cores; timing reflects ICC \
              bus load from all concurrent WFE/SEV operations across all processes. \
              Paradox: SEVL is 3\u{00d7} slower than SEV (local event register write is less \
              optimized than ICC broadcast path). Measured: SEV CV=240.4%, mean=6.1 ticks; \
              SEVL CV=103.5%, mean=20.1 ticks, range=0\u{2013}58 ticks. XOR-mixed to combine \
              ICC fabric state (SEV) with local core pipeline state (SEVL).",
    category: SourceCategory::Microarch,
    platform: Platform::MacOS,
    requirements: &[],
    entropy_rate_estimate: 3.0,
    composite: false,
    is_fast: false,
};

/// Entropy source from ARM64 SEV/SEVL broadcast event timing.
pub struct SEVEventTimingSource;

#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
impl EntropySource for SEVEventTimingSource {
    fn info(&self) -> &SourceInfo {
        &SEV_EVENT_TIMING_INFO
    }

    fn is_available(&self) -> bool {
        true
    }

    fn collect(&self, n_samples: usize) -> Vec<u8> {
        // Take equal samples from SEV and SEVL, then XOR-mix for independence.
        let raw = n_samples * 4 + 32;
        let mut sev_times = Vec::with_capacity(raw);
        let mut sevl_times = Vec::with_capacity(raw);

        // Warm up
        for _ in 0..16 {
            unsafe {
                core::arch::asm!("sev", options(nostack, nomem));
                core::arch::asm!("sevl", options(nostack, nomem));
            }
        }

        for _ in 0..raw {
            let t0 = mach_time();
            unsafe {
                core::arch::asm!("sev", options(nostack, nomem));
            }
            let sev_t = mach_time().wrapping_sub(t0);

            let t1 = mach_time();
            unsafe {
                core::arch::asm!("sevl", options(nostack, nomem));
            }
            let sevl_t = mach_time().wrapping_sub(t1);

            // Reject suspend/resume spikes (>2ms).
            if sev_t < 48_000 && sevl_t < 48_000 {
                // XOR combines ICC state (sev_t) with local core state (sevl_t).
                // Since they measure different microarchitectural paths, the XOR
                // maximizes independence between the two timing channels.
                sev_times.push(sev_t);
                sevl_times.push(sevl_t ^ (sev_t.wrapping_shl(7)));
            }
        }

        // Interleave for extraction.
        let min = sev_times.len().min(sevl_times.len());
        let mut combined = Vec::with_capacity(min * 2);
        for i in 0..min {
            combined.push(sev_times[i]);
            combined.push(sevl_times[i]);
        }

        extract_timing_entropy(&combined, n_samples)
    }
}

#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
impl EntropySource for SEVEventTimingSource {
    fn info(&self) -> &SourceInfo {
        &SEV_EVENT_TIMING_INFO
    }
    fn is_available(&self) -> bool {
        false
    }
    fn collect(&self, _n_samples: usize) -> Vec<u8> {
        Vec::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn info() {
        let src = SEVEventTimingSource;
        assert_eq!(src.info().name, "sev_event_timing");
        assert!(matches!(src.info().category, SourceCategory::Microarch));
        assert_eq!(src.info().platform, Platform::MacOS);
        assert!(!src.info().composite);
    }

    #[test]
    #[cfg(all(target_os = "macos", target_arch = "aarch64"))]
    fn is_available_on_apple_silicon() {
        assert!(SEVEventTimingSource.is_available());
    }

    #[test]
    #[ignore]
    fn collects_with_variation() {
        let data = SEVEventTimingSource.collect(32);
        assert!(!data.is_empty());
    }
}