use std::io::ErrorKind;
use std::net::{IpAddr, TcpStream, ToSocketAddrs};
use std::time::Duration;
use crate::rendezvous::call::Roving;
use crate::rendezvous::say::{self, Kind};
const DIRECT: Duration = Duration::from_secs(10);
struct Missed {
sentence: String,
tried: Vec<IpAddr>,
timed_out: bool,
}
pub(super) fn climb(
address: &str,
roving: Option<&mut Roving>,
) -> Result<(TcpStream, bool), String> {
let direct = direct(address);
let Some(roving) = roving else {
return direct.map(|tcp| (tcp, false)).map_err(|m| m.sentence);
};
let refusal = match direct {
Ok(tcp) => {
roving.say.say(Kind::Direct, say::direct_connected());
return Ok((tcp, false));
}
Err(missed) => {
let line = say::direct_refused(&missed.tried, missed.timed_out);
roving.say.say(Kind::Direct, line);
missed.sentence
}
};
if let Some(tcp) = roving.recall() {
return Ok((tcp, true));
}
match roving.rendezvous() {
Ok(tcp) => Ok((tcp, true)),
Err(why) => Err(format!("{refusal}; rendezvous: {why}")),
}
}
fn direct(address: &str) -> Result<TcpStream, Missed> {
let resolved = address.to_socket_addrs().map_err(|e| Missed {
sentence: format!("connect {address}: {e}"),
tried: Vec::new(),
timed_out: false,
})?;
let mut last = None;
let mut tried = Vec::new();
for at in resolved {
tried.push(at.ip());
match TcpStream::connect_timeout(&at, DIRECT) {
Ok(tcp) => return Ok(tcp),
Err(e) => last = Some(e),
}
}
let timed_out = last
.as_ref()
.is_some_and(|e| e.kind() == ErrorKind::TimedOut);
Err(Missed {
sentence: format!(
"connect {address}: {}",
last.map_or_else(|| "no address resolved".to_owned(), |e| e.to_string())
),
tried,
timed_out,
})
}