openentropy_core/sources/
sysctl.rs1use std::collections::HashMap;
6use std::thread;
7use std::time::Duration;
8
9use crate::source::{EntropySource, SourceCategory, SourceInfo};
10
11use super::helpers::{extract_delta_bytes_i64, run_command};
12
13const SYSCTL_PATH: &str = "/usr/sbin/sysctl";
15
16const SNAPSHOT_DELAY: Duration = Duration::from_millis(100);
18
19pub struct SysctlSource {
20 info: SourceInfo,
21}
22
23impl SysctlSource {
24 pub fn new() -> Self {
25 Self {
26 info: SourceInfo {
27 name: "sysctl_deltas",
28 description: "Batch-reads ~1600 kernel counters via sysctl -a and extracts deltas from the ~40-60 that change within 200ms",
29 physics: "Batch-reads ~1600 kernel counters via sysctl and extracts deltas from \
30 the ~40-60 that change within 200ms. These counters track page faults, context \
31 switches, TCP segments, interrupts \u{2014} each driven by independent processes. \
32 The LSBs of their deltas reflect the unpredictable micro-timing of the entire \
33 operating system\u{2019}s activity.",
34 category: SourceCategory::System,
35 platform_requirements: &["macos"],
36 entropy_rate_estimate: 5000.0,
37 composite: false,
38 },
39 }
40 }
41}
42
43impl Default for SysctlSource {
44 fn default() -> Self {
45 Self::new()
46 }
47}
48
49fn snapshot_sysctl() -> Option<HashMap<String, i64>> {
53 let stdout = run_command(SYSCTL_PATH, &["-a"])?;
54 let mut map = HashMap::new();
55
56 for line in stdout.lines() {
57 let (key, val_str) = if let Some(idx) = line.find(": ") {
59 (&line[..idx], line[idx + 2..].trim())
60 } else if let Some(idx) = line.find(" = ") {
61 (&line[..idx], line[idx + 3..].trim())
62 } else {
63 continue;
64 };
65
66 if let Ok(v) = val_str.parse::<i64>() {
68 map.insert(key.to_string(), v);
69 }
70 }
71
72 Some(map)
73}
74
75impl EntropySource for SysctlSource {
76 fn info(&self) -> &SourceInfo {
77 &self.info
78 }
79
80 fn is_available(&self) -> bool {
81 std::path::Path::new(SYSCTL_PATH).exists()
82 }
83
84 fn collect(&self, n_samples: usize) -> Vec<u8> {
85 let snap1 = match snapshot_sysctl() {
87 Some(s) => s,
88 None => return Vec::new(),
89 };
90
91 thread::sleep(SNAPSHOT_DELAY);
92
93 let snap2 = match snapshot_sysctl() {
94 Some(s) => s,
95 None => return Vec::new(),
96 };
97
98 let mut deltas: Vec<i64> = Vec::new();
100 for (key, v2) in &snap2 {
101 if let Some(v1) = snap1.get(key) {
102 let delta = v2.wrapping_sub(*v1);
103 if delta != 0 {
104 deltas.push(delta);
105 }
106 }
107 }
108
109 extract_delta_bytes_i64(&deltas, n_samples)
110 }
111}