use std::sync::Arc;
use std::time::Duration;
use async_trait::async_trait;
use crate::error::{MethodError, NatError};
use crate::method::{MethodOutcome, TraversalKind, TraversalMethod};
use crate::peer::{PeerConnection, PeerTarget};
#[async_trait]
pub trait Dialer: Send + Sync {
async fn dial(
&self,
peer: &PeerTarget,
outcome: &MethodOutcome,
) -> Result<PeerConnection, MethodError>;
}
pub async fn connect_with_strategy(
peer: &PeerTarget,
methods: Vec<Arc<dyn TraversalMethod>>,
dialer: &dyn Dialer,
per_method_timeout: Duration,
) -> Result<PeerConnection, NatError> {
if methods.is_empty() {
return Err(NatError::NoMethodsEnabled);
}
let mut ordered = methods;
ordered.sort_by_key(|m| m.kind().rank());
let mut failures: Vec<MethodError> = Vec::with_capacity(ordered.len());
for method in ordered {
let kind = method.kind();
let outcome = match run_bounded(per_method_timeout, kind, method.attempt(peer)).await {
Ok(outcome) => outcome,
Err(e) => {
tracing::debug!(?kind, reason = %e.reason, "traversal method did not produce an address");
failures.push(e);
continue;
}
};
match run_bounded(per_method_timeout, kind, dialer.dial(peer, &outcome)).await {
Ok(conn) => {
tracing::info!(?kind, remote = %conn.remote_addr, "peer connection established");
return Ok(conn);
}
Err(e) => {
tracing::debug!(?kind, reason = %e.reason, "dial failed; falling through");
failures.push(e);
}
}
}
Err(NatError::AllMethodsFailed(failures))
}
async fn run_bounded<T, F>(timeout: Duration, kind: TraversalKind, fut: F) -> Result<T, MethodError>
where
F: std::future::Future<Output = Result<T, MethodError>>,
{
match tokio::time::timeout(timeout, fut).await {
Ok(res) => res,
Err(_) => Err(MethodError::timeout(kind)),
}
}