Skip to main content

cranpose_services/
network_status.rs

1//! Network status (connectivity + whether the connection is metered). Apps use
2//! it to defer or confirm large transfers on cellular/metered links.
3//!
4//! Each part is an observation or [`Unknown`](Connectivity::Unknown): with no
5//! platform monitor installed via [`set_platform_network_monitor`], or one
6//! that stopped, both parts are unknown. Android observes both through
7//! `ConnectivityManager`, iOS and macOS through Network.framework's path
8//! monitor, where Low Data Mode also counts as metered; web observes
9//! connectivity through `navigator.onLine` and cannot tell metering. Linux
10//! and Windows install no monitor. An app that defers large transfers on metered
11//! links starts one on its own only when the status says
12//! [`Unmetered`](Metering::Unmetered).
13
14use std::sync::{Arc, OnceLock};
15
16use crate::registry::{RecoveryGate, ServiceRegistry};
17
18/// Whether the device has a usable network path.
19#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
20pub enum Connectivity {
21    /// A path that can reach the internet.
22    Online,
23    /// No usable path.
24    Offline,
25    /// Nothing observes connectivity: no platform monitor, or one that
26    /// stopped. A request may still succeed.
27    #[default]
28    Unknown,
29}
30
31/// Whether the active network path costs the user by the byte.
32#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
33pub enum Metering {
34    /// Cellular, a hotspot, or a network the user marked as metered.
35    Metered,
36    /// Wi-Fi or Ethernet the user did not mark as metered.
37    Unmetered,
38    /// The platform cannot tell, nothing observes the network, or there is
39    /// no active path to meter.
40    #[default]
41    Unknown,
42}
43
44/// A snapshot of the current network state. The default knows nothing.
45#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
46pub struct NetworkStatus {
47    pub connectivity: Connectivity,
48    pub metering: Metering,
49}
50
51/// Reports the current network status.
52pub trait NetworkMonitor: Send + Sync {
53    fn status(&self) -> NetworkStatus;
54    fn is_alive(&self) -> bool;
55    fn reconnect(&self);
56}
57
58pub type NetworkMonitorRef = Arc<dyn NetworkMonitor>;
59
60struct DefaultNetworkMonitor;
61
62impl NetworkMonitor for DefaultNetworkMonitor {
63    fn status(&self) -> NetworkStatus {
64        NetworkStatus::default()
65    }
66
67    fn is_alive(&self) -> bool {
68        true
69    }
70
71    fn reconnect(&self) {}
72}
73
74static PLATFORM_NETWORK_MONITOR: ServiceRegistry<dyn NetworkMonitor> = ServiceRegistry::new();
75static DEFAULT_NETWORK_MONITOR: OnceLock<NetworkMonitorRef> = OnceLock::new();
76static NETWORK_MONITOR_HANDLE: OnceLock<NetworkMonitorRef> = OnceLock::new();
77static NETWORK_RECOVERY: RecoveryGate = RecoveryGate::new();
78
79struct PlatformNetworkMonitor;
80
81fn registered_network_monitor() -> NetworkMonitorRef {
82    PLATFORM_NETWORK_MONITOR
83        .get_or_warn("network monitor")
84        .unwrap_or_else(|| {
85            DEFAULT_NETWORK_MONITOR
86                .get_or_init(|| Arc::new(DefaultNetworkMonitor))
87                .clone()
88        })
89}
90
91fn active_network_monitor() -> NetworkMonitorRef {
92    let monitor = registered_network_monitor();
93    if monitor.is_alive() {
94        NETWORK_RECOVERY.succeeded();
95    } else if NETWORK_RECOVERY.try_start() {
96        monitor.reconnect();
97    }
98    monitor
99}
100
101impl NetworkMonitor for PlatformNetworkMonitor {
102    /// The monitor's observation, or nothing known while it stays stopped.
103    fn status(&self) -> NetworkStatus {
104        let monitor = active_network_monitor();
105        if monitor.is_alive() {
106            monitor.status()
107        } else {
108            NetworkStatus::default()
109        }
110    }
111
112    fn is_alive(&self) -> bool {
113        registered_network_monitor().is_alive()
114    }
115
116    fn reconnect(&self) {
117        registered_network_monitor().reconnect();
118    }
119}
120
121/// Installs a platform network monitor, replacing any previous one.
122pub fn set_platform_network_monitor(monitor: NetworkMonitorRef) {
123    PLATFORM_NETWORK_MONITOR.set(monitor);
124    NETWORK_RECOVERY.succeeded();
125}
126
127/// Removes any registered platform network monitor (tests and teardown).
128pub fn clear_platform_network_monitor() {
129    PLATFORM_NETWORK_MONITOR.clear();
130}
131
132/// The active network monitor: the platform one if installed, else a default
133/// that knows nothing.
134pub fn network_monitor() -> NetworkMonitorRef {
135    NETWORK_MONITOR_HANDLE
136        .get_or_init(|| Arc::new(PlatformNetworkMonitor))
137        .clone()
138}
139
140/// The current network status. A stopped platform monitor gets a reconnect
141/// attempt first.
142pub fn network_status() -> NetworkStatus {
143    network_monitor().status()
144}
145
146#[cfg(test)]
147#[path = "tests/network_status_tests.rs"]
148mod tests;