use super::item::Presence;
use super::pairing::Pairing;
use super::punch::Punch;
use super::say::{self, Kind, Say};
use crate::dht::{Config, Dht, Udp};
use std::net::{SocketAddr, TcpStream, ToSocketAddrs};
use std::time::Duration;
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,
pub(crate) say: Say,
}
impl Roving {
pub(crate) fn new(pairing: Pairing, tuning: Tuning) -> Roving {
Roving {
pairing,
tuning,
punch: None,
cached: None,
last_seq: 0,
say: Say::silent(),
}
}
pub(crate) fn recall(&mut self) -> Option<TcpStream> {
let engine = self.cached.take()?;
let mut dht = self.dht().ok()?;
self.called(&mut dht, engine, true).ok()?
}
pub(crate) fn rendezvous(&mut self) -> Result<TcpStream, String> {
let unread = |why: &str| say::presence_unread(why);
let mut dht = self
.dht()
.map_err(|e| self.missed(Kind::Presence, unread(say::NO_WALK), e))?;
let got = dht.get(self.pairing.key, self.pairing.presence_salt());
let item = got
.map_err(|e| self.missed(Kind::Presence, unread(say::WALK), e))?
.ok_or_else(|| {
self.missed(
Kind::Presence,
unread(say::NONE),
"no presence is published under the engine's rendezvous key".to_owned(),
)
})?;
let presence = Presence::open(&self.pairing.seal_key(), &item.value).ok_or_else(|| {
self.missed(
Kind::Presence,
unread(say::SEALED),
"the presence item does not open under this pairing".to_owned(),
)
})?;
let line = say::presence_read(item.seq, &presence.endpoints);
self.say.say(Kind::Presence, line);
if presence.endpoints.is_empty() {
return Err("the engine's presence names no endpoint".to_owned());
}
let count = presence.endpoints.len();
self.called(&mut dht, presence.endpoints, false)?
.ok_or_else(|| {
format!("no SYN crossed toward {count} engine endpoint(s) inside the window")
})
}
fn punched(
&mut self,
nonce: u64,
targets: Vec<SocketAddr>,
) -> Result<Option<TcpStream>, String> {
let window = self.tuning.window;
self.punch()?;
self.say
.say(Kind::Punch, say::punch_started(nonce, &targets, window));
let landed = self.punch()?.punch(targets, window);
let line = landed.as_ref().map_or_else(
|| say::expired(window),
|tcp| say::landed(tcp.peer_addr().ok()),
);
self.say.say(Kind::Landed, line);
Ok(landed)
}
fn missed(&mut self, kind: Kind, line: String, why: String) -> String {
self.say.say(kind, line);
why
}
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())
}
}
mod write;
#[cfg(test)]
mod tests;