use std::{sync::atomic::Ordering, time::Duration};
use qbase::{frame::PathChallengeFrame, net::tx::Signals};
use qcongestion::Transport;
use thiserror::Error;
use tokio::time::Instant;
#[derive(Debug, Error, Clone, Copy)]
pub enum ValidateFailure {
#[error(
"Path validation abort due to path inactivity by other reasons(usually connection closed)"
)]
PathInactive,
#[error("Path validation failed after {0} ms", elapsed.as_millis())]
Timeout { elapsed: Duration },
}
impl super::Path {
pub fn validated(&self) {
self.validated.store(true, Ordering::Release);
self.tx_waker.wake_by(Signals::PATH_VALIDATE);
}
pub async fn validate(&self) -> Result<(), ValidateFailure> {
let challenge = PathChallengeFrame::random();
let start = Instant::now();
for _ in 0..30 {
let timeout_duration = self.cc().get_pto(qbase::Epoch::Data);
self.challenge_sndbuf.write(challenge);
match tokio::time::timeout(timeout_duration, self.response_rcvbuf.receive()).await {
Ok(Some(response)) if *response == *challenge => {
self.validated();
self.anti_amplifier.grant();
tracing::debug!(target: "quic", pathway=%self.pathway, "path validated successfully");
return Ok(());
}
Ok(None) => return Err(ValidateFailure::PathInactive),
_ => continue,
}
}
Err(ValidateFailure::Timeout {
elapsed: start.elapsed(),
})
}
}