use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
use crate::sources::helpers::{extract_timing_entropy, mach_time};
const OPS_PER_SAMPLE: usize = 100;
static DENORMAL_TIMING_INFO: SourceInfo = SourceInfo {
name: "denormal_timing",
description: "Floating-point denormal multiply-accumulate timing jitter",
physics: "Times blocks of floating-point operations on denormalized values \
(magnitudes between 0 and f64::MIN_POSITIVE). Denormals may trigger \
microcode assists on some architectures, creating data-dependent timing. \
Even on Apple Silicon where denormal handling is fast in hardware, \
residual timing jitter comes from FPU pipeline state, cache line \
alignment, and memory controller arbitration.",
category: SourceCategory::Microarch,
platform: Platform::Any,
requirements: &[],
entropy_rate_estimate: 0.5,
composite: false,
is_fast: false,
};
pub struct DenormalTimingSource;
impl EntropySource for DenormalTimingSource {
fn info(&self) -> &SourceInfo {
&DENORMAL_TIMING_INFO
}
fn is_available(&self) -> bool {
true
}
fn collect(&self, n_samples: usize) -> Vec<u8> {
let raw_count = n_samples * 4 + 64;
let mut timings: Vec<u64> = Vec::with_capacity(raw_count);
let mut lcg: u64 = mach_time() | 1;
let mut denormals = [0.0f64; OPS_PER_SAMPLE];
for d in denormals.iter_mut() {
lcg = lcg.wrapping_mul(6364136223846793005).wrapping_add(1);
let bits = lcg & 0x000F_FFFF_FFFF_FFFF_u64;
*d = f64::from_bits(bits);
}
for _ in 0..raw_count {
lcg = lcg.wrapping_mul(6364136223846793005).wrapping_add(1);
let start_idx = (lcg >> 32) as usize % OPS_PER_SAMPLE;
let mut acc = denormals[start_idx];
let t0 = mach_time();
for i in 0..OPS_PER_SAMPLE {
let idx = (start_idx + i) % OPS_PER_SAMPLE;
acc *= denormals[idx];
acc += denormals[(idx + 1) % OPS_PER_SAMPLE];
}
let t1 = mach_time();
std::hint::black_box(acc);
timings.push(t1.wrapping_sub(t0));
}
extract_timing_entropy(&timings, n_samples)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn info() {
let src = DenormalTimingSource;
assert_eq!(src.name(), "denormal_timing");
assert_eq!(src.info().category, SourceCategory::Microarch);
assert!(!src.info().composite);
}
#[test]
#[ignore] fn collects_bytes() {
let src = DenormalTimingSource;
assert!(src.is_available());
let data = src.collect(64);
assert!(!data.is_empty());
assert!(data.len() <= 64);
}
}