/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2024 Dyne.org foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
use std::{
sync::Arc,
time::{Duration, Instant},
};
use async_trait::async_trait;
use log::{debug, error, warn};
use rand::{rngs::OsRng, Rng};
use smol::Executor;
use super::{
super::{
channel::ChannelPtr,
message::{PingMessage, PongMessage},
message_subscriber::MessageSubscription,
p2p::P2pPtr,
settings::SettingsPtr,
},
protocol_base::{ProtocolBase, ProtocolBasePtr},
protocol_jobs_manager::{ProtocolJobsManager, ProtocolJobsManagerPtr},
};
use crate::{
system::{sleep, timeout::timeout},
Error, Result,
};
/// Defines ping and pong messages
pub struct ProtocolPing {
channel: ChannelPtr,
ping_sub: MessageSubscription,
pong_sub: MessageSubscription,
settings: SettingsPtr,
jobsman: ProtocolJobsManagerPtr,
}
const PROTO_NAME: &str = "ProtocolPing";
impl ProtocolPing {
/// Create a new ping-pong protocol.
pub async fn init(channel: ChannelPtr, p2p: P2pPtr) -> ProtocolBasePtr {
let settings = p2p.settings();
// Creates a subscription to ping message
let ping_sub =
channel.subscribe_msg::().await.expect("Missing ping dispatcher!");
// Creates a subscription to pong message
let pong_sub =
channel.subscribe_msg::().await.expect("Missing pong dispatcher!");
Arc::new(Self {
channel: channel.clone(),
ping_sub,
pong_sub,
settings,
jobsman: ProtocolJobsManager::new(PROTO_NAME, channel),
})
}
/// Runs the ping-pong protocol. Creates a subscription to pong, then
/// starts a loop. Loop sleeps for the duration of the channel heartbeat,
/// then sends a ping message with a random nonce. Loop starts a timer,
/// waits for the pong reply and ensures the nonce is the same.
async fn run_ping_pong(self: Arc) -> Result<()> {
debug!(
target: "net::protocol_ping::run_ping_pong()",
"START => address={}", self.channel.address(),
);
loop {
// Create a random nonce.
let nonce = Self::random_nonce();
// Send ping message.
let ping = PingMessage { nonce };
self.channel.send(&ping).await?;
// Start the timer for the ping timer
let timer = Instant::now();
// Wait for pong, check nonce matches.
let pong_msg = match timeout(
Duration::from_secs(self.settings.outbound_connect_timeout),
self.pong_sub.receive(),
)
.await
{
Ok(msg) => {
// msg will be an error when the channel is stopped
// so just yield out of this function.
msg?
}
Err(_e) => {
// Pong timeout. We didn't receive any message back
// so close the connection.
warn!(
target: "net::protocol_ping::run_ping_pong()",
"[P2P] Ping-Pong protocol timed out for {}", self.channel.address(),
);
self.channel.stop().await;
return Err(Error::ChannelStopped)
}
};
if pong_msg.nonce != nonce {
error!(
target: "net::protocol_ping::run_ping_pong()",
"[P2P] Wrong nonce in pingpong, disconnecting {}",
self.channel.address(),
);
self.channel.stop().await;
return Err(Error::ChannelStopped)
}
debug!(
target: "net::protocol_ping::run_ping_pong()",
"Received Pong from {}: {:?}",
self.channel.address(),
timer.elapsed(),
);
// Sleep until next heartbeat
sleep(self.settings.channel_heartbeat_interval).await;
}
}
/// Waits for ping, then replies with pong.
/// Copies ping's nonce into the pong reply.
async fn reply_to_ping(self: Arc) -> Result<()> {
debug!(
target: "net::protocol_ping::reply_to_ping()",
"START => address={}", self.channel.address(),
);
loop {
// Wait for ping, reply with pong that has a matching nonce.
let ping = self.ping_sub.receive().await?;
debug!(
target: "net::protocol_ping::reply_to_ping()",
"Received Ping from {}", self.channel.address(),
);
// Send pong message
let pong = PongMessage { nonce: ping.nonce };
self.channel.send(&pong).await?;
debug!(
target: "net::protocol_ping::reply_to_ping()",
"Sent Pong reply to {}", self.channel.address(),
);
}
}
fn random_nonce() -> u16 {
OsRng::gen(&mut OsRng)
}
}
#[async_trait]
impl ProtocolBase for ProtocolPing {
/// Starts ping-pong keepalive messages exchange. Runs ping-pong in the
/// protocol task manager, then queues the reply. Sends out a ping and
/// waits for pong reply. Waits for ping and replies with a pong.
async fn start(self: Arc, ex: Arc>) -> Result<()> {
debug!(target: "net::protocol_ping::start()", "START => address={}", self.channel.address());
self.jobsman.clone().start(ex.clone());
self.jobsman.clone().spawn(self.clone().run_ping_pong(), ex.clone()).await;
self.jobsman.clone().spawn(self.clone().reply_to_ping(), ex).await;
debug!(target: "net::protocol_ping::start()", "END => address={}", self.channel.address());
Ok(())
}
fn name(&self) -> &'static str {
PROTO_NAME
}
}