use std::net::IpAddr;
use std::path::PathBuf;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum QuincyError {
#[error("Authentication failed: {0}")]
Auth(#[from] AuthError),
#[error("Configuration error: {0}")]
Config(#[from] ConfigError),
#[error("Network error: {0}")]
Network(#[from] NetworkError),
#[error("Certificate error: {0}")]
Certificate(#[from] CertificateError),
#[error("QUIC protocol error: {0}")]
Quic(#[from] QuicError),
#[error("Interface error: {0}")]
Interface(#[from] InterfaceError),
#[error("DNS error: {0}")]
Dns(#[from] DnsError),
#[error("Routing error: {0}")]
Route(#[from] RouteError),
#[error("Noise protocol error: {0}")]
Noise(#[from] NoiseError),
#[error("Socket error: {0}")]
Socket(#[from] SocketError),
#[error("Metrics error: {0}")]
Metrics(#[from] MetricsError),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("System error: {message}")]
System { message: String },
}
#[derive(Error, Debug)]
pub enum AuthError {
#[error("Handshake rejected")]
HandshakeRejected,
#[error("User unknown")]
UserUnknown,
#[error("Authentication timeout")]
Timeout,
#[error("Authentication store unavailable")]
StoreUnavailable,
#[error("Address pool exhausted")]
AddressPoolExhausted,
#[error("IP assignment failed")]
IpAssignmentFailed,
#[error("Invalid authentication store: {reason}")]
InvalidUserStore { reason: String },
}
#[derive(Error, Debug)]
pub enum ConfigError {
#[error("Configuration file not found: {path}")]
FileNotFound { path: PathBuf },
#[error("Cannot read configuration file: {path}")]
FileNotReadable { path: PathBuf },
#[error("Invalid configuration syntax in file: {path}")]
InvalidSyntax { path: PathBuf },
#[error("Missing required configuration field: {field}")]
MissingField { field: String },
#[error("Invalid value for field '{field}': {reason}")]
InvalidValue { field: String, reason: String },
#[error("Conflicting configuration: {conflict}")]
Conflict { conflict: String },
#[error("Invalid environment variable: {variable}")]
InvalidEnvironmentVariable { variable: String },
#[error("Configuration parsing error: {message}")]
ParseError { message: String },
}
#[derive(Error, Debug)]
pub enum NetworkError {
#[error("Connection failed to {address}")]
ConnectionFailed { address: String },
#[error("Connection closed")]
ConnectionClosed,
#[error("Network operation timed out")]
Timeout,
#[error("Invalid network address: {address}")]
InvalidAddress { address: String },
#[error("Packet processing error: {reason}")]
PacketError { reason: String },
#[error("Network interface unavailable: {interface}")]
InterfaceUnavailable { interface: String },
#[error("Address resolution failed: {hostname}")]
AddressResolution { hostname: String },
#[error("Port binding failed: {port}")]
PortBindFailed { port: u16 },
#[error("Network unreachable")]
NetworkUnreachable,
#[error("MTU exceeded: packet size {size}, limit {limit}")]
MtuExceeded { size: usize, limit: u16 },
}
#[derive(Error, Debug)]
pub enum CertificateError {
#[error("Certificate loading failed: {path}")]
LoadFailed { path: PathBuf },
#[error("Private key loading failed: {path}")]
PrivateKeyLoadFailed { path: PathBuf },
#[error("Certificate validation failed")]
ValidationFailed,
#[error("Certificate has expired")]
Expired,
#[error("Certificate is not yet valid")]
NotYetValid,
#[error("Certificate hostname verification failed")]
HostnameMismatch,
#[error("Invalid certificate chain")]
InvalidChain,
#[error("Unsupported certificate format")]
UnsupportedFormat,
#[error("Untrusted certificate authority")]
UntrustedCa,
#[error("Certificate has been revoked")]
Revoked,
}
#[derive(Error, Debug)]
pub enum QuicError {
#[error("QUIC connection failed: {reason}")]
ConnectionFailed { reason: String },
#[error("QUIC stream error: {reason}")]
StreamError { reason: String },
#[error("QUIC configuration error: {reason}")]
ConfigError { reason: String },
#[error("QUIC transport error: {error_code}")]
TransportError { error_code: u64 },
#[error("QUIC application error: {error_code}")]
ApplicationError { error_code: u64 },
#[error("QUIC connection idle timeout")]
IdleTimeout,
#[error("QUIC endpoint configuration error")]
EndpointError,
#[error("QUIC datagram error: {reason}")]
DatagramError { reason: String },
}
#[derive(Error, Debug)]
pub enum InterfaceError {
#[error("TUN interface creation failed")]
CreationFailed,
#[error("Interface configuration failed: {reason}")]
ConfigurationFailed { reason: String },
#[error("Interface not available: {name}")]
NotAvailable { name: String },
#[error("Insufficient permissions for interface operations")]
PermissionDenied,
#[error("Interface I/O error: {operation}")]
IoError { operation: String },
#[error("MTU configuration failed: requested {requested}, supported {supported}")]
MtuConfigFailed { requested: u16, supported: u16 },
#[error("Interface in invalid state for operation: {state}")]
InvalidState { state: String },
#[error("Platform interface error: {message}")]
PlatformError { message: String },
}
#[derive(Error, Debug)]
pub enum DnsError {
#[error("DNS server configuration failed")]
ConfigurationFailed,
#[error("DNS resolution failed for: {hostname}")]
ResolutionFailed { hostname: String },
#[error("DNS server unreachable: {server}")]
ServerUnreachable { server: IpAddr },
#[error("DNS query timeout")]
QueryTimeout,
#[error("Invalid DNS configuration: {reason}")]
InvalidConfiguration { reason: String },
#[error("DNS configuration backup failed")]
BackupFailed,
#[error("DNS configuration restore failed")]
RestoreFailed,
#[error("Platform DNS error: {message}")]
PlatformError { message: String },
}
#[derive(Error, Debug)]
pub enum RouteError {
#[error("Failed to add route '{destination}': {message}")]
AddFailed {
destination: String,
message: String,
},
#[error("Route removal failed: {destination}")]
RemoveFailed { destination: String },
#[error("Route table query failed")]
QueryFailed,
#[error("Invalid route: {route}")]
InvalidRoute { route: String },
#[error("Route already exists: {destination}")]
AlreadyExists { destination: String },
#[error("Route not found: {destination}")]
NotFound { destination: String },
#[error("Insufficient permissions for routing operations")]
PermissionDenied,
#[error("Platform routing error: {message}")]
PlatformError { message: String },
#[error("Exclusion route required for server {server} but could not be installed")]
ExclusionRequired { server: IpAddr },
}
#[derive(Error, Debug)]
pub enum NoiseError {
#[error("Invalid Noise key: {reason}")]
InvalidKey { reason: String },
#[error("Noise configuration error: {reason}")]
ConfigError { reason: String },
}
#[derive(Error, Debug)]
pub enum SocketError {
#[error("Socket creation failed")]
CreationFailed,
#[error("Socket bind failed: {address}")]
BindFailed { address: String },
#[error("Socket configuration failed: {option}")]
ConfigFailed { option: String },
#[error("Buffer size configuration failed: requested {requested}, actual {actual}")]
BufferSizeFailed { requested: usize, actual: usize },
#[error("Socket in invalid state: {state}")]
InvalidState { state: String },
#[error("Socket operation not supported: {operation}")]
NotSupported { operation: String },
#[error("Address already in use: {address}")]
AddressInUse { address: String },
#[error("Address not available: {address}")]
AddressNotAvailable { address: String },
}
#[derive(Error, Debug)]
pub enum MetricsError {
#[error("Metrics recorder installation failed: {reason}")]
RecorderInstallFailed { reason: String },
}
impl From<quinn::ConnectError> for QuincyError {
fn from(err: quinn::ConnectError) -> Self {
QuincyError::Quic(QuicError::ConnectionFailed {
reason: err.to_string(),
})
}
}
impl From<quinn::ConnectionError> for QuincyError {
fn from(err: quinn::ConnectionError) -> Self {
match err {
quinn::ConnectionError::TimedOut => QuincyError::Quic(QuicError::IdleTimeout),
quinn::ConnectionError::ApplicationClosed(app_err) => {
QuincyError::Quic(QuicError::ApplicationError {
error_code: app_err.error_code.into(),
})
}
quinn::ConnectionError::TransportError(transport_err) => {
QuincyError::Quic(QuicError::TransportError {
error_code: transport_err.code.into(),
})
}
_ => QuincyError::Quic(QuicError::ConnectionFailed {
reason: err.to_string(),
}),
}
}
}
impl From<quinn::WriteError> for QuincyError {
fn from(err: quinn::WriteError) -> Self {
match err {
quinn::WriteError::ConnectionLost(conn_err) => conn_err.into(),
_ => QuincyError::Quic(QuicError::StreamError {
reason: err.to_string(),
}),
}
}
}
impl From<quinn::ReadError> for QuincyError {
fn from(err: quinn::ReadError) -> Self {
match err {
quinn::ReadError::ConnectionLost(conn_err) => conn_err.into(),
_ => QuincyError::Quic(QuicError::StreamError {
reason: err.to_string(),
}),
}
}
}
impl From<rustls::Error> for QuincyError {
fn from(err: rustls::Error) -> Self {
let cert_error = match err {
rustls::Error::InvalidCertificate(_) => CertificateError::ValidationFailed,
rustls::Error::NoCertificatesPresented => CertificateError::InvalidChain,
rustls::Error::UnsupportedNameType => CertificateError::UnsupportedFormat,
rustls::Error::DecryptError => CertificateError::ValidationFailed,
rustls::Error::BadMaxFragmentSize => CertificateError::UnsupportedFormat,
_ => CertificateError::ValidationFailed,
};
QuincyError::Certificate(cert_error)
}
}
impl From<quinn::SendDatagramError> for QuincyError {
fn from(err: quinn::SendDatagramError) -> Self {
match err {
quinn::SendDatagramError::UnsupportedByPeer => {
QuincyError::Quic(QuicError::DatagramError {
reason: "Datagrams not supported by peer".to_string(),
})
}
quinn::SendDatagramError::Disabled => QuincyError::Quic(QuicError::DatagramError {
reason: "Datagrams disabled on connection".to_string(),
}),
quinn::SendDatagramError::TooLarge => QuincyError::Quic(QuicError::DatagramError {
reason: "Datagram too large".to_string(),
}),
quinn::SendDatagramError::ConnectionLost(conn_err) => conn_err.into(),
}
}
}
impl<T> From<tokio::sync::mpsc::error::SendError<T>> for QuincyError {
fn from(_err: tokio::sync::mpsc::error::SendError<T>) -> Self {
QuincyError::system("Channel send failed: receiver dropped")
}
}
impl From<tokio::task::JoinError> for QuincyError {
fn from(err: tokio::task::JoinError) -> Self {
if err.is_cancelled() {
QuincyError::system("Task was cancelled")
} else if err.is_panic() {
QuincyError::system("Task panicked")
} else {
QuincyError::system(format!("Task failed: {err}"))
}
}
}
impl From<serde_json::Error> for QuincyError {
fn from(err: serde_json::Error) -> Self {
QuincyError::system(format!("JSON serialization/deserialization failed: {err}"))
}
}
impl From<tracing::subscriber::SetGlobalDefaultError> for QuincyError {
fn from(err: tracing::subscriber::SetGlobalDefaultError) -> Self {
QuincyError::system(format!("Failed to set global tracing subscriber: {err}"))
}
}
impl From<figment::Error> for QuincyError {
fn from(err: figment::Error) -> Self {
let config_error = if err.path.is_empty() {
ConfigError::ParseError {
message: err.to_string(),
}
} else {
let path = PathBuf::from(err.path.join("."));
match err.kind {
figment::error::Kind::MissingField(field) => ConfigError::MissingField {
field: field.to_string(),
},
figment::error::Kind::InvalidType(_, _) => ConfigError::InvalidValue {
field: path.to_string_lossy().to_string(),
reason: "invalid type".to_string(),
},
figment::error::Kind::InvalidLength(_, _) => ConfigError::InvalidValue {
field: path.to_string_lossy().to_string(),
reason: "invalid length".to_string(),
},
figment::error::Kind::UnknownVariant(_, _) => ConfigError::InvalidValue {
field: path.to_string_lossy().to_string(),
reason: "unknown variant".to_string(),
},
figment::error::Kind::UnknownField(..) => ConfigError::InvalidValue {
field: path.to_string_lossy().to_string(),
reason: "unknown field".to_string(),
},
figment::error::Kind::UnsupportedKey(..) => ConfigError::InvalidValue {
field: path.to_string_lossy().to_string(),
reason: "unsupported key".to_string(),
},
figment::error::Kind::ISizeOutOfRange(_) => ConfigError::InvalidValue {
field: path.to_string_lossy().to_string(),
reason: "integer out of range".to_string(),
},
figment::error::Kind::Unsupported(_) => ConfigError::InvalidValue {
field: path.to_string_lossy().to_string(),
reason: "unsupported value".to_string(),
},
figment::error::Kind::Message(_) => ConfigError::ParseError {
message: err.to_string(),
},
figment::error::Kind::InvalidValue(_, _) => ConfigError::InvalidValue {
field: path.to_string_lossy().to_string(),
reason: "invalid value".to_string(),
},
figment::error::Kind::DuplicateField(_) => ConfigError::InvalidValue {
field: path.to_string_lossy().to_string(),
reason: "duplicate field".to_string(),
},
figment::error::Kind::USizeOutOfRange(_) => ConfigError::InvalidValue {
field: path.to_string_lossy().to_string(),
reason: "integer out of range".to_string(),
},
}
};
QuincyError::Config(config_error)
}
}
impl QuincyError {
pub fn system(message: impl Into<String>) -> Self {
QuincyError::System {
message: message.into(),
}
}
pub fn connection_failed(address: impl Into<String>) -> Self {
QuincyError::Network(NetworkError::ConnectionFailed {
address: address.into(),
})
}
pub fn config_file_not_found(path: impl Into<PathBuf>) -> Self {
QuincyError::Config(ConfigError::FileNotFound { path: path.into() })
}
}
pub type Result<T> = std::result::Result<T, QuincyError>;