use crate::error::{Error, Result};
use crate::event::Event;
use crate::node::ChorrentNode;
use futures_lite::stream::StreamExt;
use iroh::endpoint::Connection;
use iroh::{EndpointAddr, EndpointId};
use iroh_gossip::api::{Event as GossipEvent, GossipReceiver, GossipSender};
use iroh_gossip::proto::TopicId;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
use tokio::sync::{broadcast, mpsc};
use tokio::time::Instant;
const ANNOUNCE_EVERY: Duration = Duration::from_secs(5);
const REJOIN_EVERY: Duration = Duration::from_secs(15);
const DIAL_TIMEOUT: Duration = Duration::from_secs(10);
#[derive(Serialize, Deserialize, Debug, Clone)]
pub(crate) struct Announcement {
pub addr: EndpointAddr,
}
async fn predial(node: &Arc<ChorrentNode>, peers: &[EndpointAddr]) -> Vec<Connection> {
let mut dials = tokio::task::JoinSet::new();
for peer in peers.iter().filter(|p| p.id != node.id() && !p.is_empty()) {
let (node, peer) = (Arc::clone(node), peer.clone());
dials.spawn(async move {
tokio::time::timeout(DIAL_TIMEOUT, node.connect(peer))
.await
.ok()?
.ok()
});
}
dials.join_all().await.into_iter().flatten().collect()
}
pub(crate) struct Swarm {
pub node: Arc<ChorrentNode>,
pub topic: TopicId,
pub bootstrap: Vec<EndpointAddr>,
pub announce: Arc<AtomicBool>,
pub found: Option<mpsc::Sender<EndpointAddr>>,
pub events: broadcast::Sender<Event>,
}
pub(crate) async fn join(swarm: &Swarm) -> Result<(GossipSender, GossipReceiver, Vec<Connection>)> {
let gossip = swarm
.node
.gossip()
.ok_or_else(|| Error::Gossip("gossip is disabled".into()))?;
let warm = predial(&swarm.node, &swarm.bootstrap).await;
let ids = swarm
.bootstrap
.iter()
.map(|p| p.id)
.filter(|id| *id != swarm.node.id())
.collect();
let (sender, receiver) = gossip
.subscribe(swarm.topic, ids)
.await
.map_err(|e| Error::Gossip(e.to_string()))?
.split();
Ok((sender, receiver, warm))
}
pub(crate) async fn keep(
swarm: Swarm,
sender: GossipSender,
mut receiver: GossipReceiver,
mut warm: Vec<Connection>,
) {
let me = swarm.node.id();
let mut neighbours: HashSet<EndpointId> = HashSet::new();
let mut known: HashMap<EndpointId, EndpointAddr> = swarm
.bootstrap
.iter()
.filter(|p| p.id != me)
.map(|p| (p.id, p.clone()))
.collect();
let mut announce = tokio::time::interval(ANNOUNCE_EVERY);
let mut last_rejoin = Instant::now();
let _ = swarm.events.send(Event::SwarmPeers { connected: 0 });
loop {
tokio::select! {
event = receiver.next() => {
let Some(event) = event else { break };
let before = neighbours.len();
match event {
Ok(GossipEvent::NeighborUp(id)) => { neighbours.insert(id); }
Ok(GossipEvent::NeighborDown(id)) => { neighbours.remove(&id); }
Ok(GossipEvent::Received(message)) => {
if let Ok(announcement) = postcard::from_bytes::<Announcement>(&message.content)
&& announcement.addr.id != me
{
known.insert(announcement.addr.id, announcement.addr.clone());
if let Some(found) = &swarm.found {
let _ = found.try_send(announcement.addr);
}
}
}
_ => {}
}
if neighbours.len() != before {
let _ = swarm.events.send(Event::SwarmPeers { connected: neighbours.len() });
if !neighbours.is_empty() {
warm.clear(); }
}
}
_ = announce.tick() => {
if swarm.announce.load(Ordering::Relaxed) {
let bytes = postcard::to_allocvec(&Announcement { addr: swarm.node.addr() })
.expect("Announcement always serializes");
let _ = sender.broadcast(bytes.into()).await;
}
if neighbours.is_empty() && !known.is_empty() && last_rejoin.elapsed() >= REJOIN_EVERY {
last_rejoin = Instant::now();
let peers: Vec<EndpointAddr> = known.values().cloned().collect();
warm = predial(&swarm.node, &peers).await;
let _ = sender.join_peers(peers.iter().map(|p| p.id).collect()).await;
}
}
}
}
}