use crate::driver::utils::varint_q2w;
use crate::driver::DriverError;
use std::fmt::Display;
use wtransport_proto::error::ErrorCode;
use wtransport_proto::varint::VarInt;
#[derive(thiserror::Error, Debug)]
pub enum ConnectionError {
#[error("Connection aborted by peer: {0}")]
ConnectionClosed(ConnectionClose),
#[error("Connection closed by peer: {0}")]
ApplicationClosed(ApplicationClose),
#[error("Connection locally closed")]
LocallyClosed,
#[error("Connection locally aborted: {0}")]
LocalH3Error(H3Error),
#[error("Connection timed out")]
TimedOut,
#[error("QUIC protocol error: {0}")]
QuicProto(QuicProtoError),
}
impl ConnectionError {
pub(crate) fn with_driver_error(
driver_error: DriverError,
quic_connection: &quinn::Connection,
) -> Self {
match driver_error {
DriverError::Proto(error_code) => Self::local_h3_error(error_code),
DriverError::NotConnected => Self::no_connect(quic_connection),
}
}
pub(crate) fn no_connect(quic_connection: &quinn::Connection) -> Self {
quic_connection
.close_reason()
.expect("QUIC connection is still alive on close-cast")
.into()
}
pub(crate) fn local_h3_error(error_code: ErrorCode) -> Self {
ConnectionError::LocalH3Error(H3Error { code: error_code })
}
}
#[derive(thiserror::Error, Debug)]
pub enum ConnectingError {
#[error("Invalid URL: {0}")]
InvalidUrl(String),
#[error("Cannot resolve domain: {0}")]
DnsLookup(std::io::Error),
#[error("No domain found for dns resolution")]
DnsNotFound,
#[error(transparent)]
ConnectionError(ConnectionError),
#[error("Server rejected WebTransport session request")]
SessionRejected,
#[error("Additional header '{0}' is reserved")]
ReservedHeader(String),
}
impl ConnectingError {
pub(crate) fn with_no_connection(quic_connection: &quinn::Connection) -> Self {
ConnectingError::ConnectionError(
quic_connection
.close_reason()
.expect("QUIC connection is still alive on close-cast")
.into(),
)
}
}
#[derive(thiserror::Error, Debug)]
pub enum StreamWriteError {
#[error("Not connected")]
NotConnected,
#[error("Stream stopped (code: {0})")]
Stopped(VarInt),
#[error("QUIC protocol error")]
QuicProto,
}
#[derive(thiserror::Error, Debug)]
pub enum StreamReadError {
#[error("Not connected")]
NotConnected,
#[error("Stream reset (code: {0})")]
Reset(VarInt),
#[error("QUIC protocol error")]
QuicProto,
}
#[derive(thiserror::Error, Debug)]
pub enum StreamReadExactError {
#[error("Stream finished too early")]
FinishedEarly,
#[error(transparent)]
Read(StreamReadError),
}
#[derive(thiserror::Error, Debug)]
pub enum SendDatagramError {
#[error("Not connected")]
NotConnected,
#[error("Peer does not support datagrams")]
UnsupportedByPeer,
#[error("Datagram payload too large")]
TooLarge,
}
#[derive(thiserror::Error, Debug)]
pub enum StreamOpeningError {
#[error("Not connected")]
NotConnected,
#[error("Opening stream refused")]
Refused,
}
#[derive(Debug)]
pub struct ApplicationClose {
code: VarInt,
reason: Box<[u8]>,
}
impl Display for ApplicationClose {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.reason.is_empty() {
self.code.fmt(f)?;
} else {
f.write_str(&String::from_utf8_lossy(&self.reason))?;
f.write_str(" (code ")?;
self.code.fmt(f)?;
f.write_str(")")?;
}
Ok(())
}
}
#[derive(Debug)]
pub struct ConnectionClose(quinn::ConnectionClose);
impl Display for ConnectionClose {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug)]
pub struct H3Error {
code: ErrorCode,
}
impl Display for H3Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.code.fmt(f)
}
}
impl From<quinn::ConnectionError> for ConnectionError {
fn from(error: quinn::ConnectionError) -> Self {
match error {
quinn::ConnectionError::VersionMismatch => ConnectionError::QuicProto(QuicProtoError {
code: None,
reason: "QUIC protocol version mismatched".to_string(),
}),
quinn::ConnectionError::TransportError(e) => {
ConnectionError::QuicProto(QuicProtoError {
code: VarInt::try_from_u64(e.code.into()).ok(),
reason: e.reason,
})
}
quinn::ConnectionError::ConnectionClosed(close) => {
ConnectionError::ConnectionClosed(ConnectionClose(close))
}
quinn::ConnectionError::ApplicationClosed(close) => {
ConnectionError::ApplicationClosed(ApplicationClose {
code: varint_q2w(close.error_code),
reason: close.reason.to_vec().into_boxed_slice(),
})
}
quinn::ConnectionError::Reset => ConnectionError::QuicProto(QuicProtoError {
code: None,
reason: "Connection has been reset".to_string(),
}),
quinn::ConnectionError::TimedOut => ConnectionError::TimedOut,
quinn::ConnectionError::LocallyClosed => ConnectionError::LocallyClosed,
}
}
}
#[derive(Debug)]
pub struct QuicProtoError {
code: Option<VarInt>,
reason: String,
}
impl Display for QuicProtoError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let code = self
.code
.map(|code| format!(" (code: {})", code))
.unwrap_or_default();
f.write_fmt(format_args!("{}{}", self.reason, code))
}
}