use std::time::Instant;
use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
use crate::sources::helpers::{run_command_raw, xor_fold_u64};
const JITTER_ROUNDS: usize = 256;
pub struct ProcessSource;
static PROCESS_INFO: SourceInfo = SourceInfo {
name: "process_table",
description: "Process table snapshots combined with getpid() timing jitter",
physics: "Snapshots the process table (PIDs, CPU usage, memory) and extracts \
entropy from the constantly-changing state. New PIDs are allocated \
semi-randomly, CPU percentages fluctuate with scheduling decisions, and \
resident memory sizes shift with page reclamation.",
category: SourceCategory::System,
platform: Platform::MacOS,
requirements: &[],
entropy_rate_estimate: 1.0,
composite: false,
is_fast: false,
};
impl ProcessSource {
pub fn new() -> Self {
Self
}
}
impl Default for ProcessSource {
fn default() -> Self {
Self::new()
}
}
fn collect_getpid_jitter(n_bytes: usize) -> Vec<u8> {
let rounds = JITTER_ROUNDS.max(n_bytes * 2);
let mut timings: Vec<u64> = Vec::with_capacity(rounds);
for _ in 0..rounds {
let start = Instant::now();
unsafe {
libc::getpid();
}
let elapsed = start.elapsed().as_nanos() as u64;
timings.push(elapsed);
}
let mut raw = Vec::with_capacity(n_bytes);
for pair in timings.windows(2) {
let delta = pair[1].wrapping_sub(pair[0]);
raw.push(delta as u8);
if raw.len() >= n_bytes {
break;
}
}
raw
}
fn snapshot_process_table() -> Option<Vec<u8>> {
run_command_raw("/bin/ps", &["-eo", "pid,pcpu,rss"])
}
impl EntropySource for ProcessSource {
fn info(&self) -> &SourceInfo {
&PROCESS_INFO
}
fn is_available(&self) -> bool {
crate::sources::helpers::command_exists("/bin/ps")
}
fn collect(&self, n_samples: usize) -> Vec<u8> {
let mut entropy = Vec::with_capacity(n_samples);
if let Some(stdout) = snapshot_process_table() {
for chunk in stdout.chunks(8) {
let mut val = 0u64;
for (i, &b) in chunk.iter().enumerate() {
val |= (b as u64) << (i * 8);
}
entropy.push(xor_fold_u64(val));
if entropy.len() >= n_samples {
break;
}
}
}
if entropy.len() < n_samples {
let jitter = collect_getpid_jitter(n_samples - entropy.len());
entropy.extend_from_slice(&jitter);
}
entropy.truncate(n_samples);
entropy
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn process_info() {
let src = ProcessSource::new();
assert_eq!(src.name(), "process_table");
assert_eq!(src.info().category, SourceCategory::System);
assert!(!src.info().composite);
}
#[test]
#[ignore] fn process_collects_bytes() {
let src = ProcessSource::new();
if src.is_available() {
let data = src.collect(64);
assert!(!data.is_empty());
assert!(data.len() <= 64);
}
}
}