use crate::driver::utils::varint_q2w;
use crate::driver::DriverError;
use crate::VarInt;
use std::borrow::Cow;
use std::fmt::Display;
use std::net::SocketAddr;
use wtransport_proto::error::ErrorCode;
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
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),
#[error("CIDs exhausted")]
CidsExhausted,
}
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::ApplicationClosed(close) => Self::ApplicationClosed(close),
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("cannot resolve domain")]
DnsNotFound,
#[error(transparent)]
ConnectionError(ConnectionError),
#[error("server rejected WebTransport session request")]
SessionRejected,
#[error("additional header '{0}' is reserved")]
ReservedHeader(String),
#[error("endpoint stopping")]
EndpointStopping,
#[error("CIDs exhausted")]
CidsExhausted,
#[error("invalid server name: {0}")]
InvalidServerName(String),
#[error("invalid remote address: {0}")]
InvalidRemoteAddress(SocketAddr),
}
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(),
)
}
pub(crate) fn with_connect_error(error: quinn::ConnectError) -> Self {
match error {
quinn::ConnectError::EndpointStopping => ConnectingError::EndpointStopping,
quinn::ConnectError::CidsExhausted => ConnectingError::CidsExhausted,
quinn::ConnectError::InvalidServerName(name) => {
ConnectingError::InvalidServerName(name)
}
quinn::ConnectError::InvalidRemoteAddress(socket_addr) => {
ConnectingError::InvalidRemoteAddress(socket_addr)
}
quinn::ConnectError::NoDefaultClientConfig => {
unreachable!("quic client config is internally provided")
}
quinn::ConnectError::UnsupportedVersion => {
unreachable!("quic version is internally configured")
}
}
}
}
#[derive(thiserror::Error, Debug)]
#[error("closed stream")]
pub struct ClosedStream;
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
pub enum StreamWriteError {
#[error("not connected")]
NotConnected,
#[error("stream closed")]
Closed,
#[error("stream stopped (code: {0})")]
Stopped(VarInt),
#[error("QUIC protocol error")]
QuicProto,
}
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
pub enum StreamReadError {
#[error("not connected")]
NotConnected,
#[error("stream reset (code: {0})")]
Reset(VarInt),
#[error("QUIC protocol error")]
QuicProto,
}
#[derive(thiserror::Error, Debug, Clone)]
pub enum StreamReadExactError {
#[error("stream finished too early ({0} bytes read)")]
FinishedEarly(usize),
#[error(transparent)]
Read(StreamReadError),
}
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
pub enum SendDatagramError {
#[error("not connected")]
NotConnected,
#[error("peer does not support datagrams")]
UnsupportedByPeer,
#[error("datagram payload too large")]
TooLarge,
}
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
pub enum StreamOpeningError {
#[error("not connected")]
NotConnected,
#[error("opening stream refused")]
Refused,
}
#[derive(Debug, Clone, Eq, PartialEq)]
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(())
}
}
impl ApplicationClose {
pub(crate) fn new(code: VarInt, reason: Box<[u8]>) -> Self {
Self { code, reason }
}
pub fn code(&self) -> VarInt {
self.code
}
pub fn reason(&self) -> &[u8] {
&self.reason
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
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, Clone, PartialEq, Eq)]
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: Cow::Borrowed("QUIC protocol version mismatched"),
}),
quinn::ConnectionError::TransportError(e) => {
ConnectionError::QuicProto(QuicProtoError {
code: VarInt::try_from_u64(e.code.into()).ok(),
reason: Cow::Owned(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: Cow::Borrowed("connection has been reset"),
}),
quinn::ConnectionError::TimedOut => ConnectionError::TimedOut,
quinn::ConnectionError::LocallyClosed => ConnectionError::LocallyClosed,
quinn::ConnectionError::CidsExhausted => ConnectionError::CidsExhausted,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct QuicProtoError {
code: Option<VarInt>,
reason: Cow<'static, str>,
}
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))
}
}
#[derive(Debug, thiserror::Error, Clone, Eq, PartialEq)]
#[error("cannot derive keying material as requested output length is too large")]
pub struct ExportKeyingMaterialError;