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 { reason: String },
17
18    /// Stream-related errors.
19    #[error("Stream error: {0}")]
20    Stream(#[from] StreamError),
21
22    /// Protocol parsing errors.
23    #[error("Protocol error: {0}")]
24    Protocol(#[from] ProtocolError),
25
26    /// Timeout during operation.
27    #[error("Operation timed out after {timeout_ms}ms")]
28    Timeout { timeout_ms: u128 },
29
30    /// Configuration error.
31    #[error("Configuration error: {message}")]
32    Configuration { message: String },
33
34    /// Protocol mismatch whilst connecting.
35    #[error("Protocol mismatch: expected {expected}, actual {actual}")]
36    ProtocolMismatch { expected: String, actual: String },
37
38    /// Invalid internal state.
39    #[error("Invalid internal state: {reason}")]
40    InvalidInternalState { reason: String },
41}
42
43/// Connection-specific errors.
44#[derive(Debug, thiserror::Error)]
45pub enum ConnectionError {
46    /// Failed to establish TCP connection.
47    #[error("Failed to connect to {address}: {source}")]
48    TcpConnect {
49        address: String,
50        #[source]
51        source: StdIoError,
52    },
53
54    /// Noise protocol handshake failed.
55    #[error("Noise handshake failed: {reason}")]
56    NoiseHandshake { reason: String },
57}
58
59/// Stream-related errors.
60#[derive(Debug, thiserror::Error)]
61pub enum StreamError {
62    /// Invalid frame format received.
63    #[error("Invalid frame format: {reason}")]
64    InvalidFrame { reason: String },
65
66    /// Frame size exceeds maximum allowed size.
67    #[error("Frame too large: {size} bytes (max: {max_size})")]
68    FrameTooLarge { size: usize, max_size: usize },
69
70    /// Failed to read from stream.
71    #[error("Read error: {source}")]
72    Read {
73        #[source]
74        source: StdIoError,
75    },
76
77    /// Failed to write to stream.
78    #[error("Write error: {source}")]
79    Write {
80        #[source]
81        source: StdIoError,
82    },
83}
84
85/// Protocol-related errors.
86#[derive(Debug, thiserror::Error)]
87pub enum ProtocolError {
88    /// Failed to parse protobuf message.
89    #[error("Protobuf parsing failed: {source}")]
90    ProtobufParse {
91        #[source]
92        source: prost::DecodeError,
93    },
94
95    /// Failed to encode protobuf message.
96    #[error("Protobuf encoding failed: {source}")]
97    ProtobufEncode {
98        #[source]
99        source: prost::EncodeError,
100    },
101
102    /// Unexpected encryption received.
103    #[error("Unexpected plain data: Device is notusing noise encryption protocol")]
104    UnexpectedPlain,
105
106    /// Unexpected encryption received.
107    #[error("Unexpected encryption: Device is using noise encryption protocol")]
108    UnexpectedEncryption,
109
110    /// Message validation failed.
111    #[error("Message validation failed: {reason}")]
112    ValidationFailed { reason: String },
113}
114
115/// Discovery-related errors.
116#[derive(Debug, thiserror::Error)]
117pub enum DiscoveryError {
118    /// Error during initialization of the discovery client.
119    #[error("Initialization error: {reason}")]
120    InitializationError { reason: String },
121
122    /// Discovery was aborted, e.g., due to a shutdown signal.
123    #[error("Discovery aborted")]
124    Aborted,
125}
126
127/// Noise protocol specific errors.
128#[derive(Debug, thiserror::Error)]
129pub enum NoiseError {
130    /// Noise handshake state error.
131    #[error("Noise handshake error: {reason}")]
132    Handshake { reason: String },
133
134    /// Noise transport state error.
135    #[error("Noise transport error: {reason}")]
136    Transport { reason: String },
137
138    /// Invalid noise key format.
139    #[error("Invalid noise key: {reason}")]
140    InvalidKey { reason: String },
141
142    /// Noise encryption/decryption failed.
143    #[error("Noise crypto operation failed: {reason}")]
144    CryptoOperation { reason: String },
145}
146
147/// Convert snow errors to `NoiseError`.
148impl From<snow::Error> for NoiseError {
149    fn from(err: snow::Error) -> Self {
150        match err {
151            snow::Error::Init(_) => Self::Handshake {
152                reason: err.to_string(),
153            },
154            snow::Error::Decrypt => Self::CryptoOperation {
155                reason: "Decryption failed".to_owned(),
156            },
157            _ => Self::Transport {
158                reason: err.to_string(),
159            },
160        }
161    }
162}
163
164/// Convert `NoiseError` to `ClientError`.
165impl From<NoiseError> for ClientError {
166    fn from(err: NoiseError) -> Self {
167        Self::Connection(ConnectionError::NoiseHandshake {
168            reason: err.to_string(),
169        })
170    }
171}
172
173/// Convert `prost` errors to `ProtocolError`.
174impl From<prost::DecodeError> for ProtocolError {
175    fn from(err: prost::DecodeError) -> Self {
176        Self::ProtobufParse { source: err }
177    }
178}
179
180impl From<prost::EncodeError> for ProtocolError {
181    fn from(err: prost::EncodeError) -> Self {
182        Self::ProtobufEncode { source: err }
183    }
184}