dc/s2n-quic-dc/src/stream/recv/error.rs (158 lines of code) (raw):
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use crate::{
credentials,
crypto::open,
packet::{self, stream},
stream::TransportFeatures,
};
use core::{fmt, panic::Location};
use s2n_quic_core::{buffer, frame};
#[derive(Clone, Copy)]
pub struct Error {
kind: Kind,
location: &'static Location<'static>,
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Error")
.field("kind", &self.kind)
.field("crate", &"s2n-quic-dc")
.field("file", &self.file())
.field("line", &self.location.line())
.finish()
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let Self { kind, location } = self;
let file = self.file();
let line = location.line();
write!(f, "[s2n-quic-dc::{file}:{line}]: {kind}")
}
}
impl std::error::Error for Error {}
impl Error {
#[track_caller]
#[inline]
pub fn new(kind: Kind) -> Self {
Self {
kind,
location: Location::caller(),
}
}
#[inline]
pub fn kind(&self) -> &Kind {
&self.kind
}
#[inline]
fn file(&self) -> &'static str {
self.location
.file()
.trim_start_matches(concat!(env!("CARGO_MANIFEST_DIR"), "/src/"))
}
}
impl From<Kind> for Error {
#[track_caller]
#[inline]
fn from(kind: Kind) -> Self {
Self::new(kind)
}
}
#[derive(Clone, Copy, Debug, thiserror::Error)]
pub enum Kind {
#[error("could not decode packet")]
Decode,
#[error("could not decrypt packet: {0}")]
Crypto(open::Error),
#[error("packet has already been processed")]
Duplicate,
#[error("the packet was for another credential ({actual:?}) but was handled by {expected:?}")]
CredentialMismatch {
expected: credentials::Credentials,
actual: credentials::Credentials,
},
#[error("the packet was for another stream ({actual}) but was handled by {expected}")]
StreamMismatch {
expected: stream::Id,
actual: stream::Id,
},
#[error("the stream expected in-order delivery of {expected} but got {actual}")]
OutOfOrder { expected: u64, actual: u64 },
#[error("the peer exceeded the max data window")]
MaxDataExceeded,
#[error("invalid fin")]
InvalidFin,
#[error("out of range")]
OutOfRange,
#[error("unexpected retransmission packet")]
UnexpectedRetransmission,
#[error("the transport has been truncated without authentication")]
TruncatedTransport,
#[error("the receiver idle timer expired")]
IdleTimeout,
#[error("the crypto key has been replayed and is invalid")]
KeyReplayPrevented,
#[error("the crypto key has been potentially replayed (gap: {gap:?}) and is invalid")]
KeyReplayMaybePrevented { gap: Option<u64> },
#[error("application error: {error}")]
ApplicationError {
error: s2n_quic_core::application::Error,
},
#[error("unexpected packet: {packet:?}")]
UnexpectedPacket { packet: packet::Kind },
}
impl Kind {
#[inline]
#[track_caller]
pub(crate) fn err(self) -> Error {
Error::new(self)
}
}
impl From<open::Error> for Error {
#[track_caller]
fn from(value: open::Error) -> Self {
match value {
open::Error::ReplayDefinitelyDetected => Kind::KeyReplayPrevented,
open::Error::ReplayPotentiallyDetected { gap } => Kind::KeyReplayMaybePrevented { gap },
error => Kind::Crypto(error),
}
.err()
}
}
impl Error {
#[inline]
pub(super) fn is_fatal(&self, features: &TransportFeatures) -> bool {
// if the transport is a stream then any error we encounter is fatal, since the stream is
// now likely corrupted
if features.is_stream() {
return true;
}
!matches!(
self.kind(),
Kind::Decode
| Kind::Crypto(_)
| Kind::Duplicate
| Kind::CredentialMismatch { .. }
| Kind::StreamMismatch { .. }
)
}
#[inline]
pub(super) fn connection_close(&self) -> Option<frame::ConnectionClose<'static>> {
use s2n_quic_core::transport;
match self.kind() {
Kind::Decode
| Kind::Crypto(_)
| Kind::Duplicate
| Kind::CredentialMismatch { .. }
| Kind::StreamMismatch { .. }
| Kind::UnexpectedPacket { .. }
| Kind::UnexpectedRetransmission => {
// return protocol violation for the errors that are only fatal for reliable
// transports
Some(transport::Error::PROTOCOL_VIOLATION.into())
}
Kind::IdleTimeout => None,
Kind::MaxDataExceeded => Some(transport::Error::FLOW_CONTROL_ERROR.into()),
Kind::InvalidFin | Kind::TruncatedTransport => {
Some(transport::Error::FINAL_SIZE_ERROR.into())
}
Kind::OutOfOrder { .. } => Some(transport::Error::STREAM_STATE_ERROR.into()),
Kind::OutOfRange => Some(transport::Error::STREAM_LIMIT_ERROR.into()),
// we don't have working crypto keys so we can't respond
Kind::KeyReplayPrevented | Kind::KeyReplayMaybePrevented { .. } => None,
Kind::ApplicationError { error } => Some((*error).into()),
}
}
}
impl From<buffer::Error<Error>> for Error {
#[inline]
#[track_caller]
fn from(value: buffer::Error<Error>) -> Self {
match value {
buffer::Error::OutOfRange => Kind::OutOfRange.err(),
buffer::Error::InvalidFin => Kind::InvalidFin.err(),
buffer::Error::ReaderError(error) => error,
}
}
}
impl From<Error> for std::io::Error {
#[inline]
fn from(error: Error) -> Self {
Self::new(error.kind.into(), error)
}
}
impl From<Kind> for std::io::ErrorKind {
#[inline]
fn from(kind: Kind) -> Self {
use std::io::ErrorKind;
match kind {
Kind::Decode => ErrorKind::InvalidData,
Kind::Crypto(_) => ErrorKind::InvalidData,
Kind::Duplicate => ErrorKind::InvalidData,
Kind::CredentialMismatch { .. } | Kind::StreamMismatch { .. } => ErrorKind::InvalidData,
Kind::MaxDataExceeded => ErrorKind::ConnectionAborted,
Kind::InvalidFin => ErrorKind::InvalidData,
Kind::TruncatedTransport => ErrorKind::UnexpectedEof,
Kind::OutOfRange => ErrorKind::ConnectionAborted,
Kind::OutOfOrder { .. } => ErrorKind::InvalidData,
Kind::UnexpectedRetransmission => ErrorKind::InvalidData,
Kind::IdleTimeout => ErrorKind::TimedOut,
Kind::KeyReplayPrevented => ErrorKind::PermissionDenied,
Kind::KeyReplayMaybePrevented { .. } => ErrorKind::PermissionDenied,
Kind::ApplicationError { .. } => ErrorKind::ConnectionReset,
Kind::UnexpectedPacket {
packet:
packet::Kind::UnknownPathSecret
| packet::Kind::StaleKey
| packet::Kind::ReplayDetected,
} => ErrorKind::ConnectionRefused,
Kind::UnexpectedPacket {
packet: packet::Kind::Stream | packet::Kind::Control | packet::Kind::Datagram,
} => ErrorKind::InvalidData,
}
}
}