use std::collections::BTreeMap;
use std::io::{Error, ErrorKind};
use std::net::{IpAddr, SocketAddr};
use std::time::Duration;
use crate::run::Notice;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum Kind {
Direct,
Presence,
Call,
Punch,
Landed,
Held,
Ping,
}
pub(crate) struct Say {
notice: Notice,
last: BTreeMap<Kind, String>,
}
impl Say {
pub(crate) fn to(notice: &Notice) -> Say {
Say {
notice: std::sync::Arc::clone(notice),
last: BTreeMap::new(),
}
}
pub(crate) fn silent() -> Say {
Say::to(&(std::sync::Arc::new(|_: &str| ()) as Notice))
}
pub(crate) fn say(&mut self, kind: Kind, line: String) {
if self.last.get(&kind) != Some(&line) {
(self.notice)(&line);
self.last.insert(kind, line);
}
if kind == Kind::Held {
self.last.remove(&Kind::Ping);
}
}
}
impl std::fmt::Debug for Say {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Say")
.field("notice", &"a sink")
.field("last", &self.last)
.finish()
}
}
const P: &str = "rendezvous:";
pub(crate) const WALK: &str = "the DHT walk failed (reason withheld: it names nodes)";
pub(crate) const NO_WALK: &str =
"no DHT walk could start (no bootstrap node resolved, or no socket)";
pub(crate) const NONE: &str = "none is published under the engine's rendezvous key";
pub(crate) const SEALED: &str = "the item does not open under this pairing";
pub(crate) fn direct_connected() -> String {
format!("{P} direct rung connected — no punch needed")
}
pub(crate) fn direct_refused(tried: &[IpAddr], timed_out: bool) -> String {
let how = if timed_out { "timed out" } else { "refused" };
format!(
"{P} direct rung {how} — {} address(es) tried ({})",
tried.len(),
families(tried)
)
}
pub(crate) fn presence_read(seq: i64, endpoints: &[SocketAddr]) -> String {
format!(
"{P} presence read — seq {seq}, {}",
counted(endpoints, "endpoint")
)
}
pub(crate) fn presence_unread(why: &str) -> String {
format!("{P} presence not read — {why}")
}
pub(crate) fn call_written(nonce: u64, seq: i64, endpoints: &[SocketAddr], acks: usize) -> String {
format!(
"{P} call nonce {nonce} written — seq {seq}, {}, {acks} ack(s)",
counted(endpoints, "endpoint")
)
}
pub(crate) fn call_unwritten(nonce: u64) -> String {
format!("{P} call nonce {nonce} not written — {WALK}")
}
pub(crate) fn recalled(nonce: u64, endpoints: &[SocketAddr], acks: usize) -> String {
format!(
"{P} re-call from cached presence — nonce {nonce}, {}, {acks} ack(s)",
counted(endpoints, "endpoint")
)
}
pub(crate) fn punch_started(nonce: u64, targets: &[SocketAddr], window: Duration) -> String {
format!(
"{P} punch for call nonce {nonce} at {} — window {window:?}",
counted(targets, "endpoint")
)
}
pub(crate) fn landed(peer: Option<SocketAddr>) -> String {
let peer: Vec<IpAddr> = peer.iter().map(SocketAddr::ip).collect();
format!("{P} punch landed ({})", families(&peer))
}
pub(crate) fn expired(window: Duration) -> String {
format!("{P} punch expired after {window:?} with no stream")
}
pub(crate) fn held_kept() -> String {
format!("{P} held line kept — the punched connection carries the next ask")
}
pub(crate) fn held_dropped(leg: &str, e: &Error) -> String {
let class = match e.kind() {
ErrorKind::TimedOut | ErrorKind::WouldBlock => "silent past the hangup",
ErrorKind::UnexpectedEof
| ErrorKind::ConnectionReset
| ErrorKind::ConnectionAborted
| ErrorKind::BrokenPipe => "closed by the far end",
_ => "a transport error",
};
format!("{P} held line dropped — the {leg} failed ({class})")
}
pub(crate) fn ping() -> String {
format!("{P} ping discarded off the held line")
}
fn counted(endpoints: &[SocketAddr], noun: &str) -> String {
let ips: Vec<IpAddr> = endpoints.iter().map(SocketAddr::ip).collect();
format!("{} {noun}(s) ({})", ips.len(), families(&ips))
}
fn families(ips: &[IpAddr]) -> String {
let v6 = ips.iter().filter(|ip| ip.is_ipv6()).count();
let v4 = ips.len() - v6;
let parts: Vec<String> = [(v6, "v6"), (v4, "v4")]
.into_iter()
.filter(|(n, _)| *n > 0)
.map(|(n, family)| format!("{n} {family}"))
.collect();
if parts.is_empty() {
"none".to_owned()
} else {
parts.join(", ")
}
}
#[cfg(test)]
mod tests;