use std::{collections::HashMap, fmt, sync::Arc};
use futures::future::BoxFuture;
use snafu::ensure;
use tokio::sync::mpsc;
use super::{
error::{AlreadyRegisteredSnafu, RegisterError},
session::{RoutedBiStream, RoutedUniStream, WebTransportSession},
};
use crate::{
codec::{
BoxReadStream, BoxWriteStream, DecodeExt, ErasedPeekableBiStream, ErasedPeekableUniStream,
},
connection::StreamError,
protocol::{ProductProtocol, Protocol, Protocols, StreamVerdict},
quic::{self, ConnectionError},
varint::VarInt,
};
pub const WT_BIDI_SIGNAL: VarInt = VarInt::from_u32(0x41);
pub const WT_UNI_SIGNAL: VarInt = VarInt::from_u32(0x54);
pub(super) type Registry = Arc<std::sync::Mutex<HashMap<VarInt, SessionStreamRouter>>>;
pub(super) struct SessionStreamRouter {
bidi_tx: mpsc::Sender<RoutedBiStream>,
uni_tx: mpsc::Sender<RoutedUniStream>,
}
impl SessionStreamRouter {
fn new(bidi_tx: mpsc::Sender<RoutedBiStream>, uni_tx: mpsc::Sender<RoutedUniStream>) -> Self {
Self { bidi_tx, uni_tx }
}
fn route_bi(&self, session_id: VarInt, stream: RoutedBiStream) {
if self.bidi_tx.try_send(stream).is_err() {
tracing::debug!(
?session_id,
"session bidi channel full or closed, dropping stream"
);
}
}
fn route_uni(&self, session_id: VarInt, stream: RoutedUniStream) {
if self.uni_tx.try_send(stream).is_err() {
tracing::debug!(
?session_id,
"session uni channel full or closed, dropping stream"
);
}
}
}
pub struct WebTransportProtocol {
registry: Registry,
conn: Arc<dyn quic::DynManageStream>,
}
impl fmt::Debug for WebTransportProtocol {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("WebTransportProtocol")
.field(
"sessions",
&self.registry.lock().map(|r| r.len()).unwrap_or(0),
)
.finish()
}
}
impl WebTransportProtocol {
pub fn register(&self, session_id: VarInt) -> Result<WebTransportSession, RegisterError> {
let (bidi_tx, bidi_rx) = mpsc::channel(16);
let (uni_tx, uni_rx) = mpsc::channel(16);
let mut registry = self
.registry
.lock()
.map_err(|_| RegisterError::RegistryPoisoned)?;
ensure!(
!registry.contains_key(&session_id),
AlreadyRegisteredSnafu {
session_id: crate::stream_id::StreamId::from(session_id),
}
);
registry.insert(session_id, SessionStreamRouter::new(bidi_tx, uni_tx));
Ok(WebTransportSession::new(
session_id,
bidi_rx,
uni_rx,
Arc::clone(&self.conn),
Arc::clone(&self.registry),
))
}
}
impl WebTransportProtocol {
async fn accept_bi_inner(
&self,
(mut reader, writer): ErasedPeekableBiStream,
) -> Result<StreamVerdict<ErasedPeekableBiStream>, StreamError> {
let Ok(signal_value) = reader.decode_one::<VarInt>().await else {
return Ok(StreamVerdict::Passed((reader, writer)));
};
if signal_value != WT_BIDI_SIGNAL {
return Ok(StreamVerdict::Passed((reader, writer)));
}
let Ok(session_id) = reader.decode_one::<VarInt>().await else {
tracing::debug!("failed to decode session ID from webtransport bidi stream");
return Ok(StreamVerdict::Accepted);
};
tracing::debug!(?session_id, "routing webtransport bidi stream to session");
let reader: BoxReadStream = Box::pin(reader.into_stream_reader());
let writer: BoxWriteStream = writer.into_inner();
let Ok(registry) = self.registry.lock() else {
tracing::debug!("webtransport session registry lock poisoned");
return Ok(StreamVerdict::Accepted);
};
if let Some(router) = registry.get(&session_id) {
router.route_bi(session_id, (reader, writer));
} else {
tracing::debug!(
?session_id,
"no registered session for webtransport bidi stream"
);
}
Ok(StreamVerdict::Accepted)
}
async fn accept_uni_inner(
&self,
mut stream: ErasedPeekableUniStream,
) -> Result<StreamVerdict<ErasedPeekableUniStream>, StreamError> {
let Ok(signal_value) = stream.decode_one::<VarInt>().await else {
return Ok(StreamVerdict::Passed(stream));
};
if signal_value != WT_UNI_SIGNAL {
return Ok(StreamVerdict::Passed(stream));
}
let Ok(session_id) = stream.decode_one::<VarInt>().await else {
tracing::debug!("failed to decode session ID from webtransport uni stream");
return Ok(StreamVerdict::Accepted);
};
tracing::debug!(?session_id, "routing webtransport uni stream to session");
let reader: BoxReadStream = Box::pin(stream.into_stream_reader());
let Ok(registry) = self.registry.lock() else {
tracing::debug!("webtransport session registry lock poisoned");
return Ok(StreamVerdict::Accepted);
};
if let Some(router) = registry.get(&session_id) {
router.route_uni(session_id, reader);
} else {
tracing::debug!(
?session_id,
"no registered session for webtransport uni stream"
);
}
Ok(StreamVerdict::Accepted)
}
}
impl Protocol for WebTransportProtocol {
fn accept_uni<'a>(
&'a self,
stream: ErasedPeekableUniStream,
) -> BoxFuture<'a, Result<StreamVerdict<ErasedPeekableUniStream>, StreamError>> {
Box::pin(self.accept_uni_inner(stream))
}
fn accept_bi<'a>(
&'a self,
stream: ErasedPeekableBiStream,
) -> BoxFuture<'a, Result<StreamVerdict<ErasedPeekableBiStream>, StreamError>> {
Box::pin(self.accept_bi_inner(stream))
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct WebTransportProtocolFactory;
impl fmt::Display for WebTransportProtocolFactory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "WebTransport")
}
}
impl<C: quic::Connection> ProductProtocol<C> for WebTransportProtocolFactory {
type Protocol = WebTransportProtocol;
fn init<'a>(
&'a self,
conn: &'a Arc<C>,
_layers: &'a Protocols,
) -> BoxFuture<'a, Result<Self::Protocol, ConnectionError>> {
let conn: Arc<dyn quic::DynManageStream> = conn.clone();
Box::pin(async move {
Ok(WebTransportProtocol {
registry: Arc::new(std::sync::Mutex::new(HashMap::new())),
conn,
})
})
}
}
#[cfg(test)]
mod tests {
use super::*;
const fn assert_send_sync<T: Send + Sync>() {}
const _: () = assert_send_sync::<WebTransportProtocol>();
#[test]
fn signal_values_are_correct() {
assert_eq!(WT_BIDI_SIGNAL.into_inner(), 0x41);
assert_eq!(WT_UNI_SIGNAL.into_inner(), 0x54);
}
}