openentropy_core/sources/
process.rs1use std::time::Instant;
9
10use crate::source::{EntropySource, SourceCategory, SourceInfo};
11
12use super::helpers::run_command_raw;
13
14const JITTER_ROUNDS: usize = 256;
16
17pub struct ProcessSource {
18 info: SourceInfo,
19}
20
21impl ProcessSource {
22 pub fn new() -> Self {
23 Self {
24 info: SourceInfo {
25 name: "process_table",
26 description: "Process table snapshots combined with getpid() timing jitter",
27 physics: "Snapshots the process table (PIDs, CPU usage, memory) and extracts \
28 entropy from the constantly-changing state. New PIDs are allocated \
29 semi-randomly, CPU percentages fluctuate with scheduling decisions, and \
30 resident memory sizes shift with page reclamation.",
31 category: SourceCategory::System,
32 platform_requirements: &[],
33 entropy_rate_estimate: 400.0,
34 composite: false,
35 },
36 }
37 }
38}
39
40impl Default for ProcessSource {
41 fn default() -> Self {
42 Self::new()
43 }
44}
45
46fn collect_getpid_jitter(n_bytes: usize) -> Vec<u8> {
49 let rounds = JITTER_ROUNDS.max(n_bytes * 2);
50 let mut timings: Vec<u64> = Vec::with_capacity(rounds);
51
52 for _ in 0..rounds {
53 let start = Instant::now();
54 unsafe {
56 libc::getpid();
57 }
58 let elapsed = start.elapsed().as_nanos() as u64;
59 timings.push(elapsed);
60 }
61
62 let mut raw = Vec::with_capacity(n_bytes);
64 for pair in timings.windows(2) {
65 let delta = pair[1].wrapping_sub(pair[0]);
66 raw.push(delta as u8);
67 if raw.len() >= n_bytes {
68 break;
69 }
70 }
71 raw
72}
73
74fn snapshot_process_table() -> Option<Vec<u8>> {
76 run_command_raw("ps", &["-eo", "pid,pcpu,rss"])
77}
78
79impl EntropySource for ProcessSource {
80 fn info(&self) -> &SourceInfo {
81 &self.info
82 }
83
84 fn is_available(&self) -> bool {
85 super::helpers::command_exists("ps")
86 }
87
88 fn collect(&self, n_samples: usize) -> Vec<u8> {
89 let mut entropy = Vec::with_capacity(n_samples);
90
91 if let Some(stdout) = snapshot_process_table() {
93 for pair in stdout.chunks(2) {
95 if pair.len() == 2 {
96 entropy.push(pair[0] ^ pair[1]);
97 }
98 if entropy.len() >= n_samples {
99 break;
100 }
101 }
102 }
103
104 if entropy.len() < n_samples {
106 let jitter = collect_getpid_jitter(n_samples - entropy.len());
107 entropy.extend_from_slice(&jitter);
108 }
109
110 entropy.truncate(n_samples);
111 entropy
112 }
113}