use n0_future::time::{self, Duration, Instant};
use n0_watcher::Watchable;
pub(super) use os::Error;
use os::RouteMonitor;
#[cfg(not(wasm_browser))]
pub(crate) use os::is_interesting_interface;
use tokio::sync::mpsc;
use tracing::{debug, trace};
#[cfg(target_os = "android")]
use super::android as os;
#[cfg(any(
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "macos",
target_os = "ios"
))]
use super::bsd as os;
#[cfg(target_os = "linux")]
use super::linux as os;
#[cfg(wasm_browser)]
use super::wasm_browser as os;
#[cfg(target_os = "windows")]
use super::windows as os;
use crate::interfaces::State;
#[derive(Debug, Copy, Clone)]
pub(super) enum NetworkMessage {
#[allow(dead_code)]
Change,
}
#[cfg(not(any(target_os = "ios", target_os = "android")))]
const POLL_WALL_TIME_INTERVAL: Duration = Duration::from_secs(15);
#[cfg(any(target_os = "ios", target_os = "android"))]
const POLL_WALL_TIME_INTERVAL: Duration = Duration::from_secs(60 * 60);
const MON_CHAN_CAPACITY: usize = 16;
const ACTOR_CHAN_CAPACITY: usize = 16;
pub(super) struct Actor {
interface_state: Watchable<State>,
wall_time: Instant,
#[allow(dead_code)]
route_monitor: RouteMonitor,
mon_receiver: mpsc::Receiver<NetworkMessage>,
actor_receiver: mpsc::Receiver<ActorMessage>,
actor_sender: mpsc::Sender<ActorMessage>,
}
pub(super) enum ActorMessage {
NetworkChange,
}
impl Actor {
pub(super) async fn new() -> Result<Self, os::Error> {
let interface_state = State::new().await;
let wall_time = Instant::now();
let (mon_sender, mon_receiver) = mpsc::channel(MON_CHAN_CAPACITY);
let route_monitor = RouteMonitor::new(mon_sender)?;
let (actor_sender, actor_receiver) = mpsc::channel(ACTOR_CHAN_CAPACITY);
Ok(Actor {
interface_state: Watchable::new(interface_state),
wall_time,
route_monitor,
mon_receiver,
actor_receiver,
actor_sender,
})
}
pub(super) fn state(&self) -> &Watchable<State> {
&self.interface_state
}
pub(super) fn subscribe(&self) -> mpsc::Sender<ActorMessage> {
self.actor_sender.clone()
}
pub(super) async fn run(mut self) {
const DEBOUNCE: Duration = Duration::from_millis(250);
let mut pending_change = false;
let mut pending_time_jump = false;
let debounce = time::sleep(DEBOUNCE);
tokio::pin!(debounce);
let mut wall_time_interval = time::interval(POLL_WALL_TIME_INTERVAL);
loop {
tokio::select! {
_ = &mut debounce, if pending_change || pending_time_jump => {
self.handle_potential_change(pending_time_jump).await;
pending_change = false;
pending_time_jump = false;
}
_ = wall_time_interval.tick() => {
trace!("tick: wall_time_interval");
if self.check_wall_time_advance() {
pending_time_jump = true;
debounce.as_mut().reset(Instant::now() + DEBOUNCE);
}
}
event = self.mon_receiver.recv() => {
match event {
Some(NetworkMessage::Change) => {
trace!("network activity detected");
pending_change = true;
debounce.as_mut().reset(Instant::now() + DEBOUNCE);
}
None => {
debug!("shutting down, network monitor receiver gone");
break;
}
}
}
msg = self.actor_receiver.recv() => {
match msg {
Some(ActorMessage::NetworkChange) => {
trace!("external network activity detected");
pending_change = true;
debounce.as_mut().reset(Instant::now() + DEBOUNCE);
}
None => {
debug!("shutting down, actor receiver gone");
break;
}
}
}
}
}
}
async fn handle_potential_change(&mut self, time_jumped: bool) {
trace!("potential change");
let mut new_state = State::new().await;
let old_state = &self.interface_state.get();
if time_jumped {
new_state.last_unsuspend.replace(Instant::now());
} else if old_state == &new_state {
debug!("no changes detected");
return;
}
self.interface_state.set(new_state).ok();
}
fn check_wall_time_advance(&mut self) -> bool {
let now = Instant::now();
let jumped = if let Some(elapsed) = now.checked_duration_since(self.wall_time) {
elapsed > POLL_WALL_TIME_INTERVAL * 3 / 2
} else {
false
};
self.wall_time = now;
jumped
}
}