#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ConnectError {
#[error("a connection to this static already exists")]
AlreadyConnected,
#[error("the initial connect gave up after HANDSHAKE_GIVEUP")]
TimedOut,
#[error("our own identity provider failed to open")]
Local,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum IntroError {
#[error("the parked introduction is no longer queued")]
Expired,
#[error("the parked introduction was evicted under intro-queue pressure")]
Evicted,
#[error("the initiation belonged to a pending outbound dial and was consumed")]
Internal,
#[error("the introduction's msg1 is structurally unreadable")]
Malformed,
#[error("our own identity provider failed to open")]
Local,
#[error("the endpoint driver stopped")]
EndpointDropped,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum AuthError {
#[error("the timestamp guard rejected this initiation as a replay")]
Replay,
#[error("the handshake failed to authenticate")]
HandshakeFailed,
#[error("the parked introduction is no longer queued")]
Expired,
#[error("our own identity provider failed to open")]
Local,
#[error("the endpoint driver stopped")]
EndpointDropped,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum AcceptError {
#[error("no initiation is parked for this static, or it fails the replacement basis")]
Stale,
#[error("the endpoint driver stopped")]
EndpointDropped,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ConnectionLost {
#[error("no authenticated packet arrived for DEAD_TIMEOUT")]
TimedOut,
#[error("the send counter is exhausted")]
NonceExhausted,
#[error("closed by this application")]
LocallyClosed,
#[error("closed by the peer: code {code}")]
PeerClosed {
code: u64,
reason: Vec<u8>,
},
#[error("torn down after a protocol violation: code {code}")]
ProtocolViolation {
code: u64,
},
#[error("replaced by a newer connection from the same static")]
Replaced,
#[error("the endpoint driver stopped")]
EndpointDropped,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum WriteError {
#[error("the stream was reset: code {0}")]
Reset(u64),
#[error(transparent)]
ConnectionLost(#[from] ConnectionLost),
#[error("write after finish")]
Finished,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ReadError {
#[error("the peer reset the stream: code {0}")]
Reset(u64),
#[error(transparent)]
ConnectionLost(#[from] ConnectionLost),
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum MessageError {
#[error("the message exceeds MESSAGE_RECV_MAX")]
TooLarge,
#[error(transparent)]
ConnectionLost(#[from] ConnectionLost),
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum DatagramError {
#[error("the datagram exceeds MAX_DATAGRAM_PAYLOAD")]
TooLarge,
#[error(transparent)]
ConnectionLost(#[from] ConnectionLost),
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ConfigError {
#[error("the persistent-keepalive interval is below the 1 s floor")]
KeepaliveTooShort,
#[error("the persistent-keepalive interval is at or above DEAD_TIMEOUT")]
KeepaliveTooLong,
#[error("a flow-control window below §10.2's ratified initial value")]
WindowTooSmall,
#[error("a flow-control window above VarInt::MAX_VALUE (2^62 - 1)")]
WindowTooLarge,
#[error("the stream window exceeds the connection window")]
StreamWindowAboveConnection,
}
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error as _;
fn _assert_clone<T: Clone>() {}
fn _assert_send_sync<T: Send + Sync + 'static>() {}
#[test]
fn write_error_is_exhaustive_in_crate() {
fn fence(e: WriteError) {
match e {
WriteError::Reset(code) => {
let _: u64 = code;
}
WriteError::ConnectionLost(inner) => {
let _: ConnectionLost = inner;
}
WriteError::Finished => {}
}
}
fence(WriteError::Reset(0));
fence(WriteError::ConnectionLost(ConnectionLost::TimedOut));
fence(WriteError::Finished);
}
#[test]
fn connection_lost_is_clone() {
_assert_clone::<ConnectionLost>();
_assert_clone::<WriteError>();
_assert_clone::<ReadError>();
_assert_clone::<MessageError>();
_assert_clone::<DatagramError>();
let lost = ConnectionLost::PeerClosed {
code: 0x10,
reason: b"goodbye".to_vec(),
};
assert_eq!(lost.clone(), lost);
}
#[test]
fn error_types_are_send_and_sync() {
_assert_send_sync::<ConnectError>();
_assert_send_sync::<IntroError>();
_assert_send_sync::<AuthError>();
_assert_send_sync::<AcceptError>();
_assert_send_sync::<ConnectionLost>();
_assert_send_sync::<WriteError>();
_assert_send_sync::<ReadError>();
_assert_send_sync::<MessageError>();
_assert_send_sync::<DatagramError>();
_assert_send_sync::<ConfigError>();
}
fn every_variant() -> Vec<Box<dyn std::error::Error>> {
vec![
Box::new(ConnectError::AlreadyConnected),
Box::new(ConnectError::TimedOut),
Box::new(ConnectError::Local),
Box::new(IntroError::Expired),
Box::new(IntroError::Evicted),
Box::new(IntroError::Internal),
Box::new(IntroError::Malformed),
Box::new(IntroError::Local),
Box::new(IntroError::EndpointDropped),
Box::new(AuthError::Replay),
Box::new(AuthError::HandshakeFailed),
Box::new(AuthError::Expired),
Box::new(AuthError::Local),
Box::new(AuthError::EndpointDropped),
Box::new(AcceptError::Stale),
Box::new(AcceptError::EndpointDropped),
Box::new(ConnectionLost::TimedOut),
Box::new(ConnectionLost::NonceExhausted),
Box::new(ConnectionLost::LocallyClosed),
Box::new(ConnectionLost::PeerClosed {
code: 0x10,
reason: b"bye".to_vec(),
}),
Box::new(ConnectionLost::ProtocolViolation { code: 0x01 }),
Box::new(ConnectionLost::Replaced),
Box::new(ConnectionLost::EndpointDropped),
Box::new(WriteError::Reset(7)),
Box::new(WriteError::ConnectionLost(ConnectionLost::TimedOut)),
Box::new(WriteError::Finished),
Box::new(ReadError::Reset(7)),
Box::new(ReadError::ConnectionLost(ConnectionLost::TimedOut)),
Box::new(MessageError::TooLarge),
Box::new(MessageError::ConnectionLost(ConnectionLost::TimedOut)),
Box::new(DatagramError::TooLarge),
Box::new(DatagramError::ConnectionLost(ConnectionLost::TimedOut)),
Box::new(ConfigError::KeepaliveTooShort),
Box::new(ConfigError::KeepaliveTooLong),
Box::new(ConfigError::WindowTooSmall),
Box::new(ConfigError::WindowTooLarge),
Box::new(ConfigError::StreamWindowAboveConnection),
]
}
#[test]
fn display_strings_are_non_empty_and_lowercase_initial() {
for e in every_variant() {
let s = e.to_string();
assert!(!s.is_empty(), "empty display string for {e:?}");
assert!(
!s.ends_with('.'),
"display string ends in a full stop: {s:?}"
);
let first = s.chars().next().expect("non-empty");
assert!(
!first.is_uppercase(),
"display string starts upper-case: {s:?}"
);
}
}
#[test]
fn embedded_connection_lost_converts() {
fn w() -> Result<(), WriteError> {
Err(ConnectionLost::TimedOut)?
}
fn r() -> Result<(), ReadError> {
Err(ConnectionLost::TimedOut)?
}
fn m() -> Result<(), MessageError> {
Err(ConnectionLost::TimedOut)?
}
fn d() -> Result<(), DatagramError> {
Err(ConnectionLost::TimedOut)?
}
assert_eq!(
w().unwrap_err(),
WriteError::ConnectionLost(ConnectionLost::TimedOut)
);
assert_eq!(
r().unwrap_err(),
ReadError::ConnectionLost(ConnectionLost::TimedOut)
);
assert_eq!(
m().unwrap_err(),
MessageError::ConnectionLost(ConnectionLost::TimedOut)
);
assert_eq!(
d().unwrap_err(),
DatagramError::ConnectionLost(ConnectionLost::TimedOut)
);
let inner = ConnectionLost::TimedOut.to_string();
assert_eq!(w().unwrap_err().to_string(), inner);
assert_eq!(r().unwrap_err().to_string(), inner);
assert_eq!(m().unwrap_err().to_string(), inner);
assert_eq!(d().unwrap_err().to_string(), inner);
assert!(ConnectionLost::TimedOut.source().is_none());
assert!(w().unwrap_err().source().is_none());
assert!(WriteError::Finished.source().is_none());
}
}