use thiserror::Error;
#[cfg(test)]
mod tests;
pub type Result<T> = std::result::Result<T, KnxError>;
#[derive(Debug, Error)]
pub enum KnxError {
#[error("Transport error: {0}")]
Transport(#[from] TransportError),
#[error("Protocol error: {0}")]
Protocol(#[from] ProtocolError),
#[error("Device error: {0}")]
Device(#[from] DeviceError),
#[error("Security error: {0}")]
Security(#[from] SecurityError),
#[error("Configuration error: {0}")]
Configuration(#[from] ConfigurationError),
#[error("Discovery error: {0}")]
Discovery(#[from] DiscoveryError),
#[error("Address error: {0}")]
Address(#[from] crate::protocol::address::AddressError),
}
#[derive(Debug, Error)]
pub enum TransportError {
#[error("Connection failed to {address}: {source}")]
ConnectionFailed {
address: String,
#[source]
source: std::io::Error,
},
#[error("Socket operation failed: {operation} - {source}")]
SocketError {
operation: String,
#[source]
source: std::io::Error,
},
#[error("Connection timeout after {timeout_ms}ms")]
Timeout { timeout_ms: u64 },
#[error("Connection closed unexpectedly")]
ConnectionClosed,
#[error("Invalid connection configuration: {details}")]
InvalidConfiguration { details: String },
#[error("Queue is full, cannot enqueue more telegrams")]
QueueFull,
#[error("Queue is closed, no more operations allowed")]
QueueClosed,
#[error("Queue processing timeout after {timeout_ms}ms")]
QueueTimeout { timeout_ms: u64 },
}
#[derive(Debug, Error)]
pub enum ProtocolError {
#[error("Frame parsing failed at offset {offset}: {reason}")]
ParseError { offset: usize, reason: String },
#[error("Invalid frame format: {details}")]
InvalidFrame { details: String },
#[error("Unsupported protocol version: {version}")]
UnsupportedVersion { version: u8 },
#[error("CEMI error: {message}")]
CemiError { message: String },
#[error("DPT error: {dpt_type} - {details}")]
DptError { dpt_type: String, details: String },
#[error("Invalid address format: {address} - {reason}")]
InvalidAddress { address: String, reason: String },
#[error("Frame is not a telegram: {service_type}")]
NotATelegram { service_type: String },
}
#[derive(Debug, Error)]
pub enum DeviceError {
#[error("Communication timeout for {device} after {timeout_ms}ms")]
CommunicationTimeout { device: String, timeout_ms: u64 },
}
#[derive(Debug, Error)]
pub enum SecurityError {
#[error("Authentication failed: {reason}")]
AuthenticationFailed { reason: String },
#[error("Invalid security credentials: {details}")]
InvalidCredentials { details: String },
#[error("Cryptographic operation failed: {operation} - {reason}")]
CryptographicError { operation: String, reason: String },
#[error("Security session expired")]
SessionExpired,
#[error("Invalid security configuration: {details}")]
InvalidConfiguration { details: String },
}
#[derive(Debug, Error)]
pub enum ConfigurationError {
#[error("Missing required configuration parameter: {parameter}")]
MissingParameter { parameter: String },
#[error("Invalid configuration value for {parameter}: {value} - {reason}")]
InvalidValue {
parameter: String,
value: String,
reason: String,
},
#[error("Configuration file parsing failed: {file} - {reason}")]
ParseError { file: String, reason: String },
#[error("Configuration validation failed: {details}")]
ValidationError { details: String },
}
#[derive(Debug, Error)]
pub enum DiscoveryError {
#[error("No gateways found during discovery")]
NoGatewaysFound,
#[error("Discovery timeout after {timeout_ms}ms")]
Timeout { timeout_ms: u64 },
#[error("Invalid discovery response from {addr}: {reason}")]
InvalidResponse { addr: String, reason: String },
#[error("Network error during discovery: {0}")]
NetworkError(#[from] std::io::Error),
}
impl KnxError {
#[must_use]
pub fn is_recoverable(&self) -> bool {
matches!(
self,
KnxError::Transport(TransportError::Timeout { .. } | TransportError::ConnectionClosed)
| KnxError::Discovery(DiscoveryError::Timeout { .. })
| KnxError::Security(SecurityError::SessionExpired)
)
}
#[must_use]
pub fn category(&self) -> &'static str {
match self {
KnxError::Transport(_) => "transport",
KnxError::Protocol(_) => "protocol",
KnxError::Device(_) => "device",
KnxError::Security(_) => "security",
KnxError::Configuration(_) => "configuration",
KnxError::Discovery(_) => "discovery",
KnxError::Address(_) => "address",
}
}
#[must_use]
pub fn context(&self) -> String {
match self {
KnxError::Transport(e) => format!("Transport layer: {e}"),
KnxError::Protocol(e) => format!("Protocol layer: {e}"),
KnxError::Device(e) => format!("Device layer: {e}"),
KnxError::Security(e) => format!("Security layer: {e}"),
KnxError::Configuration(e) => format!("Configuration: {e}"),
KnxError::Discovery(e) => format!("Discovery: {e}"),
KnxError::Address(e) => format!("Address: {e}"),
}
}
}