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,
};
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;
let buf_size = 8 * 1024 * 1024 + 4096;
let mut buf_vec: Vec<u8> = vec![0u8; buf_size];
let buf = buf_vec.as_mut_ptr();
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 {
for i in 0..N_TRAIN {
let offset = (i * STRIDE) % (buf_size - STRIDE);
unsafe { ptr::read_volatile(buf.add(offset)) };
}
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);
for i in 0..N_TRAIN {
let offset = ((i * 7919) % (buf_size / STRIDE)) * STRIDE;
unsafe { ptr::read_volatile(buf.add(offset)) };
}
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);
timings.push(learned_t);
timings.push(random_t);
unsafe { ptr::read_volatile(buf.add(s % buf_size)) };
}
drop(buf_vec);
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());
}
}