use std::io;
use std::net::SocketAddr;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::time::Instant;
use parking_lot::Mutex;
use tokio::sync::{Notify, broadcast};
use tracing::Span;
use crate::config::NodeConfig;
use crate::events::Events;
use crate::explorer::Explorer;
use crate::view::{View, ViewConfig};
pub const EVENT_CHANNEL_CAPACITY: usize = 256;
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum DiscoveryEvent {
SampleSent {
to: SocketAddr,
count: usize,
},
SampleReceived {
from: SocketAddr,
count: usize,
},
PeerDiscovered {
addr: SocketAddr,
category: crate::view::PeerCategory,
},
PeerEvicted {
addr: SocketAddr,
},
}
pub(crate) struct NodeInner {
pub(crate) config: NodeConfig,
pub(crate) peashape: peashape::Node,
pub(crate) view: Mutex<View>,
pub(crate) explorer: std::sync::OnceLock<Explorer>,
pub(crate) events: Events,
pub(crate) shutting_down: AtomicBool,
pub(crate) shutdown_waiter: Notify,
pub(crate) span: Span,
pub(crate) receive_handle: Mutex<Option<tokio::task::JoinHandle<()>>>,
}
impl NodeInner {
pub fn recent_window(&self) -> usize {
(self.config.view_size / 2).clamp(1, 4)
}
}
#[derive(Clone)]
pub struct Node {
pub(crate) inner: Arc<NodeInner>,
}
impl Node {
pub fn new(config: NodeConfig) -> Self {
Self::with_seed(config, 0)
}
pub fn with_seed(config: NodeConfig, seed: u64) -> Self {
let min_frame =
peashape::ID_SIZE + crate::sample::SAMPLE_HEADER_SIZE + crate::sample::IPV4_ENTRY_SIZE;
assert!(
config.frame_size >= min_frame,
"frame_size ({fs} bytes) is too small: a peaveil frame needs at least \
{min} bytes (peashape ID ({id}) + sample header ({hdr}) + one IPv4 entry ({e}))",
fs = config.frame_size,
min = min_frame,
id = peashape::ID_SIZE,
hdr = crate::sample::SAMPLE_HEADER_SIZE,
e = crate::sample::IPV4_ENTRY_SIZE,
);
assert!(config.view_size > 0, "view_size must be non-zero");
assert!(config.sample_size > 0, "sample_size must be non-zero");
let worst_case_payload =
crate::sample::SAMPLE_HEADER_SIZE + config.sample_size * crate::sample::IPV6_ENTRY_SIZE;
assert!(
peashape::ID_SIZE + worst_case_payload <= config.frame_size,
"frame_size ({fs} bytes) is too small for sample_size={ss}: \
a worst-case (all-IPv6) sample needs at least {need} bytes \
(peashape ID ({id}) + sample header ({hdr}) + {ss} x IPv6 entry ({e}))",
fs = config.frame_size,
ss = config.sample_size,
need = peashape::ID_SIZE + worst_case_payload,
id = peashape::ID_SIZE,
hdr = crate::sample::SAMPLE_HEADER_SIZE,
e = crate::sample::IPV6_ENTRY_SIZE,
);
let placeholder: SocketAddr = "0.0.0.0:0".parse().unwrap();
let peashape_node = peashape::Node::new(crate::config_bridge::config_to_shape(&config));
let view = View::new(
ViewConfig {
max_non_bootstrap: config.view_size,
..ViewConfig::default()
},
placeholder,
seed,
);
let span = tracing::info_span!("peaveil", name = config.name.as_deref().unwrap_or("?"));
let inner = Arc::new(NodeInner {
config: config.clone(),
peashape: peashape_node,
view: Mutex::new(view),
explorer: std::sync::OnceLock::new(),
events: Events::new(),
shutting_down: AtomicBool::new(false),
shutdown_waiter: Notify::new(),
span,
receive_handle: Mutex::new(None),
});
inner
.explorer
.set(Explorer::new(inner.clone(), seed))
.ok()
.expect("OnceLock::set called exactly once");
Self { inner }
}
pub async fn spawn(&self) -> io::Result<Option<SocketAddr>> {
let addr = self.inner.peashape.spawn().await?;
let self_addr = addr.unwrap_or_else(|| "0.0.0.0:0".parse().unwrap());
self.inner
.view
.lock()
.set_self_addr(self_addr, Instant::now());
let bootstrap = self.inner.config.bootstrap.clone();
if !bootstrap.is_empty() {
self.inner.view.lock().add_bootstrap(bootstrap);
}
self.spawn_receive_task();
self.inner
.explorer
.get()
.expect("explorer initialized")
.spawn();
Ok(addr)
}
pub async fn local_addr(&self) -> io::Result<Option<SocketAddr>> {
let a = self.inner.peashape.local_addr().await?;
Ok(Some(a))
}
pub fn connected_peers(&self) -> Vec<SocketAddr> {
self.inner.peashape.connected_peers()
}
pub async fn connect(&self, addr: SocketAddr) -> io::Result<()> {
self.inner.peashape.connect(addr).await
}
pub async fn disconnect(&self, addr: SocketAddr) -> bool {
self.inner.peashape.disconnect(addr).await
}
pub fn view(&self) -> crate::view::ViewSnapshot {
self.inner.view.lock().snapshot()
}
pub fn add_bootstrap(&self, addr: SocketAddr) {
self.inner.view.lock().add_bootstrap([addr]);
}
pub fn add_recent(&self, addr: SocketAddr) {
self.inner.view.lock().add_recent([addr]);
}
pub fn is_known(&self, addr: &SocketAddr) -> bool {
self.inner.view.lock().contains(addr)
}
pub fn drop_peer(&self, addr: &SocketAddr) {
self.inner.view.lock().drop_entry(addr);
}
pub fn known_peers(&self) -> Vec<SocketAddr> {
let snap = self.inner.view.lock().snapshot();
let mut all: Vec<SocketAddr> = snap
.trusted
.into_iter()
.chain(snap.recent)
.chain(snap.random)
.chain(snap.bootstrap)
.map(|p| p.addr)
.collect();
all.sort();
all.dedup();
all
}
pub fn subscribe_events(&self) -> broadcast::Receiver<DiscoveryEvent> {
self.inner.events.subscribe()
}
pub fn peashape(&self) -> &peashape::Node {
&self.inner.peashape
}
pub fn p2p(&self) -> &pea2pea::Node {
self.inner.peashape.p2p()
}
pub fn config(&self) -> &NodeConfig {
&self.inner.config
}
pub fn reseed(&self, seed: u64) {
if let Some(e) = self.inner.explorer.get() {
e.reseed(seed);
}
}
pub async fn shutdown(&self) {
self.inner
.shutting_down
.store(true, std::sync::atomic::Ordering::SeqCst);
self.inner.shutdown_waiter.notify_waiters();
if let Some(e) = self.inner.explorer.get() {
e.abort();
}
if let Some(h) = self.inner.receive_handle.lock().take() {
h.abort();
}
self.inner.peashape.shutdown().await;
}
fn spawn_receive_task(&self) {
let inner = self.inner.clone();
let mut rx = inner.peashape.subscribe();
let handle = tokio::spawn(async move {
let min_len = peashape::ID_SIZE + crate::sample::SAMPLE_HEADER_SIZE;
while let Ok(frame) = rx.recv().await {
if frame.len() < min_len {
continue;
}
let payload = &frame[peashape::ID_SIZE..];
match crate::sample::PeerSample::decode(payload) {
Ok(sample) => {
crate::explorer::handle_received_sample(&inner, sample);
}
Err(_) => {
}
}
}
});
*self.inner.receive_handle.lock() = Some(handle);
}
}