use std::io::{Read, Seek, SeekFrom, Write};
use std::time::Instant;
use tempfile::NamedTempFile;
use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
use crate::sources::helpers::extract_timing_entropy;
const TEMP_FILE_SIZE: usize = 64 * 1024;
const READ_BLOCK_SIZE: usize = 4 * 1024;
static DISK_IO_INFO: SourceInfo = SourceInfo {
name: "disk_io",
description: "NVMe/SSD read latency jitter from random 4KB reads",
physics: "Measures NVMe/SSD read latency for small random reads. Jitter sources: \
flash translation layer (FTL) remapping, wear leveling, garbage collection, \
read disturb mitigation, NAND page read latency variation (depends on charge \
level in floating-gate transistors), and NVMe controller queue arbitration.",
category: SourceCategory::IO,
platform: Platform::Any,
requirements: &[],
entropy_rate_estimate: 1.5,
composite: false,
is_fast: false,
};
pub struct DiskIOSource;
impl EntropySource for DiskIOSource {
fn info(&self) -> &SourceInfo {
&DISK_IO_INFO
}
fn is_available(&self) -> bool {
true
}
fn collect(&self, n_samples: usize) -> Vec<u8> {
let mut tmpfile = match NamedTempFile::new() {
Ok(f) => f,
Err(_) => return Vec::new(),
};
let mut fill_data = vec![0u8; TEMP_FILE_SIZE];
let mut lcg: u64 = 0xCAFE_BABE_DEAD_BEEF;
for chunk in fill_data.chunks_mut(8) {
lcg = lcg.wrapping_mul(6364136223846793005).wrapping_add(1);
let bytes = lcg.to_le_bytes();
for (i, b) in chunk.iter_mut().enumerate() {
*b = bytes[i % 8];
}
}
if tmpfile.write_all(&fill_data).is_err() {
return Vec::new();
}
if tmpfile.flush().is_err() {
return Vec::new();
}
let mut read_buf = vec![0u8; READ_BLOCK_SIZE];
let max_offset = TEMP_FILE_SIZE.saturating_sub(READ_BLOCK_SIZE);
let seed = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64;
let mut lcg_state = if seed == 0 {
0xDEAD_BEEF_CAFE_1235u64
} else {
seed | 1
};
let num_reads = n_samples * 4 + 64;
let mut timings = Vec::with_capacity(num_reads);
for _ in 0..num_reads {
lcg_state = lcg_state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
let offset = (lcg_state as usize) % (max_offset + 1);
let t0 = Instant::now();
let _ = tmpfile.seek(SeekFrom::Start(offset as u64));
let _ = tmpfile.read(&mut read_buf);
let elapsed_ns = t0.elapsed().as_nanos() as u64;
timings.push(elapsed_ns);
}
extract_timing_entropy(&timings, n_samples)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[ignore] fn disk_io_collects_bytes() {
let src = DiskIOSource;
assert!(src.is_available());
let data = src.collect(128);
assert!(!data.is_empty());
}
#[test]
fn disk_io_info() {
let src = DiskIOSource;
assert_eq!(src.name(), "disk_io");
assert_eq!(src.info().category, SourceCategory::IO);
}
}