use super::server::Answerer;
use crate::dht::{Config, Udp};
use crate::registry::presence::Presence;
use crate::ui_state::Clock;
use rustls::ServerConfig;
use std::net::IpAddr;
use std::path::Path;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread::JoinHandle;
mod accept;
mod cadence;
mod call;
mod cycle;
pub mod item;
pub mod material;
pub mod punch;
mod say;
mod stats;
pub(crate) use cadence::{Cadence, mainline};
pub(crate) use punch::Punch;
pub(crate) use say::{Say, stderr};
pub use stats::Standing;
pub(crate) use stats::Stats;
pub(crate) struct Ctx {
pub(crate) pairing: material::Pairing,
pub(crate) transport: Udp,
pub(crate) bootstrap: Vec<String>,
pub(crate) config: Config,
pub(crate) punch: Punch,
pub(crate) advertise: Vec<IpAddr>,
pub(crate) tls: Arc<ServerConfig>,
pub(crate) answerer: Arc<dyn Answerer>,
pub(crate) presence: Presence,
pub(crate) clock: Arc<dyn Clock>,
pub(crate) cadence: Cadence,
pub(crate) stats: Arc<Stats>,
pub(crate) say: Say,
}
pub(crate) struct Rendezvous {
stats: Arc<Stats>,
stop: Arc<AtomicBool>,
handles: Vec<JoinHandle<()>>,
}
impl Rendezvous {
pub(crate) fn spawn(ctx: Ctx) -> Result<Rendezvous, String> {
let dht_port = ctx.transport.local_addr().map_or(0, |at| at.port());
let (stats, say) = (Arc::clone(&ctx.stats), Arc::clone(&ctx.say));
let (hints, toward) = std::sync::mpsc::channel();
let cycle = cycle::Cycle::new(ctx, hints)?;
let answer = cycle.answer.clone();
stats.active.store(true, Ordering::Relaxed);
say(&say::started(cycle.punch_port(), dht_port));
let stop = Arc::new(AtomicBool::new(false));
let (flag, also) = (Arc::clone(&stop), Arc::clone(&stop));
let handles = vec![
std::thread::spawn(move || cycle.run(&flag)),
std::thread::spawn(move || answer.accept(&toward, &also)),
];
Ok(Rendezvous {
stats,
stop,
handles,
})
}
#[cfg(test)]
pub(crate) fn stats(&self) -> Arc<Stats> {
Arc::clone(&self.stats)
}
}
impl Drop for Rendezvous {
fn drop(&mut self) {
self.stop.store(true, Ordering::Relaxed);
for handle in self.handles.drain(..) {
let _ = handle.join();
}
self.stats.active.store(false, Ordering::Relaxed);
}
}
pub(crate) fn start(
dir: &Path,
answerer: Arc<dyn Answerer>,
presence: Presence,
clock: Arc<dyn Clock>,
bootstrap: Vec<String>,
stats: Arc<Stats>,
) -> Result<Option<Rendezvous>, String> {
let Some(pairing) = material::read_dir(dir)? else {
return Ok(None);
};
let Some(served) = super::material::read_dir(dir, super::material::Role::Server)? else {
return Ok(None);
};
let tls = super::tls::server_config(&served)?;
let udp = Udp::bind("0.0.0.0:0".parse().map_err(|e| format!("{e}"))?)
.map_err(|e| format!("rendezvous: {e}"))?;
Rendezvous::spawn(Ctx {
pairing,
transport: udp,
bootstrap,
config: Config::default(),
punch: Punch::bind(0)?,
advertise: punch::local_ips(),
tls,
answerer,
presence,
clock,
cadence: Cadence::default(),
stats,
say: stderr(),
})
.map(Some)
}
#[cfg(test)]
mod tests;