1#[cfg(target_os = "android")]
16#[path = "network_watch_android.rs"]
17mod android;
18#[cfg(target_vendor = "apple")]
19#[path = "network_watch_apple.rs"]
20mod apple;
21#[cfg(any(test, target_os = "android"))]
22#[path = "network_watch_default.rs"]
23mod default_network;
24#[cfg(target_os = "linux")]
25#[path = "network_watch_linux.rs"]
26mod linux;
27#[cfg(windows)]
28#[path = "network_watch_windows.rs"]
29mod winrt;
30
31#[cfg(any(
32 test,
33 feature = "test-utils",
34 target_vendor = "apple",
35 target_os = "android",
36 target_os = "linux",
37 windows
38))]
39use crate::locks::lock_unpoisoned;
40use std::sync::{Arc, Mutex};
41use tokio::sync::watch;
42
43#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub struct NetworkState {
46 pub usable: Option<bool>,
49 pub metered: bool,
53 pub joined: u64,
57}
58
59impl NetworkState {
60 const UNKNOWN: Self = Self { usable: None, metered: false, joined: 0 };
62}
63
64#[cfg(any(
67 test,
68 feature = "test-utils",
69 target_vendor = "apple",
70 target_os = "android",
71 target_os = "linux",
72 windows
73))]
74#[derive(Clone, Debug, PartialEq, Eq)]
75pub(crate) struct UsableNetwork {
76 pub(crate) id: String,
79 pub(crate) metered: bool,
81}
82
83#[cfg(any(
86 test,
87 feature = "test-utils",
88 target_vendor = "apple",
89 target_os = "android",
90 target_os = "linux",
91 windows
92))]
93pub(crate) struct NetworkSink {
94 current: Option<String>,
96 state: watch::Sender<NetworkState>,
97}
98
99#[cfg(any(
100 test,
101 feature = "test-utils",
102 target_vendor = "apple",
103 target_os = "android",
104 target_os = "linux",
105 windows
106))]
107impl NetworkSink {
108 fn new() -> Self {
109 Self { current: None, state: watch::channel(NetworkState::UNKNOWN).0 }
110 }
111
112 pub(crate) fn observe(&mut self, network: Option<UsableNetwork>) {
114 let mut next = *self.state.borrow();
115 match network {
116 Some(network) => {
117 if self.current.as_ref() != Some(&network.id) {
118 next.joined += 1;
119 }
120 next.usable = Some(true);
121 next.metered = network.metered;
122 self.current = Some(network.id);
123 }
124 None => {
125 next.usable = Some(false);
126 next.metered = false;
127 self.current = None;
128 }
129 }
130 self.state.send_if_modified(|state| {
131 let changed = *state != next;
132 *state = next;
133 changed
134 });
135 }
136
137 pub(crate) fn cannot_tell(&mut self) {
144 self.state.send_if_modified(|state| {
145 let next = NetworkState { usable: None, metered: true, joined: state.joined };
146 let changed = *state != next;
147 *state = next;
148 changed
149 });
150 }
151
152 fn subscribe(&self) -> watch::Receiver<NetworkState> {
153 self.state.subscribe()
154 }
155
156 #[cfg(any(test, target_os = "android"))]
160 pub(crate) fn network_returned(&mut self) {
161 if self.current.is_some() {
162 self.state.send_modify(|state| state.joined += 1);
163 }
164 }
165}
166
167#[cfg(any(test, target_vendor = "apple", target_os = "android", windows))]
171pub(crate) fn heard(
172 sink: &Mutex<NetworkSink>,
173 bugs: &dyn crate::task::BugSink,
174 operation: &'static str,
175 read: impl FnOnce(),
176) {
177 if let Err(bug) = crate::task::caught_sync(operation, read) {
178 panicked(sink, bugs, &bug);
179 }
180}
181
182#[cfg(any(test, target_vendor = "apple", target_os = "android", target_os = "linux", windows))]
186pub(crate) fn panicked(
187 sink: &Mutex<NetworkSink>,
188 bugs: &dyn crate::task::BugSink,
189 bug: &crate::task::Bug,
190) {
191 lock_unpoisoned(sink).cannot_tell();
192 crate::task::report(bugs, bug);
193}
194
195#[derive(Clone)]
198pub enum NetworkSource {
199 Platform,
201 #[cfg(any(test, feature = "test-utils"))]
203 Test(TestNetwork),
204 #[cfg(any(test, feature = "test-utils"))]
206 Refused(NetworkWatchError),
207}
208
209#[derive(Clone, Debug, thiserror::Error)]
212pub enum NetworkWatchError {
213 #[error("the platform's path monitor could not be created")]
215 MonitorUnavailable,
216 #[error("the platform's path monitor ended before describing the network")]
218 MonitorEnded,
219 #[error("coven can't watch the device's network: `{call}` failed")]
221 Platform {
222 call: &'static str,
224 #[source]
226 source: std::sync::Arc<dyn std::error::Error + Send + Sync>,
227 },
228}
229
230#[cfg(any(target_os = "android", target_os = "linux", windows))]
231impl NetworkWatchError {
232 pub(crate) fn platform(
234 call: &'static str,
235 source: impl std::error::Error + Send + Sync + 'static,
236 ) -> Self {
237 Self::Platform { call, source: std::sync::Arc::new(source) }
238 }
239}
240
241pub struct NetworkWatch {
245 state: watch::Receiver<NetworkState>,
246 #[cfg(target_vendor = "apple")]
248 _platform: Option<apple::PathMonitor>,
249 #[cfg(target_os = "android")]
250 _platform: Option<android::JavaWatch>,
251 #[cfg(target_os = "linux")]
252 _platform: Option<linux::ManagerWatch>,
253 #[cfg(windows)]
254 _platform: Option<winrt::ProfileWatch>,
255}
256
257impl NetworkWatch {
258 pub fn start(
262 source: &NetworkSource,
263 #[cfg_attr(
264 not(any(
265 target_vendor = "apple",
266 target_os = "android",
267 target_os = "linux",
268 windows
269 )),
270 expect(unused_variables, reason = "where coven asks nothing, nothing calls back")
271 )]
272 bugs: &crate::task::BugSinkRef,
273 ) -> Result<Self, NetworkWatchError> {
274 match source {
275 #[cfg(any(
276 target_vendor = "apple",
277 target_os = "android",
278 target_os = "linux",
279 windows
280 ))]
281 NetworkSource::Platform => {
282 let sink = Arc::new(Mutex::new(NetworkSink::new()));
283 let state = lock_unpoisoned(&sink).subscribe();
284 #[cfg(target_vendor = "apple")]
285 let platform = Some(apple::PathMonitor::start(sink, bugs.clone())?);
286 #[cfg(target_os = "android")]
287 let platform = Some(android::JavaWatch::start(sink, bugs.clone())?);
288 #[cfg(windows)]
289 let platform = Some(winrt::ProfileWatch::start(sink, bugs.clone())?);
290 #[cfg(target_os = "linux")]
292 let platform = linux::ManagerWatch::start(sink, bugs.clone())?;
293 Ok(Self { state, _platform: platform })
294 }
295 #[cfg(not(any(
298 target_vendor = "apple",
299 target_os = "android",
300 target_os = "linux",
301 windows
302 )))]
303 NetworkSource::Platform => Ok(Self::unwatched()),
304 #[cfg(any(test, feature = "test-utils"))]
305 NetworkSource::Test(network) => Ok(Self::without_platform(network.subscribe())),
306 #[cfg(any(test, feature = "test-utils"))]
307 NetworkSource::Refused(error) => Err(error.clone()),
308 }
309 }
310
311 pub fn unwatched() -> Self {
314 Self::without_platform(watch::channel(NetworkState::UNKNOWN).1)
315 }
316
317 fn without_platform(state: watch::Receiver<NetworkState>) -> Self {
319 Self {
320 state,
321 #[cfg(any(
322 target_vendor = "apple",
323 target_os = "android",
324 target_os = "linux",
325 windows
326 ))]
327 _platform: None,
328 }
329 }
330
331 pub fn view(&self) -> NetworkView {
333 NetworkView(self.state.clone())
334 }
335}
336
337#[derive(Clone)]
339pub struct NetworkView(watch::Receiver<NetworkState>);
340
341impl NetworkView {
342 pub fn current(&self) -> NetworkState {
344 *self.0.borrow()
345 }
346
347 pub async fn changed(&mut self) {
350 if self.0.changed().await.is_err() {
351 std::future::pending::<()>().await;
352 }
353 }
354
355 pub fn read(&mut self) -> NetworkState {
357 *self.0.borrow_and_update()
358 }
359}
360
361#[cfg(any(test, feature = "test-utils"))]
364#[derive(Clone)]
365pub struct TestNetwork {
366 state: Arc<Mutex<TestNetworkState>>,
367}
368
369#[cfg(any(test, feature = "test-utils"))]
371struct TestNetworkState {
372 sink: NetworkSink,
373 named: u64,
374}
375
376#[cfg(any(test, feature = "test-utils"))]
377impl TestNetwork {
378 pub fn new(usable: bool, metered: bool) -> Self {
380 let network = Self {
381 state: Arc::new(Mutex::new(TestNetworkState { sink: NetworkSink::new(), named: 0 })),
382 };
383 if usable {
384 network.join(metered);
385 } else {
386 network.lose();
387 }
388 network
389 }
390
391 fn subscribe(&self) -> watch::Receiver<NetworkState> {
392 lock_unpoisoned(&self.state).sink.subscribe()
393 }
394
395 pub fn join(&self, metered: bool) {
397 let mut state = lock_unpoisoned(&self.state);
398 state.named += 1;
399 let id = format!("test network {}", state.named);
400 state.sink.observe(Some(UsableNetwork { id, metered }));
401 }
402
403 pub fn lose(&self) {
405 lock_unpoisoned(&self.state).sink.observe(None);
406 }
407
408 pub fn cannot_tell(&self) {
411 lock_unpoisoned(&self.state).sink.cannot_tell();
412 }
413
414 pub fn set_metered(&self, metered: bool) {
416 let mut state = lock_unpoisoned(&self.state);
417 let Some(id) = state.sink.current.clone() else {
418 panic!("a test network turns metered only while the device is on one");
419 };
420 state.sink.observe(Some(UsableNetwork { id, metered }));
421 }
422}
423
424#[cfg(test)]
425#[path = "network_watch_tests.rs"]
426mod tests;