use std::time::{
Duration,
SystemTime
};
use crate::message::PingOrPong;
use crate::util::*;
use super::{
Address,
status::{
elapsed,
remaining
},
data::{
reping_delay,
TIME_TO_PING,
TIME_TO_DOUBLE_PING,
PINGS_UNTIL_SUSPICIOUS,
PINGS_UNTIL_UNREACHABLE
}
};
use serde::{Serialize, Deserialize};
use LinkState::*;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Link {
pub address: Address,
pub state: LinkState,
pub seen: Option<SystemTime>,
pub pinged: Option<SystemTime>,
pub latency: Option<Duration>
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum LinkState {
HasPinged,
HasPonged,
BeenPinged(u8),
Suspicious(u8)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LinkTickResult {
Ok(Duration),
PingDue(Duration),
LinkSuspicious(Duration),
LinkUnreachable
}
impl LinkState {
pub fn been_pinged(&mut self) {
match self {
HasPinged |
HasPonged => *self = LinkState::BeenPinged(1),
BeenPinged(unpongeds) => *unpongeds += 1,
Suspicious(unpongeds) => *unpongeds += 1
}
}
}
impl Link {
pub fn new(address: Address) -> Self {
Self {
address,
state: LinkState::BeenPinged(0),
seen: None,
pinged: None,
latency: None
}
}
#[tracing::instrument]
#[must_use = "assumes due ping will be sent"]
pub fn tick(&mut self) -> LinkTickResult {
use LinkTickResult as Ltr;
match self.state {
HasPinged => match remaining(self.seen, TIME_TO_PING) {
Some(t) => Ltr::Ok(t),
None => {
self.pinged.set_now();
self.state = BeenPinged(1);
Ltr::PingDue(reping_delay(0))
}
},
HasPonged => match remaining(self.seen, TIME_TO_DOUBLE_PING) {
Some(t) => Ltr::Ok(t),
None => {
self.pinged.set_now();
self.state = BeenPinged(1);
Ltr::PingDue(reping_delay(0))
}
},
BeenPinged(upp) => match remaining(self.pinged, reping_delay(upp)) {
Some(t) => Ltr::Ok(t),
None if upp >= PINGS_UNTIL_SUSPICIOUS => {
self.pinged.set_now();
self.state = Suspicious(upp + 1);
Ltr::LinkSuspicious(reping_delay(upp + 1))
},
None => {
self.pinged.set_now();
self.state = BeenPinged(upp + 1);
Ltr::PingDue(reping_delay(upp + 1))
}
},
Suspicious(upp) => match remaining(self.pinged, reping_delay(upp)) {
Some(t) => Ltr::Ok(t),
None if upp >= PINGS_UNTIL_UNREACHABLE => Ltr::LinkUnreachable,
None => {
self.pinged.set_now();
self.state = Suspicious(upp + 1);
Ltr::PingDue(reping_delay(upp + 1))
}
}
}
}
pub fn been_pinged(&mut self) {
self.pinged.set_now();
self.state.been_pinged();
}
pub fn has_pnged(&mut self, png: PingOrPong) {
self.seen.set_now();
match png {
PingOrPong::Ping => {
self.state = LinkState::HasPinged;
},
PingOrPong::Pong => {
self.state = LinkState::HasPonged;
self.latency = elapsed(self.pinged);
}
}
}
}
impl std::fmt::Display for Link {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.address)?;
if self.state != BeenPinged(0) {
write!(f, " ({:?})", self.state)?;
}
if let Some(seen) = self.seen {
write!(f, ", seen ")?;
fmt_time(f, seen)?;
}
if let Some(pinged) = self.pinged {
write!(f, ", pinged ")?;
fmt_time(f, pinged)?;
}
if let Some(latency) = self.latency {
write!(f, ", latency {latency:.2?}")?;
}
Ok(())
}
}