use std::net::{IpAddr, TcpStream, ToSocketAddrs};
use std::time::Duration;
use super::line::{Held, Line};
use super::{Channel, say};
use crate::dht::Config;
use crate::state;
mod commons;
#[derive(Clone, Debug)]
pub struct Roving {
pub direct: Duration,
pub window: Duration,
pub dht: Config,
pub bootstrap: Vec<String>,
pub advertise: Option<Vec<IpAddr>>,
pub say: say::Say,
}
impl Default for Roving {
fn default() -> Roving {
Roving {
direct: Duration::from_secs(5),
window: Duration::from_secs(40),
dht: Config::default(),
bootstrap: vec![
"router.bittorrent.com:6881".to_owned(),
"dht.transmissionbt.com:6881".to_owned(),
"router.utorrent.com:6881".to_owned(),
"dht.aelitis.com:6881".to_owned(),
],
advertise: None,
say: say::Say::default(),
}
}
}
pub(crate) fn climb(ch: &Channel, held: bool) -> Result<Line, String> {
let mut said = Vec::new();
let line = rungs(ch, held, &mut said);
say::tell(ch, "climb", said);
line
}
fn rungs(ch: &Channel, held: bool, said: &mut Vec<String>) -> Result<Line, String> {
if held && let Some(line) = rung_held(ch, said) {
return Ok(line);
}
let direct = rung_direct(ch);
if ch.pairing.is_some() {
let outcome = direct.as_ref().map(|_| ()).map_err(std::io::Error::kind);
said.push(say::direct(outcome, ch.roving.direct));
}
let direct = match direct {
Ok(tcp) => return ch.line(tcp, false),
Err(e) => format!("connect {}: {e}", ch.address),
};
let Some(pairing) = ch.pairing else {
return Err(direct);
};
let tcp = commons::rungs(ch, &pairing, said)
.map_err(|refusal| format!("{direct}; rendezvous: {refusal}"))?;
said.push(say::kept());
ch.line(tcp, true)
}
fn rung_held(ch: &Channel, said: &mut Vec<String>) -> Option<Line> {
let mut pool = state::worked(&ch.key, |w| std::mem::take(&mut w.held));
let now = ch.clock.now();
let mut chosen: Option<Held> = None;
while chosen.is_none()
&& let Some(mut held) = pool.pop()
{
match held.gone(now) {
None => chosen = Some(held),
Some(why) => said.push(say::dropped(why)),
}
}
state::worked(&ch.key, |w| w.held.append(&mut pool));
let line = chosen.map(Line::from_held);
if line.is_some() {
said.push(say::taken());
}
line
}
fn rung_direct(ch: &Channel) -> std::io::Result<TcpStream> {
if ch.pairing.is_none() {
TcpStream::connect(&ch.address)
} else {
bounded(&ch.address, ch.roving.direct)
}
}
fn bounded(address: &str, within: Duration) -> std::io::Result<TcpStream> {
let mut refusal = std::io::Error::other("resolved to no address");
for addr in address.to_socket_addrs()? {
match TcpStream::connect_timeout(&addr, within) {
Ok(tcp) => return Ok(tcp),
Err(e) => refusal = e,
}
}
Err(refusal)
}
#[cfg(test)]
mod tests;