use std::io::Write;
use std::time::Instant;
use flate2::Compression;
use flate2::write::ZlibEncoder;
use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
use crate::sources::helpers::{extract_timing_entropy, mach_time};
static COMPRESSION_TIMING_INFO: SourceInfo = SourceInfo {
name: "compression_timing",
description: "Zlib compression timing jitter from data-dependent branch prediction",
physics: "Compresses varying data with zlib and measures per-operation timing. \
Compression algorithms have heavily data-dependent branches (Huffman tree \
traversal, LZ77 match finding). The CPU\u{2019}s branch predictor state from \
ALL running code affects prediction accuracy for these branches. Pipeline \
stalls from mispredictions create timing variation.",
category: SourceCategory::Signal,
platform: Platform::Any,
requirements: &[],
entropy_rate_estimate: 2.0,
composite: false,
is_fast: true,
};
pub struct CompressionTimingSource;
impl EntropySource for CompressionTimingSource {
fn info(&self) -> &SourceInfo {
&COMPRESSION_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;
for i in 0..raw_count {
let data_len = 128 + (lcg as usize % 385);
let mut data = vec![0u8; data_len];
let third = data_len / 3;
for byte in data[..third].iter_mut() {
lcg = lcg.wrapping_mul(6364136223846793005).wrapping_add(1);
*byte = (lcg >> 32) as u8;
}
for (j, byte) in data[third..third * 2].iter_mut().enumerate() {
*byte = (j % 4) as u8;
}
for byte in data[third * 2..].iter_mut() {
lcg = lcg.wrapping_mul(6364136223846793005).wrapping_add(i as u64);
*byte = (lcg >> 32) as u8;
}
let t0 = Instant::now();
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
let _ = encoder.write_all(&data);
let _ = encoder.finish();
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]
fn compression_timing_info() {
let src = CompressionTimingSource;
assert_eq!(src.name(), "compression_timing");
assert_eq!(src.info().category, SourceCategory::Signal);
assert!(!src.info().composite);
}
#[test]
#[ignore] fn compression_timing_collects_bytes() {
let src = CompressionTimingSource;
assert!(src.is_available());
let data = src.collect(64);
assert!(!data.is_empty());
}
}