Skip to main content

esphome_client/
error.rs

1#![allow(
2    clippy::module_name_repetitions,
3    reason = "Error suffix is for readability"
4)]
5use std::io::Error as StdIoError;
6
7/// Main error type for ESPHome client operations.
8#[derive(Debug, thiserror::Error)]
9pub enum ClientError {
10    /// Connection-related errors.
11    #[error("Connection error: {0}")]
12    Connection(#[from] ConnectionError),
13
14    /// Authentication failed during handshake.
15    #[error("Authentication failed: {reason}")]
16    Authentication {
17        /// Reason why authentication has failed.
18        reason: String,
19    },
20
21    /// Stream-related errors.
22    #[error("Stream error: {0}")]
23    Stream(#[from] StreamError),
24
25    /// Protocol parsing errors.
26    #[error("Protocol error: {0}")]
27    Protocol(#[from] ProtocolError),
28
29    /// Timeout during operation.
30    #[error("Operation timed out after {timeout_ms}ms")]
31    Timeout {
32        /// Duration in milliseconds after which the operation timed out.
33        timeout_ms: u128,
34    },
35
36    /// Configuration error.
37    #[error("Configuration error: {message}")]
38    Configuration {
39        /// Description of the configuration error.
40        message: String,
41    },
42
43    /// Protocol mismatch whilst connecting.
44    #[error("Protocol mismatch: expected {expected}, actual {actual}")]
45    ProtocolMismatch {
46        /// Expected protocol version.
47        expected: String,
48        /// Actual protocol version.
49        actual: String,
50    },
51
52    /// Invalid internal state.
53    #[error("Invalid internal state: {reason}")]
54    InvalidInternalState {
55        /// Reason for the invalid internal state.
56        reason: String,
57    },
58}
59
60/// Connection-specific errors.
61#[derive(Debug, thiserror::Error)]
62pub enum ConnectionError {
63    /// Failed to establish TCP connection.
64    #[error("Failed to connect to {address}: {source}")]
65    TcpConnect {
66        /// Address we attempted to connect to.
67        address: String,
68        /// Source IO error.
69        #[source]
70        source: StdIoError,
71    },
72
73    /// Noise protocol handshake failed.
74    #[error("Noise handshake failed: {reason}")]
75    NoiseHandshake {
76        /// Reason for the handshake failure.
77        reason: String,
78    },
79}
80
81/// Stream-related errors.
82#[derive(Debug, thiserror::Error)]
83pub enum StreamError {
84    /// Invalid frame format received.
85    #[error("Invalid frame format: {reason}")]
86    InvalidFrame {
87        /// Reason why the frame is invalid.
88        reason: String,
89    },
90
91    /// Frame size exceeds maximum allowed size.
92    #[error("Frame too large: {size} bytes (max: {max_size})")]
93    FrameTooLarge {
94        /// Size of the frame.
95        size: usize,
96        /// Maximum allowed size.
97        max_size: usize,
98    },
99
100    /// Failed to read from stream.
101    #[error("Read error: {source}")]
102    Read {
103        /// Source IO error.
104        #[source]
105        source: StdIoError,
106    },
107
108    /// Failed to write to stream.
109    #[error("Write error: {source}")]
110    Write {
111        /// Source IO error.
112        #[source]
113        source: StdIoError,
114    },
115}
116
117/// Protocol-related errors.
118#[derive(Debug, thiserror::Error)]
119pub enum ProtocolError {
120    /// Failed to parse protobuf message.
121    #[error("Protobuf parsing failed: {source}")]
122    ProtobufParse {
123        /// Source decode error.
124        #[source]
125        source: prost::DecodeError,
126    },
127
128    /// Failed to encode protobuf message.
129    #[error("Protobuf encoding failed: {source}")]
130    ProtobufEncode {
131        /// Source encode error.
132        #[source]
133        source: prost::EncodeError,
134    },
135
136    /// Unexpected encryption received.
137    #[error("Unexpected plain data: Device is notusing noise encryption protocol")]
138    UnexpectedPlain,
139
140    /// Unexpected encryption received.
141    #[error("Unexpected encryption: Device is using noise encryption protocol")]
142    UnexpectedEncryption,
143
144    /// Message validation failed.
145    #[error("Message validation failed: {reason}")]
146    ValidationFailed {
147        /// Reason for validation failure.
148        reason: String,
149    },
150}
151
152/// Discovery-related errors.
153#[derive(Debug, thiserror::Error)]
154pub enum DiscoveryError {
155    /// Error during initialization of the discovery client.
156    #[error("Initialization error: {reason}")]
157    InitializationError {
158        /// Reason for the initialization error.
159        reason: String,
160    },
161
162    /// Discovery was aborted, e.g., due to a shutdown signal.
163    #[error("Discovery aborted")]
164    Aborted,
165}
166
167/// Noise protocol specific errors.
168#[derive(Debug, thiserror::Error)]
169pub enum NoiseError {
170    /// Noise handshake state error.
171    #[error("Noise handshake error: {reason}")]
172    Handshake {
173        /// Reason for the handshake error.
174        reason: String,
175    },
176
177    /// Noise transport state error.
178    #[error("Noise transport error: {reason}")]
179    Transport {
180        /// Reason for the transport error.
181        reason: String,
182    },
183
184    /// Invalid noise key format.
185    #[error("Invalid noise key: {reason}")]
186    InvalidKey {
187        /// Reason for the invalid key error.
188        reason: String,
189    },
190
191    /// Noise encryption/decryption failed.
192    #[error("Noise crypto operation failed: {reason}")]
193    CryptoOperation {
194        /// Reason for the crypto operation error.
195        reason: String,
196    },
197}
198
199/// Convert snow errors to `NoiseError`.
200impl From<snow::Error> for NoiseError {
201    fn from(err: snow::Error) -> Self {
202        match err {
203            snow::Error::Init(_) => Self::Handshake {
204                reason: err.to_string(),
205            },
206            snow::Error::Decrypt => Self::CryptoOperation {
207                reason: "Decryption failed".to_owned(),
208            },
209            _ => Self::Transport {
210                reason: err.to_string(),
211            },
212        }
213    }
214}
215
216/// Convert `NoiseError` to `ClientError`.
217impl From<NoiseError> for ClientError {
218    fn from(err: NoiseError) -> Self {
219        Self::Connection(ConnectionError::NoiseHandshake {
220            reason: err.to_string(),
221        })
222    }
223}
224
225/// Convert `prost` errors to `ProtocolError`.
226impl From<prost::DecodeError> for ProtocolError {
227    fn from(err: prost::DecodeError) -> Self {
228        Self::ProtobufParse { source: err }
229    }
230}
231
232impl From<prost::EncodeError> for ProtocolError {
233    fn from(err: prost::EncodeError) -> Self {
234        Self::ProtobufEncode { source: err }
235    }
236}