netscli_core/ops/network.rs
1use super::config::Ops;
2use crate::error::Result;
3use crate::{ArpEntry, InterfaceInfo, NetworkManager};
4
5impl Ops {
6 pub fn list_interfaces(&self) -> Vec<InterfaceInfo> {
7 NetworkManager::get_interfaces()
8 }
9
10 /// Discover services via mDNS/DNS-SD across a curated list of common
11 /// service types. Waits up to `timeout` for responses.
12 ///
13 /// Pass an empty `service_types` slice to use
14 /// [`crate::mdns::COMMON_SERVICE_TYPES`] as the default probe set.
15 #[cfg(feature = "mdns")]
16 pub async fn discover_mdns(
17 &self,
18 service_types: &[String],
19 timeout: std::time::Duration,
20 ) -> Result<Vec<crate::mdns::MdnsService>> {
21 // Clamp rather than reject: a browse is a "wait this long" request,
22 // not an addressing mistake, so trimming an over-long wait gives the
23 // caller results instead of an error. The engines are the authority
24 // on their own limits -- `Ops` used to pass this straight through,
25 // leaving the TUI as the only surface that bounded it.
26 let timeout = timeout.min(std::time::Duration::from_millis(crate::MAX_MDNS_TIMEOUT_MS));
27 if service_types.is_empty() {
28 crate::mdns::MdnsEngine::discover_common(timeout).await
29 } else {
30 let refs: Vec<&str> = service_types.iter().map(String::as_str).collect();
31 crate::mdns::MdnsEngine::discover(&refs, timeout).await
32 }
33 }
34
35 /// Read the local ARP/neighbour table.
36 ///
37 /// Async, and it moves the read to a blocking thread, because on Windows
38 /// and macOS this shells out to `arp` and waits on the child process.
39 /// Every other method on `Ops` is async, so a sync one here invited
40 /// exactly the bug it produced: three separate callers — the MCP tool
41 /// dispatcher, the Tauri command, and `DiscoverEngine` — all invoked it
42 /// straight from an async context and parked a runtime worker on
43 /// `waitpid` for the life of the subprocess.
44 ///
45 /// The MCP case was the sharp one. Its handlers are capped at 16
46 /// concurrent, and the read loop is itself a task on the same pool, so
47 /// sixteen `get_arp_table` calls could stall every worker — including
48 /// the one reading stdin. No further request would even be parsed, and
49 /// a cancellation could not get through.
50 pub async fn get_arp_table(&self) -> Result<Vec<ArpEntry>> {
51 tokio::task::spawn_blocking(NetworkManager::get_arp_table)
52 .await
53 .map_err(|e| crate::error::Error::Other(format!("ARP read task failed: {e}")))?
54 }
55}