use std::collections::HashMap;
use std::thread;
use std::time::Duration;
use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
use crate::sources::helpers::{extract_delta_bytes_i64, run_command};
const SYSCTL_PATH: &str = "/usr/sbin/sysctl";
const SNAPSHOT_DELAY: Duration = Duration::from_millis(100);
pub struct SysctlSource;
static SYSCTL_INFO: SourceInfo = SourceInfo {
name: "sysctl_deltas",
description: "Batch-reads ~1600 kernel counters via sysctl -a and extracts deltas from the ~40-60 that change within 200ms",
physics: "Batch-reads ~1600 kernel counters via sysctl and extracts deltas from \
the ~40-60 that change within 200ms. These counters track page faults, context \
switches, TCP segments, interrupts \u{2014} each driven by independent processes. \
The LSBs of their deltas reflect the unpredictable micro-timing of the entire \
operating system\u{2019}s activity.",
category: SourceCategory::System,
platform: Platform::MacOS,
requirements: &[],
entropy_rate_estimate: 3.0,
composite: false,
is_fast: false,
};
impl SysctlSource {
pub fn new() -> Self {
Self
}
}
impl Default for SysctlSource {
fn default() -> Self {
Self::new()
}
}
fn snapshot_sysctl() -> Option<HashMap<String, i64>> {
let stdout = run_command(SYSCTL_PATH, &["-a"])?;
let mut map = HashMap::new();
for line in stdout.lines() {
let (key, val_str) = if let Some(idx) = line.find(": ") {
(&line[..idx], line[idx + 2..].trim())
} else if let Some(idx) = line.find(" = ") {
(&line[..idx], line[idx + 3..].trim())
} else {
continue;
};
if let Ok(v) = val_str.parse::<i64>() {
map.insert(key.to_string(), v);
}
}
Some(map)
}
impl EntropySource for SysctlSource {
fn info(&self) -> &SourceInfo {
&SYSCTL_INFO
}
fn is_available(&self) -> bool {
std::path::Path::new(SYSCTL_PATH).exists()
}
fn collect(&self, n_samples: usize) -> Vec<u8> {
let snap1 = match snapshot_sysctl() {
Some(s) => s,
None => return Vec::new(),
};
thread::sleep(SNAPSHOT_DELAY);
let snap2 = match snapshot_sysctl() {
Some(s) => s,
None => return Vec::new(),
};
let mut deltas: Vec<i64> = Vec::new();
for (key, v2) in &snap2 {
if let Some(v1) = snap1.get(key) {
let delta = v2.wrapping_sub(*v1);
if delta != 0 {
deltas.push(delta);
}
}
}
extract_delta_bytes_i64(&deltas, n_samples)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sysctl_info() {
let src = SysctlSource::new();
assert_eq!(src.name(), "sysctl_deltas");
assert_eq!(src.info().category, SourceCategory::System);
assert!(!src.info().composite);
}
#[test]
#[cfg(target_os = "macos")]
#[ignore] fn sysctl_collects_bytes() {
let src = SysctlSource::new();
if src.is_available() {
let data = src.collect(64);
assert!(!data.is_empty());
assert!(data.len() <= 64);
}
}
}