1use std::net::SocketAddr;
7use thiserror::Error;
8
9use mabi_core::Error as CoreError;
10
11pub type KnxResult<T> = Result<T, KnxError>;
13
14#[derive(Debug, Error)]
16pub enum KnxError {
17 #[error("Invalid group address: {0}")]
22 InvalidGroupAddress(String),
23
24 #[error("Invalid individual address: {0}")]
26 InvalidIndividualAddress(String),
27
28 #[error("Address out of range: {address} (valid: {valid_range})")]
30 AddressOutOfRange { address: String, valid_range: String },
31
32 #[error("Invalid datapoint type: {0}")]
37 InvalidDpt(String),
38
39 #[error("DPT encoding error for {dpt}: {reason}")]
41 DptEncoding { dpt: String, reason: String },
42
43 #[error("DPT decoding error for {dpt}: {reason}")]
45 DptDecoding { dpt: String, reason: String },
46
47 #[error("DPT value out of range: {value} (valid: {valid_range})")]
49 DptValueOutOfRange { value: String, valid_range: String },
50
51 #[error("Frame too short: expected at least {expected} bytes, got {actual}")]
56 FrameTooShort { expected: usize, actual: usize },
57
58 #[error("Invalid frame header: {0}")]
60 InvalidHeader(String),
61
62 #[error("Invalid protocol version: expected {expected:#04x}, got {actual:#04x}")]
64 InvalidProtocolVersion { expected: u8, actual: u8 },
65
66 #[error("Unknown service type: {0:#06x}")]
68 UnknownServiceType(u16),
69
70 #[error("Frame length mismatch: header says {header_length}, actual is {actual_length}")]
72 FrameLengthMismatch {
73 header_length: usize,
74 actual_length: usize,
75 },
76
77 #[error("Invalid HPAI: {0}")]
79 InvalidHpai(String),
80
81 #[error("Unknown cEMI message code: {0:#04x}")]
86 UnknownMessageCode(u8),
87
88 #[error("Invalid cEMI frame: {0}")]
90 InvalidCemi(String),
91
92 #[error("Unknown APCI: {0:#06x}")]
94 UnknownApci(u16),
95
96 #[error("Connection failed to {address}: {reason}")]
101 ConnectionFailed { address: SocketAddr, reason: String },
102
103 #[error("Connection timeout after {timeout_ms}ms")]
105 ConnectionTimeout { timeout_ms: u64 },
106
107 #[error("Connection closed: {0}")]
109 ConnectionClosed(String),
110
111 #[error("No more connections available: maximum {max} reached")]
113 NoMoreConnections { max: usize },
114
115 #[error("Invalid channel ID: {0}")]
117 InvalidChannel(u8),
118
119 #[error("Sequence error: expected {expected}, got {actual}")]
121 SequenceError { expected: u8, actual: u8 },
122
123 #[error("Tunnel connection error: {0}")]
128 TunnelError(String),
129
130 #[error("Tunnel request timeout for channel {channel_id}")]
132 TunnelTimeout { channel_id: u8 },
133
134 #[error("Tunnel ACK error: status {status:#04x}")]
136 TunnelAckError { status: u8 },
137
138 #[error("Group object not found: {0}")]
143 GroupObjectNotFound(String),
144
145 #[error("Write not allowed for group object: {0}")]
147 GroupObjectWriteNotAllowed(String),
148
149 #[error("Read not allowed for group object: {0}")]
151 GroupObjectReadNotAllowed(String),
152
153 #[error("Server error: {0}")]
158 Server(String),
159
160 #[error("Server not running")]
162 ServerNotRunning,
163
164 #[error("Server already running")]
166 ServerAlreadyRunning,
167
168 #[error("Failed to bind to {address}: {reason}")]
170 BindError { address: SocketAddr, reason: String },
171
172 #[error("Configuration error: {0}")]
177 Config(String),
178
179 #[error("Invalid configuration value for '{field}': {reason}")]
181 InvalidConfigValue { field: String, reason: String },
182
183 #[error("I/O error: {0}")]
188 Io(#[from] std::io::Error),
189
190 #[error("Core error: {0}")]
192 Core(#[from] CoreError),
193
194 #[error("Internal error: {0}")]
196 Internal(String),
197}
198
199impl KnxError {
200 pub fn frame_too_short(expected: usize, actual: usize) -> Self {
206 Self::FrameTooShort { expected, actual }
207 }
208
209 pub fn connection_failed(address: SocketAddr, reason: impl Into<String>) -> Self {
211 Self::ConnectionFailed {
212 address,
213 reason: reason.into(),
214 }
215 }
216
217 pub fn dpt_encoding(dpt: impl Into<String>, reason: impl Into<String>) -> Self {
219 Self::DptEncoding {
220 dpt: dpt.into(),
221 reason: reason.into(),
222 }
223 }
224
225 pub fn dpt_decoding(dpt: impl Into<String>, reason: impl Into<String>) -> Self {
227 Self::DptDecoding {
228 dpt: dpt.into(),
229 reason: reason.into(),
230 }
231 }
232
233 pub fn sequence_error(expected: u8, actual: u8) -> Self {
235 Self::SequenceError { expected, actual }
236 }
237
238 pub fn is_recoverable(&self) -> bool {
244 matches!(
245 self,
246 Self::ConnectionTimeout { .. }
247 | Self::TunnelTimeout { .. }
248 | Self::SequenceError { .. }
249 | Self::ConnectionClosed(_)
250 )
251 }
252
253 pub fn is_protocol_error(&self) -> bool {
255 matches!(
256 self,
257 Self::FrameTooShort { .. }
258 | Self::InvalidHeader(_)
259 | Self::InvalidProtocolVersion { .. }
260 | Self::UnknownServiceType(_)
261 | Self::FrameLengthMismatch { .. }
262 | Self::UnknownMessageCode(_)
263 | Self::InvalidCemi(_)
264 | Self::UnknownApci(_)
265 )
266 }
267
268 pub fn is_config_error(&self) -> bool {
270 matches!(self, Self::Config(_) | Self::InvalidConfigValue { .. })
271 }
272}
273
274impl From<KnxError> for CoreError {
275 fn from(err: KnxError) -> Self {
276 CoreError::Protocol(err.to_string())
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn test_error_display() {
286 let err = KnxError::InvalidGroupAddress("invalid".to_string());
287 assert!(err.to_string().contains("Invalid group address"));
288 }
289
290 #[test]
291 fn test_frame_too_short() {
292 let err = KnxError::frame_too_short(10, 5);
293 assert!(err.to_string().contains("10"));
294 assert!(err.to_string().contains("5"));
295 }
296
297 #[test]
298 fn test_is_recoverable() {
299 assert!(KnxError::ConnectionTimeout { timeout_ms: 1000 }.is_recoverable());
300 assert!(!KnxError::InvalidGroupAddress("x".into()).is_recoverable());
301 }
302
303 #[test]
304 fn test_is_protocol_error() {
305 assert!(KnxError::UnknownServiceType(0x1234).is_protocol_error());
306 assert!(!KnxError::Server("test".into()).is_protocol_error());
307 }
308}