use crate::Connection;
use s2n_quic_core::{
connection,
connection::Error,
ensure,
event::{
api as events,
api::{ConnectionInfo, ConnectionMeta, DcState, EndpointType, Subscriber},
},
};
use std::io;
use tokio::sync::watch;
pub struct ConfirmComplete;
impl ConfirmComplete {
pub async fn wait_ready(conn: &mut Connection) -> io::Result<()> {
let mut receiver = conn
.query_event_context_mut(|context: &mut ConfirmContext| context.sender.subscribe())
.expect("connection context isn't properly set");
loop {
match &*receiver.borrow_and_update() {
State::Ready => return Ok(()),
State::Failed(error) => return Err((*error).into()),
State::Waiting(_) => {}
}
if receiver.changed().await.is_err() {
return Err(io::Error::other("never reached terminal state"));
}
}
}
}
pub struct ConfirmContext {
sender: watch::Sender<State>,
}
impl Default for ConfirmContext {
fn default() -> Self {
let (sender, _receiver) = watch::channel(State::default());
Self { sender }
}
}
impl ConfirmContext {
fn update(&mut self, state: State) {
self.sender.send_replace(state);
}
}
impl Drop for ConfirmContext {
fn drop(&mut self) {
self.sender.send_modify(|state| {
if matches!(state, State::Waiting(_)) {
*state = State::Failed(connection::Error::unspecified());
}
});
}
}
enum State {
Waiting(Option<DcState>),
Ready,
Failed(connection::Error),
}
impl Default for State {
fn default() -> Self {
State::Waiting(None)
}
}
impl Subscriber for ConfirmComplete {
type ConnectionContext = ConfirmContext;
#[inline]
fn create_connection_context(
&mut self,
_: &ConnectionMeta,
_info: &ConnectionInfo,
) -> Self::ConnectionContext {
ConfirmContext::default()
}
#[inline]
fn on_connection_closed(
&mut self,
context: &mut Self::ConnectionContext,
meta: &ConnectionMeta,
event: &events::ConnectionClosed,
) {
ensure!(matches!(*context.sender.borrow(), State::Waiting(_)));
let is_ready = matches!(
*context.sender.borrow(),
State::Waiting(Some(DcState::PathSecretsReady { .. }))
);
match (&meta.endpoint_type, event.error, is_ready) {
(EndpointType::Server { .. }, Error::Closed { .. }, true) => {
context.update(State::Ready)
}
_ => context.update(State::Failed(event.error)),
}
}
#[inline]
fn on_dc_state_changed(
&mut self,
context: &mut Self::ConnectionContext,
_meta: &ConnectionMeta,
event: &events::DcStateChanged,
) {
ensure!(matches!(*context.sender.borrow(), State::Waiting(_)));
match event.state {
DcState::NoVersionNegotiated { .. } => context.update(State::Failed(
Error::invalid_configuration("peer does not support specified dc versions"),
)),
DcState::Complete { .. } => {
context.update(State::Ready);
}
_ => {
context.update(State::Waiting(Some(event.state.clone())));
}
}
}
}