use std::{
sync::Arc,
time::{Duration, Instant},
};
use iroh::{
Endpoint, EndpointAddr,
endpoint::Connection,
protocol::{AcceptError, ProtocolHandler},
};
use iroh_metrics::{Counter, MetricsGroup};
pub const ALPN: &[u8] = b"iroh/ping/0";
#[derive(Debug, Clone)]
pub struct Ping {
metrics: Arc<Metrics>,
}
impl Default for Ping {
fn default() -> Self {
Self::new()
}
}
impl Ping {
pub fn new() -> Self {
Self {
metrics: Arc::new(Metrics::default()),
}
}
pub fn metrics(&self) -> &Arc<Metrics> {
&self.metrics
}
pub async fn ping(&self, endpoint: &Endpoint, addr: EndpointAddr) -> anyhow::Result<Duration> {
let conn = endpoint.connect(addr, ALPN).await?;
let (mut send, mut recv) = conn.open_bi().await?;
let start = Instant::now();
send.write_all(b"PING").await?;
send.finish()?;
let response = recv.read_to_end(4).await?;
assert_eq!(&response, b"PONG");
let ping = start.elapsed();
self.metrics.pings_sent.inc();
conn.close(0u32.into(), b"bye!");
Ok(ping)
}
}
impl ProtocolHandler for Ping {
async fn accept(&self, connection: Connection) -> n0_error::Result<(), AcceptError> {
let metrics = self.metrics.clone();
let node_id = connection.remote_id();
println!("accepted connection from {node_id}");
let (mut send, mut recv) = connection.accept_bi().await?;
let req = recv.read_to_end(4).await.map_err(AcceptError::from_err)?;
assert_eq!(&req, b"PING");
metrics.pings_recv.inc();
send.write_all(b"PONG")
.await
.map_err(AcceptError::from_err)?;
send.finish()?;
connection.closed().await;
Ok(())
}
}
#[derive(Debug, Default, MetricsGroup)]
#[metrics(name = "ping")]
pub struct Metrics {
pub pings_sent: Counter,
pub pings_recv: Counter,
}
#[cfg(test)]
mod tests {
use anyhow::Result;
use iroh::{Endpoint, endpoint::presets, protocol::Router};
use super::*;
#[tokio::test]
async fn test_ping() -> Result<()> {
let server_endpoint = Endpoint::bind(presets::N0).await?;
let server_ping = Ping::new();
let server_metrics = server_ping.metrics().clone();
let server_router = Router::builder(server_endpoint)
.accept(ALPN, server_ping)
.spawn();
let server_addr = server_router.endpoint().addr();
let client_endpoint = Endpoint::bind(presets::N0).await?;
let client_ping = Ping::new();
let client_metrics = client_ping.metrics().clone();
let res = client_ping
.ping(&client_endpoint, server_addr.clone())
.await?;
println!("ping response: {res:?}");
assert_eq!(server_metrics.pings_recv.get(), 1);
assert_eq!(client_metrics.pings_sent.get(), 1);
let res = client_ping
.ping(&client_endpoint, server_addr.clone())
.await?;
println!("ping response: {res:?}");
assert_eq!(server_metrics.pings_recv.get(), 2);
assert_eq!(client_metrics.pings_sent.get(), 2);
client_endpoint.close().await;
server_router.shutdown().await?;
Ok(())
}
}