use super::item::{Call, Presence};
use super::pairing::Pairing;
use super::punch::{Punch, local_ips};
use crate::dht::{Config, Dht, Udp};
use std::net::{IpAddr, SocketAddr, TcpStream, ToSocketAddrs};
use std::time::{Duration, SystemTime};
const WINDOW: Duration = Duration::from_secs(35);
#[derive(Clone, Debug)]
pub(crate) struct Tuning {
pub(crate) config: Config,
pub(crate) window: Duration,
pub(crate) bootstrap: Vec<String>,
}
impl Default for Tuning {
fn default() -> Tuning {
Tuning {
config: Config::default(),
window: WINDOW,
bootstrap: super::mainline(),
}
}
}
#[derive(Debug)]
pub(crate) struct Roving {
pairing: Pairing,
pub(crate) tuning: Tuning,
punch: Option<Punch>,
cached: Option<Vec<SocketAddr>>,
last_seq: i64,
}
impl Roving {
pub(crate) fn new(pairing: Pairing, tuning: Tuning) -> Roving {
Roving {
pairing,
tuning,
punch: None,
cached: None,
last_seq: 0,
}
}
pub(crate) fn repunch(&mut self) -> Option<TcpStream> {
let targets = self.cached.clone()?;
let window = self.tuning.window;
self.punch().ok()?.punch(targets, window)
}
pub(crate) fn rendezvous(&mut self) -> Result<TcpStream, String> {
let mut dht = self.dht()?;
let seal_key = self.pairing.seal_key();
let item = dht
.get(self.pairing.key, self.pairing.presence_salt())?
.ok_or("no presence is published under the engine's rendezvous key")?;
let presence = Presence::open(&seal_key, &item.value)
.ok_or("the presence item does not open under this pairing")?;
if presence.endpoints.is_empty() {
return Err("the engine's presence names no endpoint".to_owned());
}
let port = self.punch()?.port();
let call = Call {
nonce: nonce()?,
endpoints: listed(local_ips(), &dht.observed(), port),
};
let seq = unix_now().max(self.last_seq + 1);
let signed = self.pairing.inbox_keypair()?.sign(
self.pairing.inbox_salt(),
seq,
call.seal(&seal_key)?,
)?;
dht.put(signed)?;
self.last_seq = seq;
self.cached = Some(presence.endpoints.clone());
let window = self.tuning.window;
let targets = presence.endpoints;
let count = targets.len();
self.punch()?.punch(targets, window).ok_or_else(|| {
format!("no SYN crossed toward {count} engine endpoint(s) inside the window")
})
}
fn punch(&mut self) -> Result<&Punch, String> {
if self.punch.is_none() {
self.punch = Some(Punch::bind(0)?);
}
self.punch
.as_ref()
.ok_or_else(|| "no punch port".to_owned())
}
fn dht(&self) -> Result<Dht, String> {
let bootstrap: Vec<SocketAddr> = self
.tuning
.bootstrap
.iter()
.filter_map(|name| name.to_socket_addrs().ok())
.flatten()
.collect();
if bootstrap.is_empty() {
return Err("no DHT bootstrap node resolved".to_owned());
}
let udp = Udp::bind("0.0.0.0:0".parse().map_err(|e| format!("{e}"))?)
.map_err(|e| format!("DHT socket: {e}"))?;
Dht::new(Box::new(udp), bootstrap, self.tuning.config.clone())
}
}
fn listed(mut ips: Vec<IpAddr>, observed: &[SocketAddr], port: u16) -> Vec<SocketAddr> {
for ip in observed.iter().map(SocketAddr::ip) {
if !ips.contains(&ip) {
ips.push(ip);
}
}
ips.into_iter()
.map(|ip| SocketAddr::new(ip, port))
.collect()
}
fn nonce() -> Result<u64, String> {
let mut bytes = [0u8; 8];
crate::dht::random(&mut bytes)?;
Ok(u64::from_be_bytes(bytes))
}
fn unix_now() -> i64 {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map_or(0, |d| i64::try_from(d.as_secs()).unwrap_or(i64::MAX))
}
#[cfg(test)]
mod tests;