/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2025 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::{clone::Clone, collections::HashMap, fmt::Debug, sync::Arc};
use async_trait::async_trait;
use smol::{
channel::{Receiver, Sender},
lock::RwLock,
Executor,
};
use tracing::debug;
use super::{
super::{
channel::ChannelPtr, message::Message, message_publisher::MessageSubscription,
session::SessionBitFlag,
},
protocol_base::{ProtocolBase, ProtocolBasePtr},
protocol_jobs_manager::{ProtocolJobsManager, ProtocolJobsManagerPtr},
P2pPtr,
};
use crate::{
system::{StoppableTask, StoppableTaskPtr},
Error, Result,
};
/// Defines generic messages protocol action signal.
#[derive(Debug)]
pub enum ProtocolGenericAction {
/// Broadcast message to rest nodes
Broadcast,
/// Send provided response message to the node
Response(M),
/// Skip message broadcast
Skip,
/// Stop the channel entirely
Stop,
}
pub type ProtocolGenericHandlerPtr = Arc>;
/// Defines a handler for generic protocol messages, consisting
/// of a message receiver, action signal senders mapped by each
/// channel ID, and a stoppable task to run the handler in the
/// background.
pub struct ProtocolGenericHandler {
// Since smol channels close if all senders or all receivers
// get dropped, we will keep one here to remain alive with the
// handler.
/// Message queue sender, passed to each P2P channel.
sender: Sender<(u32, M)>,
/// Message queue receiver listening for new messages
/// from all channels.
pub receiver: Receiver<(u32, M)>,
/// Senders mapped by channel ID to propagate the
/// action signal after a message retrieval.
senders: RwLock>>>,
/// Handler background task to run the messages listener
/// function with.
pub task: StoppableTaskPtr,
}
impl ProtocolGenericHandler {
/// Generate a new ProtocolGenericHandler for the provided P2P
/// instance. The handler also attaches its generic protocol.
pub async fn new(
p2p: &P2pPtr,
name: &'static str,
session: SessionBitFlag,
) -> ProtocolGenericHandlerPtr {
// Generate the message queue smol channel
let (sender, receiver) = smol::channel::unbounded::<(u32, M)>();
// Keep a map for all P2P channels senders
let senders = RwLock::new(HashMap::new());
// Create a new stoppable task
let task = StoppableTask::new();
// Create the handler
let handler = Arc::new(Self { sender, receiver, senders, task });
// Attach a generic protocol to the P2P insstance
let _handler = handler.clone();
p2p.protocol_registry()
.register(session, move |channel, p2p| {
let handler = _handler.clone();
async move { ProtocolGeneric::init(channel, name, handler, p2p).await.unwrap() }
})
.await;
handler
}
/// Registers a new channel sender to the handler map.
/// Additionally, looks for stale(closed) channels and prunes then from it.
async fn register_channel_sender(
&self,
channel: u32,
sender: Sender>,
) {
// Register the new channel sender
let mut lock = self.senders.write().await;
lock.insert(channel, sender);
// Look for stale channels
let mut stale = vec![];
for (channel, sender) in lock.iter() {
if sender.is_closed() {
stale.push(*channel);
}
}
// Prune stale channels
for channel in stale {
lock.remove(&channel);
}
drop(lock);
}
/// Sends provided protocol generic action to requested channel, if it exists.
pub async fn send_action(&self, channel: u32, action: ProtocolGenericAction) {
debug!(
target: "net::protocol_generic::ProtocolGenericHandler::send_action",
"Sending action {action:?} to channel {channel}..."
);
// Grab the requested channel sender
let mut lock = self.senders.write().await;
let Some(sender) = lock.get(&channel) else {
debug!(
target: "net::protocol_generic::ProtocolGenericHandler::send_action",
"Channel wasn't found."
);
drop(lock);
return
};
// Send the provided action
if let Err(e) = sender.send(action).await {
debug!(
target: "net::protocol_generic::ProtocolGenericHandler::send_action",
"Channel {channel} send fail: {e}"
);
lock.remove(&channel);
};
drop(lock);
}
}
/// Defines generic messages protocol.
pub struct ProtocolGeneric {
/// The P2P channel message subcription
msg_sub: MessageSubscription,
/// The generic message smol channel sender
sender: Sender<(u32, M)>,
/// Action signal smol channel receiver
receiver: Receiver>,
/// The P2P channel the protocol is serving
channel: ChannelPtr,
/// Pointer to the whole P2P instance
p2p: P2pPtr,
/// Pointer to the protocol job manager
jobsman: ProtocolJobsManagerPtr,
}
impl ProtocolGeneric {
/// Initialize a new generic protocol.
pub async fn init(
channel: ChannelPtr,
name: &'static str,
handler: ProtocolGenericHandlerPtr,
p2p: P2pPtr,
) -> Result {
debug!(
target: "net::protocol_generic::init",
"Adding generic protocol for message {name} to the protocol registry"
);
// Add the message dispatcher
let msg_subsystem = channel.message_subsystem();
msg_subsystem.add_dispatch::().await;
msg_subsystem.add_dispatch::().await;
// Create the message subscription
let msg_sub = channel.subscribe_msg::().await?;
// Create a new sender channel
let (action_sender, receiver) = smol::channel::bounded(1);
handler.register_channel_sender(channel.info.id, action_sender).await;
Ok(Arc::new(Self {
msg_sub,
sender: handler.sender.clone(),
receiver,
channel: channel.clone(),
p2p,
jobsman: ProtocolJobsManager::new(name, channel),
}))
}
/// Runs the message queue. We listen for the specified structure message,
/// and when one is received, we send it to our smol channel. Afterwards,
/// we wait for an action signal, specifying whether or not we should
/// propagate the message to rest nodes or skip it.
async fn handle_receive_message(self: Arc) -> Result<()> {
debug!(
target: "net::protocol_generic::handle_receive_message",
"START"
);
let exclude_list = vec![self.channel.address().clone()];
loop {
// Wait for a new message
let msg = match self.msg_sub.receive().await {
Ok(m) => m,
Err(e) => {
debug!(
target: "net::protocol_generic::handle_receive_message",
"[{}] recv fail: {e}", self.jobsman.clone().name()
);
continue
}
};
let msg_copy = (*msg).clone();
// Send the message across the smol channel
if let Err(e) = self.sender.send((self.channel.info.id, msg_copy.clone())).await {
debug!(
target: "net::protocol_generic::handle_receive_message",
"[{}] sending to channel fail: {e}", self.jobsman.clone().name()
);
continue
}
// Wait for action signal
let action = match self.receiver.recv().await {
Ok(a) => a,
Err(e) => {
debug!(
target: "net::protocol_generic::handle_receive_message",
"[{}] action signal recv fail: {e}", self.jobsman.clone().name()
);
continue
}
};
// Handle action signal
match action {
ProtocolGenericAction::Broadcast => {
self.p2p.broadcast_with_exclude(&msg_copy, &exclude_list).await
}
ProtocolGenericAction::Response(r) => {
if let Err(e) = self.channel.send(&r).await {
debug!(
target: "net::protocol_generic::handle_receive_message",
"[{}] Channel send fail: {e}", self.jobsman.clone().name()
)
};
}
ProtocolGenericAction::Skip => {
debug!(
target: "net::protocol_generic::handle_receive_message",
"[{}] Skip action signal received.", self.jobsman.clone().name()
);
}
ProtocolGenericAction::Stop => {
self.channel.stop().await;
return Err(Error::ChannelStopped)
}
}
}
}
}
#[async_trait]
impl ProtocolBase for ProtocolGeneric {
async fn start(self: Arc, ex: Arc>) -> Result<()> {
debug!(target: "net::protocol_generic::start", "START");
self.jobsman.clone().start(ex.clone());
self.jobsman.clone().spawn(self.clone().handle_receive_message(), ex).await;
debug!(target: "net::protocol_generic::start", "END");
Ok(())
}
fn name(&self) -> &'static str {
self.jobsman.clone().name()
}
}