use super::server::{Answerer, Quiet};
use crate::dht::{Config, Transport, 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, AtomicUsize, Ordering};
use std::thread::JoinHandle;
use std::time::Duration;
mod cycle;
pub mod item;
pub mod material;
pub mod punch;
pub(crate) use punch::Punch;
pub(crate) fn mainline() -> Vec<String> {
vec![
"router.bittorrent.com:6881".to_owned(),
"dht.transmissionbt.com:6881".to_owned(),
]
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct Cadence {
pub(crate) publish: Duration,
pub(crate) poll: Duration,
pub(crate) tick: Duration,
pub(crate) window: Duration,
pub(crate) quiet: Quiet,
}
impl Default for Cadence {
fn default() -> Cadence {
Cadence {
publish: Duration::from_hours(1),
poll: Duration::from_secs(15),
tick: Duration::from_secs(1),
window: Duration::from_secs(20),
quiet: Quiet::held(),
}
}
}
pub(crate) struct Ctx {
pub(crate) pairing: material::Pairing,
pub(crate) transport: Box<dyn Transport>,
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,
}
#[derive(Default)]
pub(crate) struct Stats {
pub(crate) published: AtomicUsize,
pub(crate) publish_failures: AtomicUsize,
pub(crate) polls: AtomicUsize,
pub(crate) poll_failures: AtomicUsize,
pub(crate) calls: AtomicUsize,
pub(crate) served: AtomicUsize,
}
pub(crate) struct Rendezvous {
#[cfg(test)]
stats: Arc<Stats>,
stop: Arc<AtomicBool>,
handle: Option<JoinHandle<()>>,
}
impl Rendezvous {
pub(crate) fn spawn(ctx: Ctx) -> Result<Rendezvous, String> {
let stats = Arc::new(Stats::default());
let cycle = cycle::Cycle::new(ctx, Arc::clone(&stats))?;
let stop = Arc::new(AtomicBool::new(false));
let flag = Arc::clone(&stop);
let handle = std::thread::spawn(move || cycle.run(&flag));
Ok(Rendezvous {
#[cfg(test)]
stats,
stop,
handle: Some(handle),
})
}
#[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);
if let Some(handle) = self.handle.take() {
let _ = handle.join();
}
}
}
pub(crate) fn start(
dir: &Path,
answerer: Arc<dyn Answerer>,
presence: Presence,
clock: Arc<dyn Clock>,
bootstrap: Vec<String>,
) -> 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: Box::new(udp),
bootstrap,
config: Config::default(),
punch: Punch::bind(0)?,
advertise: punch::local_ips(),
tls,
answerer,
presence,
clock,
cadence: Cadence::default(),
})
.map(Some)
}
#[cfg(test)]
mod tests;