Skip to main content

net_kit/
net.rs

1use std::sync::RwLock;
2
3use tokio::runtime::Handle;
4use vibe_ready::VibeLogListener;
5
6use crate::inner::inner_net::InnerNet;
7use crate::ip_stack::IpStack;
8use crate::net_error::NetError;
9use crate::network_status::NetworkStatus;
10
11pub type NetworkStatusListener = Box<dyn Fn(NetworkStatus) + Send + Sync + 'static>;
12
13/// Listener for the engine's internal diagnostic log records.
14///
15/// This is the log callback accepted by [`Net::set_log_listener`]; it is a
16/// re-export of the underlying engine listener type.
17pub type LogListener = VibeLogListener;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub struct NetworkStatusListenerHandle(u64);
21
22impl NetworkStatusListenerHandle {
23    pub(crate) fn from_raw(id: u64) -> Self {
24        Self(id)
25    }
26}
27
28/// The single public API facade exposed by `net-kit`.
29///
30/// `Net` itself is only responsible for lifecycle management and forwarding;
31/// all of the actual runtime and network-monitoring logic lives in the
32/// internal [`InnerNet`], ensuring isolation.
33pub struct Net {
34    inner: RwLock<Option<InnerNet>>,
35}
36
37impl Default for Net {
38    fn default() -> Self {
39        Self::new()
40    }
41}
42
43impl Net {
44    /// Create an instance only; does not start network monitoring.
45    pub fn new() -> Net {
46        Net {
47            inner: RwLock::new(None),
48        }
49    }
50
51    /// Start network monitoring. Redundant calls are ignored; `start` may be
52    /// called again after `shutdown`.
53    ///
54    /// Internally creates and owns a runtime engine owned by `net-kit`.
55    pub async fn start(&self) -> Result<(), NetError> {
56        self.start_inner(InnerNet::new).await
57    }
58
59    /// Start network monitoring. Redundant calls are ignored; `start` may be
60    /// called again after `shutdown`.
61    ///
62    /// Uses a Tokio runtime supplied by the developer. The caller is
63    /// responsible for keeping that runtime alive; `shutdown` does not close
64    /// it.
65    pub async fn start_with_tokio_rt(&self, runtime_handle: Handle) -> Result<(), NetError> {
66        self.start_inner(move || InnerNet::new_with_tokio_rt(runtime_handle))
67            .await
68    }
69
70    /// Install the internal engine, start monitoring, then wait for the initial
71    /// network state to become ready.
72    ///
73    /// `factory` is only invoked when a new engine actually needs to be
74    /// created — i.e. when not already started. This avoids needlessly creating
75    /// an engine (and its owned runtime) on a redundant `start` only to drop it
76    /// immediately; dropping an owned runtime inside an async context panics, so
77    /// that path must never be taken.
78    async fn start_inner<F>(&self, factory: F) -> Result<(), NetError>
79    where
80        F: FnOnce() -> Result<InnerNet, NetError>,
81    {
82        // Decide whether a new engine is needed while holding the write lock;
83        // only invoke `factory` when not started, so no surplus engine is ever
84        // created that would have to be dropped in an async context. Release the
85        // lock as soon as the initial-state receiver is obtained, to avoid
86        // holding it across an await.
87        let initial_state = {
88            let mut guard = self.inner.write().map_err(NetError::from_poison)?;
89            if guard.is_none() {
90                let candidate = factory()?;
91                *guard = Some(candidate);
92            }
93            // Whether freshly installed or already present, trigger/reuse the
94            // monitor on the current inner instance.
95            match guard.as_ref() {
96                Some(inner) => Some(inner.begin()?),
97                None => None,
98            }
99        };
100
101        if let Some(initial_state) = initial_state {
102            InnerNet::wait_for_initial_state(initial_state).await;
103        }
104
105        Ok(())
106    }
107
108    /// Stop network monitoring and destroy all resources created by `start`.
109    /// Redundant calls are ignored.
110    pub fn shutdown(&self) -> Result<(), NetError> {
111        let inner = self.inner.write().map_err(NetError::from_poison)?.take();
112        if let Some(inner) = inner {
113            inner.shutdown()?;
114        }
115        Ok(())
116    }
117
118    /// Query whether the network is currently available.
119    ///
120    /// Returns [`NetworkStatus::Unavailable`] when not started; returns
121    /// [`NetError::Lock`] if an internal lock is poisoned, leaving recovery to
122    /// the developer.
123    pub fn local_network_reachability(&self) -> Result<NetworkStatus, NetError> {
124        match self.inner.read().map_err(NetError::from_poison)?.as_ref() {
125            Some(inner) => inner.local_network_reachability(),
126            None => Ok(NetworkStatus::default()),
127        }
128    }
129
130    /// Query the IP-stack capability currently available to the host.
131    ///
132    /// Reflects which IP protocol versions have usable addresses / routes
133    /// (`have_v4` / `have_v6`), with identical semantics on every platform.
134    /// Returns [`IpStack::None`] when not started; returns [`NetError::Lock`] if
135    /// an internal lock is poisoned.
136    pub fn ip_stack(&self) -> Result<IpStack, NetError> {
137        match self.inner.read().map_err(NetError::from_poison)?.as_ref() {
138            Some(inner) => inner.ip_stack(),
139            None => Ok(IpStack::default()),
140        }
141    }
142
143    /// Query whether IPv4 is currently available.
144    ///
145    /// Convenience for [`Net::ip_stack`] followed by [`IpStack::has_ipv4`].
146    /// Returns `Ok(false)` when not started; returns [`NetError::Lock`] if an
147    /// internal lock is poisoned.
148    pub fn has_ipv4(&self) -> Result<bool, NetError> {
149        Ok(self.ip_stack()?.has_ipv4())
150    }
151
152    /// Query whether IPv6 is currently available.
153    ///
154    /// Convenience for [`Net::ip_stack`] followed by [`IpStack::has_ipv6`].
155    /// Returns `Ok(false)` when not started; returns [`NetError::Lock`] if an
156    /// internal lock is poisoned.
157    pub fn has_ipv6(&self) -> Result<bool, NetError> {
158        Ok(self.ip_stack()?.has_ipv6())
159    }
160
161    /// Register a network notification listener; multiple may be registered.
162    ///
163    /// Returns `Ok(None)` when not started; `Ok(Some(handle))` on success after
164    /// start; returns [`NetError::Lock`] if an internal lock is poisoned.
165    pub fn register(
166        &self,
167        listener: NetworkStatusListener,
168    ) -> Result<Option<NetworkStatusListenerHandle>, NetError> {
169        match self.inner.read().map_err(NetError::from_poison)?.as_ref() {
170            Some(inner) => inner.register(listener).map(Some),
171            None => Ok(None),
172        }
173    }
174
175    /// Unregister a network notification listener by handle.
176    ///
177    /// Returns `Ok(false)` when not started; `Ok(true)` when a listener was
178    /// found and removed; returns [`NetError::Lock`] if an internal lock is
179    /// poisoned.
180    pub fn unregister(&self, handle: NetworkStatusListenerHandle) -> Result<bool, NetError> {
181        match self.inner.read().map_err(NetError::from_poison)?.as_ref() {
182            Some(inner) => inner.unregister(handle),
183            None => Ok(false),
184        }
185    }
186
187    /// Clear all registered network listeners. Returns [`NetError::NotStarted`]
188    /// when not started.
189    pub fn clear_all_listener(&self) -> Result<(), NetError> {
190        match self.inner.read().map_err(NetError::from_poison)?.as_ref() {
191            Some(inner) => inner.clear_all_listener(),
192            None => Err(NetError::NotStarted),
193        }
194    }
195
196    /// Query whether network monitoring is currently started.
197    ///
198    /// Returns `true` while an engine is installed (i.e. after `start` and
199    /// before `shutdown`); returns `false` otherwise. A poisoned internal lock
200    /// is treated as not started, so this method never panics and never returns
201    /// an error.
202    pub fn is_started(&self) -> bool {
203        match self.inner.read() {
204            Ok(guard) => guard.is_some(),
205            Err(_) => false,
206        }
207    }
208
209    /// Query whether network monitoring is currently shut down (i.e. not
210    /// started).
211    ///
212    /// This is the logical inverse of [`Net::is_started`]: it returns `true`
213    /// before the first `start` and after every `shutdown`. A poisoned internal
214    /// lock is treated as not started, so this method never panics and never
215    /// returns an error.
216    pub fn is_shutdown(&self) -> bool {
217        !self.is_started()
218    }
219
220    /// Query the name of the network the host is currently connected to.
221    ///
222    /// Returns `Ok(None)` when not started; `Ok(Some(name))` when started and a
223    /// connected network name could be resolved; `Ok(None)` when started but no
224    /// name is available (or the platform is unsupported); returns
225    /// [`NetError::Lock`] if an internal lock is poisoned.
226    pub fn get_current_network_name(&self) -> Result<Option<String>, NetError> {
227        match self.inner.read().map_err(NetError::from_poison)?.as_ref() {
228            Some(inner) => inner.get_current_network_name(),
229            None => Ok(None),
230        }
231    }
232
233    /// Install (or clear, with `None`) a listener for the engine's internal
234    /// diagnostic log records.
235    ///
236    /// Returns [`NetError::NotStarted`] when not started; returns
237    /// [`NetError::Lock`] if an internal lock is poisoned.
238    pub fn set_log_listener(&self, listener: Option<LogListener>) -> Result<(), NetError> {
239        match self.inner.read().map_err(NetError::from_poison)?.as_ref() {
240            Some(inner) => {
241                inner.set_log_listener(listener);
242                Ok(())
243            }
244            None => Err(NetError::NotStarted),
245        }
246    }
247}