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}