use std::{borrow::Cow, fmt::Display};
use derive_more::From;
use thiserror::Error;
use crate::{
frame::{ConnectionCloseFrame, FrameType},
varint::VarInt,
};
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ErrorKind {
None,
Internal,
ConnectionRefused,
FlowControl,
StreamLimit,
StreamState,
FinalSize,
FrameEncoding,
TransportParameter,
ConnectionIdLimit,
ProtocolViolation,
InvalidToken,
Application,
CryptoBufferExceeded,
KeyUpdate,
AeadLimitReached,
NoViablePath,
Crypto(u8),
}
impl Display for ErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let description = match self {
ErrorKind::None => "No error",
ErrorKind::Internal => "Implementation error",
ErrorKind::ConnectionRefused => "Server refuses a connection",
ErrorKind::FlowControl => "Flow control error",
ErrorKind::StreamLimit => "Too many streams opened",
ErrorKind::StreamState => "Frame received in invalid stream state",
ErrorKind::FinalSize => "Change to final size",
ErrorKind::FrameEncoding => "Frame encoding error",
ErrorKind::TransportParameter => "Error in transport parameters",
ErrorKind::ConnectionIdLimit => "Too many connection IDs received",
ErrorKind::ProtocolViolation => "Generic protocol violation",
ErrorKind::InvalidToken => "Invalid Token received",
ErrorKind::Application => "Application error",
ErrorKind::CryptoBufferExceeded => "CRYPTO data buffer overflowed",
ErrorKind::KeyUpdate => "Invalid packet protection update",
ErrorKind::AeadLimitReached => "Excessive use of packet protection keys",
ErrorKind::NoViablePath => "No viable network path exists",
ErrorKind::Crypto(x) => return write!(f, "TLS alert code: {x}"),
};
write!(f, "{description}",)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Error)]
#[error("Invalid error code {0}")]
pub struct InvalidErrorKind(u64);
impl TryFrom<VarInt> for ErrorKind {
type Error = InvalidErrorKind;
fn try_from(value: VarInt) -> Result<Self, Self::Error> {
Ok(match value.into_inner() {
0x00 => ErrorKind::None,
0x01 => ErrorKind::Internal,
0x02 => ErrorKind::ConnectionRefused,
0x03 => ErrorKind::FlowControl,
0x04 => ErrorKind::StreamLimit,
0x05 => ErrorKind::StreamState,
0x06 => ErrorKind::FinalSize,
0x07 => ErrorKind::FrameEncoding,
0x08 => ErrorKind::TransportParameter,
0x09 => ErrorKind::ConnectionIdLimit,
0x0a => ErrorKind::ProtocolViolation,
0x0b => ErrorKind::InvalidToken,
0x0c => ErrorKind::Application,
0x0d => ErrorKind::CryptoBufferExceeded,
0x0e => ErrorKind::KeyUpdate,
0x0f => ErrorKind::AeadLimitReached,
0x10 => ErrorKind::NoViablePath,
0x0100..=0x01ff => ErrorKind::Crypto((value.into_inner() & 0xff) as u8),
other => return Err(InvalidErrorKind(other)),
})
}
}
impl From<ErrorKind> for VarInt {
fn from(value: ErrorKind) -> Self {
match value {
ErrorKind::None => VarInt::from(0x00u8),
ErrorKind::Internal => VarInt::from(0x01u8),
ErrorKind::ConnectionRefused => VarInt::from(0x02u8),
ErrorKind::FlowControl => VarInt::from(0x03u8),
ErrorKind::StreamLimit => VarInt::from(0x04u8),
ErrorKind::StreamState => VarInt::from(0x05u8),
ErrorKind::FinalSize => VarInt::from(0x06u8),
ErrorKind::FrameEncoding => VarInt::from(0x07u8),
ErrorKind::TransportParameter => VarInt::from(0x08u8),
ErrorKind::ConnectionIdLimit => VarInt::from(0x09u8),
ErrorKind::ProtocolViolation => VarInt::from(0x0au8),
ErrorKind::InvalidToken => VarInt::from(0x0bu8),
ErrorKind::Application => VarInt::from(0x0cu8),
ErrorKind::CryptoBufferExceeded => VarInt::from(0x0du8),
ErrorKind::KeyUpdate => VarInt::from(0x0eu8),
ErrorKind::AeadLimitReached => VarInt::from(0x0fu8),
ErrorKind::NoViablePath => VarInt::from(0x10u8),
ErrorKind::Crypto(x) => VarInt::from(0x0100u16 | x as u16),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
pub enum ErrorFrameType {
V1(FrameType),
Ext(VarInt),
}
#[derive(Debug, Clone, PartialEq, Eq, Error)]
#[error("{kind} in {frame_type:?}, reason: {reason}")]
pub struct QuicError {
kind: ErrorKind,
frame_type: ErrorFrameType,
reason: Cow<'static, str>,
}
impl QuicError {
pub fn new<T: Into<Cow<'static, str>>>(
kind: ErrorKind,
frame_type: ErrorFrameType,
reason: T,
) -> Self {
Self {
kind,
frame_type,
reason: reason.into(),
}
}
pub fn with_default_fty<T: Into<Cow<'static, str>>>(kind: ErrorKind, reason: T) -> Self {
Self {
kind,
frame_type: FrameType::Padding.into(),
reason: reason.into(),
}
}
pub fn kind(&self) -> ErrorKind {
self.kind
}
pub fn frame_type(&self) -> ErrorFrameType {
self.frame_type
}
pub fn reason(&self) -> &str {
&self.reason
}
}
impl From<FrameType> for ErrorFrameType {
fn from(value: FrameType) -> Self {
Self::V1(value)
}
}
impl From<ErrorFrameType> for VarInt {
fn from(val: ErrorFrameType) -> Self {
match val {
ErrorFrameType::V1(frame) => frame.into(),
ErrorFrameType::Ext(value) => value,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Error)]
#[error("App layer error occur with error code {error_code}, reason: {reason}")]
pub struct AppError {
error_code: VarInt,
reason: Cow<'static, str>,
}
impl AppError {
pub fn new(error_code: VarInt, reason: impl Into<Cow<'static, str>>) -> Self {
Self {
error_code,
reason: reason.into(),
}
}
pub fn error_code(&self) -> u64 {
self.error_code.into_inner()
}
pub fn reason(&self) -> &str {
&self.reason
}
}
#[derive(Debug, Clone, PartialEq, Eq, Error, From)]
pub enum Error {
#[error(transparent)]
Quic(QuicError),
#[error(transparent)]
App(AppError),
}
impl Error {
pub fn kind(&self) -> ErrorKind {
match self {
Error::Quic(e) => e.kind(),
Error::App(_) => ErrorKind::Application,
}
}
pub fn frame_type(&self) -> ErrorFrameType {
match self {
Error::Quic(e) => e.frame_type(),
Error::App(_) => FrameType::Padding.into(),
}
}
}
impl From<Error> for std::io::Error {
fn from(e: Error) -> Self {
Self::new(std::io::ErrorKind::BrokenPipe, e)
}
}
impl From<Error> for ConnectionCloseFrame {
fn from(e: Error) -> Self {
match e {
Error::Quic(e) => Self::new_quic(e.kind, e.frame_type, e.reason),
Error::App(app_error) => Self::new_app(app_error.error_code, app_error.reason),
}
}
}
impl From<AppError> for ConnectionCloseFrame {
fn from(e: AppError) -> Self {
Self::new_app(e.error_code, e.reason)
}
}
impl From<ConnectionCloseFrame> for Error {
fn from(frame: ConnectionCloseFrame) -> Self {
match frame {
ConnectionCloseFrame::Quic(frame) => Self::Quic(QuicError {
kind: frame.error_kind(),
frame_type: frame.frame_type(),
reason: frame.reason().to_owned().into(),
}),
ConnectionCloseFrame::App(frame) => Self::App(AppError {
error_code: VarInt::from_u64(frame.error_code())
.expect("error code never overflow"),
reason: frame.reason().to_owned().into(),
}),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_kind_display() {
assert_eq!(ErrorKind::None.to_string(), "No error");
assert_eq!(ErrorKind::Internal.to_string(), "Implementation error");
assert_eq!(ErrorKind::Crypto(10).to_string(), "TLS alert code: 10");
}
#[test]
fn test_error_kind_conversion() {
assert_eq!(
ErrorKind::try_from(VarInt::from(0x00u8)).unwrap(),
ErrorKind::None
);
assert_eq!(
ErrorKind::try_from(VarInt::from(0x10u8)).unwrap(),
ErrorKind::NoViablePath
);
assert_eq!(
ErrorKind::try_from(VarInt::from(0x0100u16)).unwrap(),
ErrorKind::Crypto(0)
);
assert_eq!(
ErrorKind::try_from(VarInt::from(0x1000u16)).unwrap_err(),
InvalidErrorKind(0x1000)
);
assert_eq!(VarInt::from(ErrorKind::None), VarInt::from(0x00u8));
assert_eq!(VarInt::from(ErrorKind::NoViablePath), VarInt::from(0x10u8));
assert_eq!(VarInt::from(ErrorKind::Crypto(5)), VarInt::from(0x0105u16));
}
#[test]
fn test_error_creation() {
let err = QuicError::new(ErrorKind::Internal, FrameType::Ping.into(), "test error");
assert_eq!(err.kind(), ErrorKind::Internal);
assert_eq!(err.frame_type(), FrameType::Ping.into());
let err = QuicError::with_default_fty(ErrorKind::Application, "default frame type");
assert_eq!(err.frame_type(), FrameType::Padding.into());
}
#[test]
fn test_error_conversion() {
let err = Error::Quic(QuicError::new(
ErrorKind::Internal,
FrameType::Ping.into(),
"test conversion",
));
let frame: ConnectionCloseFrame = err.clone().into();
match frame {
ConnectionCloseFrame::Quic(frame) => {
assert_eq!(frame.error_kind(), err.kind());
assert_eq!(frame.frame_type(), err.frame_type());
}
_ => panic!("unexpected frame type"),
}
let io_err: std::io::Error = err.into();
assert_eq!(io_err.kind(), std::io::ErrorKind::BrokenPipe);
}
}