Skip to main content

nd_300/diagnostics/
shared_cache.rs

1use std::collections::HashMap;
2
3/// Pre-fetched data shared across technician-mode diagnostic modules.
4/// Built once before `tokio::join!` and passed by reference into each
5/// module's `collect_with_cache()` to avoid duplicate subprocess calls.
6pub struct SharedCache {
7    pub netstat: Option<NetstatCache>,
8    pub ipconfig: Option<IpconfigCache>,
9    pub sysinfo_networks: Option<sysinfo::Networks>,
10    pub gateway_ip: Option<String>,
11}
12
13/// Parsed `netstat -ano` output plus PID-to-process-name map.
14pub struct NetstatCache {
15    pub lines: Vec<String>,
16    pub process_map: HashMap<u32, String>,
17}
18
19/// `ipconfig /all` output stored as raw text.
20pub struct IpconfigCache {
21    pub raw: String,
22}
23
24impl SharedCache {
25    /// Run all pre-fetches concurrently. Each field is `None` on failure
26    /// so consumers can fall back to their own subprocess call.
27    pub async fn build_for_tech_mode() -> Self {
28        let (netstat, ipconfig, networks, gateway) = tokio::join!(
29            fetch_netstat(),
30            fetch_ipconfig(),
31            fetch_sysinfo_networks(),
32            fetch_gateway(),
33        );
34
35        Self {
36            netstat,
37            ipconfig,
38            sysinfo_networks: networks,
39            gateway_ip: gateway,
40        }
41    }
42}
43
44// ── Netstat ────────────────────────────────────────────────────────────────
45
46#[cfg(windows)]
47async fn fetch_netstat() -> Option<NetstatCache> {
48    use sysinfo::System;
49
50    let mut cmd = tokio::process::Command::new("netstat");
51    cmd.args(["-ano"]);
52    let output = super::util::run_with_timeout(cmd, super::util::QUICK).await?;
53
54    let text = String::from_utf8_lossy(&output.stdout);
55    let lines: Vec<String> = text.lines().map(|l| l.to_string()).collect();
56
57    // Build PID->process-name map from a single sysinfo refresh
58    let process_map = tokio::task::spawn_blocking(|| {
59        let mut sys = System::new();
60        sys.refresh_processes(sysinfo::ProcessesToUpdate::All, true);
61        let mut map = HashMap::new();
62        for (pid, process) in sys.processes() {
63            map.insert(pid.as_u32(), process.name().to_string_lossy().to_string());
64        }
65        map
66    })
67    .await
68    .unwrap_or_default();
69
70    Some(NetstatCache { lines, process_map })
71}
72
73#[cfg(target_os = "macos")]
74async fn fetch_netstat() -> Option<NetstatCache> {
75    let mut cmd = tokio::process::Command::new("netstat");
76    cmd.args(["-anp", "tcp"]);
77    let output = super::util::run_with_timeout(cmd, super::util::QUICK).await?;
78
79    let text = String::from_utf8_lossy(&output.stdout);
80    let lines: Vec<String> = text.lines().map(|l| l.to_string()).collect();
81
82    Some(NetstatCache {
83        lines,
84        process_map: HashMap::new(),
85    })
86}
87
88#[cfg(target_os = "linux")]
89async fn fetch_netstat() -> Option<NetstatCache> {
90    // Linux modules use `ss` and `netstat -an`, not `netstat -ano`.
91    // We still fetch `netstat -an` for connection_states.
92    let mut cmd = tokio::process::Command::new("netstat");
93    cmd.args(["-an"]);
94    let output = super::util::run_with_timeout(cmd, super::util::QUICK).await?;
95
96    let text = String::from_utf8_lossy(&output.stdout);
97    let lines: Vec<String> = text.lines().map(|l| l.to_string()).collect();
98
99    Some(NetstatCache {
100        lines,
101        process_map: HashMap::new(),
102    })
103}
104
105// ── Ipconfig ───────────────────────────────────────────────────────────────
106
107#[cfg(windows)]
108async fn fetch_ipconfig() -> Option<IpconfigCache> {
109    let mut cmd = tokio::process::Command::new("ipconfig");
110    cmd.args(["/all"]);
111    let output = super::util::run_with_timeout(cmd, super::util::QUICK).await?;
112
113    let raw = String::from_utf8_lossy(&output.stdout).to_string();
114    Some(IpconfigCache { raw })
115}
116
117#[cfg(not(windows))]
118async fn fetch_ipconfig() -> Option<IpconfigCache> {
119    // ipconfig /all is Windows-only; other platforms don't use it
120    None
121}
122
123// ── Sysinfo Networks ──────────────────────────────────────────────────────
124
125async fn fetch_sysinfo_networks() -> Option<sysinfo::Networks> {
126    Some(sysinfo::Networks::new_with_refreshed_list())
127}
128
129// ── Gateway IP ─────────────────────────────────────────────────────────────
130
131async fn fetch_gateway() -> Option<String> {
132    default_net::get_default_gateway()
133        .ok()
134        .map(|gw| gw.ip_addr.to_string())
135}