use std::{
pin::Pin,
task::{Context, Poll},
time::Duration,
};
use futures::FutureExt;
use tokio::time::{Instant, Sleep};
use crate::{error::ErrorKind, header::PingPayload};
pub struct OutboundPings {
sleep: Pin<Box<Sleep>>,
interval: Duration,
state: State,
}
enum State {
WaitingToSend,
ReadyToSend,
Sent(PingPayload, Instant),
}
impl OutboundPings {
pub fn new(interval: Duration) -> Self {
Self {
sleep: Box::pin(tokio::time::sleep(Duration::ZERO)),
interval,
state: State::ReadyToSend,
}
}
pub fn poll_should_send(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), ErrorKind>> {
match &mut self.state {
State::WaitingToSend => {
std::task::ready!(self.sleep.poll_unpin(cx));
self.state = State::ReadyToSend;
Poll::Ready(Ok(()))
},
State::ReadyToSend => Poll::Ready(Ok(())),
State::Sent(payload, sent_at) => {
std::task::ready!(self.sleep.poll_unpin(cx));
Poll::Ready(Err(ErrorKind::PingTimeout {
payload: *payload,
elapsed: sent_at.elapsed(),
}))
},
}
}
pub fn try_collect(&mut self) -> Option<PingPayload> {
match &mut self.state {
State::ReadyToSend => {
let payload = PingPayload::random();
let now = Instant::now();
self.state = State::Sent(payload, now);
self.sleep.as_mut().reset(now + self.interval);
Some(payload)
},
State::WaitingToSend | State::Sent(..) => None,
}
}
pub fn received_response(&mut self, payload: PingPayload) -> Result<Duration, ErrorKind> {
match &mut self.state {
State::ReadyToSend | State::WaitingToSend => Err(ErrorKind::UnexpectedPing(payload)),
State::Sent(expected, sent_at) => {
if *expected != payload {
Err(ErrorKind::UnexpectedPing(payload))
} else {
let elapsed = sent_at.elapsed();
self.state = State::WaitingToSend;
Ok(elapsed)
}
},
}
}
}