use crate::protocol;
use futures::prelude::*;
use futures::future::BoxFuture;
use libp2p_swarm::{
KeepAlive,
NegotiatedSubstream,
SubstreamProtocol,
ProtocolsHandler,
ProtocolsHandlerUpgrErr,
ProtocolsHandlerEvent
};
use std::{
error::Error,
io,
fmt,
num::NonZeroU32,
task::{Context, Poll},
time::Duration
};
use std::collections::VecDeque;
use wasm_timer::Delay;
use void::Void;
#[derive(Clone, Debug)]
pub struct PingConfig {
timeout: Duration,
interval: Duration,
max_failures: NonZeroU32,
keep_alive: bool,
}
impl PingConfig {
pub fn new() -> Self {
Self {
timeout: Duration::from_secs(20),
interval: Duration::from_secs(15),
max_failures: NonZeroU32::new(1).expect("1 != 0"),
keep_alive: false
}
}
pub fn with_timeout(mut self, d: Duration) -> Self {
self.timeout = d;
self
}
pub fn with_interval(mut self, d: Duration) -> Self {
self.interval = d;
self
}
pub fn with_max_failures(mut self, n: NonZeroU32) -> Self {
self.max_failures = n;
self
}
pub fn with_keep_alive(mut self, b: bool) -> Self {
self.keep_alive = b;
self
}
}
pub type PingResult = Result<PingSuccess, PingFailure>;
#[derive(Debug)]
pub enum PingSuccess {
Pong,
Ping { rtt: Duration },
}
#[derive(Debug)]
pub enum PingFailure {
Timeout,
Other { error: Box<dyn std::error::Error + Send + 'static> }
}
impl fmt::Display for PingFailure {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PingFailure::Timeout => f.write_str("Ping timeout"),
PingFailure::Other { error } => write!(f, "Ping error: {}", error)
}
}
}
impl Error for PingFailure {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
PingFailure::Timeout => None,
PingFailure::Other { error } => Some(&**error)
}
}
}
pub struct PingHandler {
config: PingConfig,
timer: Delay,
pending_errors: VecDeque<PingFailure>,
failures: u32,
outbound: Option<PingState>,
inbound: Option<PongFuture>,
}
impl PingHandler {
pub fn new(config: PingConfig) -> Self {
PingHandler {
config,
timer: Delay::new(Duration::new(0, 0)),
pending_errors: VecDeque::with_capacity(2),
failures: 0,
outbound: None,
inbound: None,
}
}
}
impl ProtocolsHandler for PingHandler {
type InEvent = Void;
type OutEvent = PingResult;
type Error = PingFailure;
type InboundProtocol = protocol::Ping;
type OutboundProtocol = protocol::Ping;
type OutboundOpenInfo = ();
type InboundOpenInfo = ();
fn listen_protocol(&self) -> SubstreamProtocol<protocol::Ping, ()> {
SubstreamProtocol::new(protocol::Ping, ())
}
fn inject_fully_negotiated_inbound(&mut self, stream: NegotiatedSubstream, (): ()) {
self.inbound = Some(protocol::recv_ping(stream).boxed());
}
fn inject_fully_negotiated_outbound(&mut self, stream: NegotiatedSubstream, (): ()) {
self.timer.reset(self.config.timeout);
self.outbound = Some(PingState::Ping(protocol::send_ping(stream).boxed()));
}
fn inject_event(&mut self, _: Void) {}
fn inject_dial_upgrade_error(&mut self, _info: (), error: ProtocolsHandlerUpgrErr<Void>) {
self.outbound = None; self.pending_errors.push_front(
match error {
ProtocolsHandlerUpgrErr::Timeout => PingFailure::Timeout,
e => PingFailure::Other { error: Box::new(e) },
})
}
fn connection_keep_alive(&self) -> KeepAlive {
if self.config.keep_alive {
KeepAlive::Yes
} else {
KeepAlive::No
}
}
fn poll(&mut self, cx: &mut Context<'_>) -> Poll<ProtocolsHandlerEvent<protocol::Ping, (), PingResult, Self::Error>> {
if let Some(fut) = self.inbound.as_mut() {
match fut.poll_unpin(cx) {
Poll::Pending => {},
Poll::Ready(Err(e)) => {
log::debug!("Inbound ping error: {:?}", e);
self.inbound = None;
}
Poll::Ready(Ok(stream)) => {
self.inbound = Some(protocol::recv_ping(stream).boxed());
return Poll::Ready(ProtocolsHandlerEvent::Custom(Ok(PingSuccess::Pong)))
}
}
}
loop {
if let Some(error) = self.pending_errors.pop_back() {
log::debug!("Ping failure: {:?}", error);
self.failures += 1;
if self.failures > 1 || self.config.max_failures.get() > 1 {
if self.failures >= self.config.max_failures.get() {
log::debug!("Too many failures ({}). Closing connection.", self.failures);
return Poll::Ready(ProtocolsHandlerEvent::Close(error))
}
return Poll::Ready(ProtocolsHandlerEvent::Custom(Err(error)))
}
}
match self.outbound.take() {
Some(PingState::Ping(mut ping)) => match ping.poll_unpin(cx) {
Poll::Pending => {
if self.timer.poll_unpin(cx).is_ready() {
self.pending_errors.push_front(PingFailure::Timeout);
} else {
self.outbound = Some(PingState::Ping(ping));
break
}
},
Poll::Ready(Ok((stream, rtt))) => {
self.failures = 0;
self.timer.reset(self.config.interval);
self.outbound = Some(PingState::Idle(stream));
return Poll::Ready(
ProtocolsHandlerEvent::Custom(
Ok(PingSuccess::Ping { rtt })))
}
Poll::Ready(Err(e)) => {
self.pending_errors.push_front(PingFailure::Other {
error: Box::new(e)
});
}
},
Some(PingState::Idle(stream)) => match self.timer.poll_unpin(cx) {
Poll::Pending => {
self.outbound = Some(PingState::Idle(stream));
break
},
Poll::Ready(Ok(())) => {
self.timer.reset(self.config.timeout);
self.outbound = Some(PingState::Ping(protocol::send_ping(stream).boxed()));
},
Poll::Ready(Err(e)) => {
return Poll::Ready(ProtocolsHandlerEvent::Close(
PingFailure::Other {
error: Box::new(e)
}))
}
}
Some(PingState::OpenStream) => {
self.outbound = Some(PingState::OpenStream);
break
}
None => {
self.outbound = Some(PingState::OpenStream);
let protocol = SubstreamProtocol::new(protocol::Ping, ())
.with_timeout(self.config.timeout);
return Poll::Ready(ProtocolsHandlerEvent::OutboundSubstreamRequest {
protocol
})
}
}
}
Poll::Pending
}
}
type PingFuture = BoxFuture<'static, Result<(NegotiatedSubstream, Duration), io::Error>>;
type PongFuture = BoxFuture<'static, Result<NegotiatedSubstream, io::Error>>;
enum PingState {
OpenStream,
Idle(NegotiatedSubstream),
Ping(PingFuture),
}