openentropy-core 0.12.3

Core entropy harvesting library — hardware noise sources, raw or SHA-256 conditioned
Documentation
//! Hardware prefetcher state timing entropy.
//!
//! Modern CPUs have hardware prefetchers that learn memory access patterns
//! and speculatively fetch data into cache before it's requested. The M4's
//! L1 and L2 prefetchers track stride patterns and sequential access streams.
//!
//! ## Physics
//!
//! When we access memory with a consistent stride (e.g., +64 bytes per access),
//! the prefetcher learns the pattern and begins prefetching ahead. Subsequent
//! accesses hit in L1 cache (fast). When we scramble the access pattern with
//! random strides, the prefetcher's learned state is invalidated, causing
//! cache misses (slow).
//!
//! Empirically on M4 Mac mini (N=500, 100 accesses per sample):
//! - **Learned-stride access**: mean=302.1 ticks, CV=24.1%, range=[250,792]
//! - **Random-stride access**: mean=679.9 ticks, CV=23.1%, range=[583,1667]
//! - **Speedup ratio**: 2.25×
//!
//! ## Why This Is Entropy
//!
//! The prefetcher state encodes:
//!
//! 1. **Recent access history** — what strides has this core seen recently?
//! 2. **Prefetch buffer occupancy** — how many prefetches are in flight?
//! 3. **Training confidence** — how strongly has the prefetcher locked onto a pattern?
//! 4. **Contention from other processes** — other threads' access patterns
//!    interfere with our training
//!
//! The 2.25× timing difference between learned and random access provides
//! a high-SNR measurement of the prefetcher's internal state, which is
//! influenced by cross-process memory access patterns.

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

#[cfg(target_os = "macos")]
use crate::sources::helpers::extract_timing_entropy;

static PREFETCHER_STATE_INFO: SourceInfo = SourceInfo {
    name: "prefetcher_state",
    description: "Hardware prefetcher stride-learning state — 2.25× learned vs random speedup",
    physics: "Trains the L1/L2 hardware prefetcher with consistent stride accesses, then \
              measures learned-stride vs random-stride timing. Learned: mean=302.1 ticks, \
              CV=24.1%. Random: mean=679.9 ticks, CV=23.1%. Speedup=2.25×. The prefetcher \
              state encodes: recent access history, prefetch buffer occupancy, training \
              confidence, and cross-process memory contention. Other threads' access \
              patterns interfere with our training, creating a cross-process covert channel.",
    category: SourceCategory::Microarch,
    platform: Platform::MacOS,
    requirements: &[],
    entropy_rate_estimate: 2.0,
    composite: false,
    is_fast: false,
};

/// Entropy source from hardware prefetcher state timing.
pub struct PrefetcherStateSource;

#[cfg(target_os = "macos")]
impl EntropySource for PrefetcherStateSource {
    fn info(&self) -> &SourceInfo {
        &PREFETCHER_STATE_INFO
    }

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

    fn collect(&self, n_samples: usize) -> Vec<u8> {
        use std::ptr;

        const STRIDE: usize = 64;
        const N_ACCESSES: usize = 500;
        const N_TRAIN: usize = 1000;

        // 8 MB buffer exceeds L2 cache.
        // Use Vec for RAII — automatically freed on drop, including panic unwind.
        let buf_size = 8 * 1024 * 1024 + 4096;
        let mut buf_vec: Vec<u8> = vec![0u8; buf_size];
        let buf = buf_vec.as_mut_ptr();

        // Touch all pages
        for i in (0..buf_size).step_by(4096) {
            unsafe { ptr::write_volatile(buf.add(i), i as u8) };
        }

        let raw = n_samples * 2 + 32;
        let mut timings = Vec::with_capacity(raw * 2);

        for s in 0..raw {
            // Phase 1: Train prefetcher with consistent stride
            for i in 0..N_TRAIN {
                let offset = (i * STRIDE) % (buf_size - STRIDE);
                unsafe { ptr::read_volatile(buf.add(offset)) };
            }

            // Measure learned-stride access (fast if prefetcher learned)
            let t0 = super::super::helpers::mach_time();
            for i in 0..N_ACCESSES {
                let offset = (i * STRIDE) % (buf_size - STRIDE);
                unsafe { ptr::read_volatile(buf.add(offset)) };
            }
            let learned_t = super::super::helpers::mach_time().wrapping_sub(t0);

            // Phase 2: Scramble prefetcher with random strides
            for i in 0..N_TRAIN {
                let offset = ((i * 7919) % (buf_size / STRIDE)) * STRIDE;
                unsafe { ptr::read_volatile(buf.add(offset)) };
            }

            // Measure random-stride access (slow if prefetcher confused)
            let t1 = super::super::helpers::mach_time();
            for i in 0..N_ACCESSES {
                let offset = ((i * 7919) % (buf_size / STRIDE)) * STRIDE;
                unsafe { ptr::read_volatile(buf.add(offset)) };
            }
            let random_t = super::super::helpers::mach_time().wrapping_sub(t1);

            // Encode both measurements
            timings.push(learned_t);
            timings.push(random_t);

            // Prevent compiler from optimizing away
            unsafe { ptr::read_volatile(buf.add(s % buf_size)) };
        }

        drop(buf_vec);

        // XOR learned and random timings to capture prefetcher state
        let combined: Vec<u64> = timings
            .chunks(2)
            .map(|c| c[0] ^ c[1].wrapping_shl(5))
            .collect();

        extract_timing_entropy(&combined, n_samples)
    }
}

#[cfg(not(target_os = "macos"))]
impl EntropySource for PrefetcherStateSource {
    fn info(&self) -> &SourceInfo {
        &PREFETCHER_STATE_INFO
    }
    fn is_available(&self) -> bool {
        false
    }
    fn collect(&self, _: usize) -> Vec<u8> {
        Vec::new()
    }
}

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

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

    #[test]
    #[cfg(target_os = "macos")]
    fn is_available() {
        assert!(PrefetcherStateSource.is_available());
    }

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