Skip to main content

irosh/
error.rs

1//! Top-level and subsystem error types for the irosh library.
2
3use std::path::PathBuf;
4
5/// Transport-layer errors.
6#[cfg(feature = "transport")]
7#[derive(Debug, thiserror::Error)]
8pub enum TransportError {
9    /// Binding a local endpoint failed.
10    #[error("failed to bind transport endpoint")]
11    EndpointBind {
12        #[source]
13        source: iroh::endpoint::BindError,
14    },
15
16    /// The P2P connection was lost or refused.
17    #[error("transport connection lost: {source}")]
18    ConnectionLost {
19        #[source]
20        source: iroh::endpoint::ConnectionError,
21    },
22
23    /// Metadata framing or parsing failed.
24    #[error(transparent)]
25    Metadata(#[from] crate::transport::metadata::MetadataError),
26
27    /// Transfer framing or parsing failed.
28    #[error(transparent)]
29    Transfer(#[from] crate::transport::transfer::TransferError),
30
31    /// The provided connection ticket has an invalid format.
32    #[error("invalid connection ticket format")]
33    TicketFormatInvalid,
34
35    /// The provided relay URL is invalid.
36    #[error("invalid relay URL: {url}")]
37    InvalidRelayUrl { url: String },
38
39    /// A general protocol violation or unexpected message sequence.
40    #[error("protocol violation: {details}")]
41    ProtocolError { details: String },
42}
43
44/// Storage and persistence errors.
45#[cfg(feature = "storage")]
46#[derive(Debug, thiserror::Error)]
47pub enum StorageError {
48    #[error("failed to create directory at {path}")]
49    DirectoryCreate {
50        path: PathBuf,
51        #[source]
52        source: std::io::Error,
53    },
54
55    #[error("failed to read directory at {path}")]
56    DirectoryRead {
57        path: PathBuf,
58        #[source]
59        source: std::io::Error,
60    },
61
62    #[error("failed to read entry in directory {path}")]
63    DirectoryEntryRead {
64        path: PathBuf,
65        #[source]
66        source: std::io::Error,
67    },
68
69    #[error("failed to read file at {path}")]
70    FileRead {
71        path: PathBuf,
72        #[source]
73        source: std::io::Error,
74    },
75
76    #[error("failed to write file at {path}")]
77    FileWrite {
78        path: PathBuf,
79        #[source]
80        source: std::io::Error,
81    },
82
83    #[error("failed to delete file at {path}")]
84    FileDelete {
85        path: PathBuf,
86        #[source]
87        source: std::io::Error,
88    },
89
90    #[error("peer '{alias}' not found in storage")]
91    PeerNotFound { alias: String },
92
93    #[error("failed to parse connection ticket")]
94    TicketParse {
95        #[source]
96        source: crate::transport::ticket::TicketError,
97    },
98
99    #[error("failed to load or generate local identity")]
100    IdentityLoad {
101        #[source]
102        source: iroh::endpoint::TransportError,
103    },
104
105    #[error("failed to parse SSH public key")]
106    PublicKeyParse {
107        #[source]
108        source: russh::keys::ssh_key::Error,
109    },
110
111    #[error("failed to read SSH public key file at {path}")]
112    PublicKeyRead {
113        path: PathBuf,
114        #[source]
115        source: russh::keys::ssh_key::Error,
116    },
117
118    #[error("failed to write SSH public key")]
119    PublicKeyWrite {
120        path: PathBuf,
121        #[source]
122        source: russh::keys::ssh_key::Error,
123    },
124
125    #[error("failed to format public key")]
126    PublicKeyFormat {
127        #[source]
128        source: russh::keys::ssh_key::Error,
129    },
130
131    #[error("blocking storage task failed during {operation}")]
132    BlockingTaskFailed {
133        operation: &'static str,
134        #[source]
135        source: tokio::task::JoinError,
136    },
137
138    #[error("invalid node secret at {path}: {details}")]
139    NodeSecretInvalid {
140        path: PathBuf,
141        details: String,
142        #[source]
143        source: Box<dyn std::error::Error + Send + Sync>,
144    },
145
146    #[error("invalid peer name: {name}")]
147    PeerNameInvalid { name: String },
148
149    #[error("failed to serialize peer profile")]
150    PeerProfileSerialize {
151        #[source]
152        source: serde_json::Error,
153    },
154
155    #[error("failed to parse peer profile")]
156    PeerProfileParse {
157        #[source]
158        source: serde_json::Error,
159    },
160
161    #[error("failed to hash password: {reason}")]
162    PasswordHash {
163        /// The underlying error from the argon2 crate.
164        ///
165        /// NOTE: This does not use `#[source]` because `argon2::password_hash::Error`
166        /// does not currently implement `std::error::Error`.
167        reason: argon2::password_hash::Error,
168    },
169}
170
171/// Authentication and credential errors.
172#[derive(Debug, thiserror::Error)]
173pub enum AuthError {
174    /// Password verification failed due to an incorrect password.
175    #[error("invalid password provided")]
176    InvalidPassword,
177
178    /// Password verification failed due to a cryptographic or format error.
179    #[error("password verification failed: {reason}")]
180    VerificationFailed {
181        /// The underlying error from the argon2 crate.
182        ///
183        /// NOTE: This does not use `#[source]` because `argon2::password_hash::Error`
184        /// does not currently implement `std::error::Error`.
185        reason: argon2::password_hash::Error,
186    },
187
188    /// The required authentication method is not supported by the client or server.
189    #[error("unsupported authentication method: {0}")]
190    UnsupportedMethod(String),
191
192    /// A required credential (like a password) was not provided.
193    #[error("missing required credential: {0}")]
194    MissingCredential(String),
195}
196
197/// Client-side session and lifecycle errors.
198#[derive(Debug, thiserror::Error)]
199pub enum ClientError {
200    /// The P2P connection to the target peer failed.
201    #[error("failed to connect to P2P endpoint")]
202    ConnectFailed {
203        #[source]
204        source: iroh::endpoint::ConnectError,
205    },
206
207    /// Opening a bi-directional stream for SSH failed.
208    #[error("failed to open SSH transport stream")]
209    StreamOpenFailed {
210        #[source]
211        source: iroh::endpoint::ConnectionError,
212    },
213
214    /// A metadata-related operation failed.
215    #[error("metadata request failed: {detail}")]
216    MetadataFailed { detail: String },
217
218    /// Negotiating the SSH protocol failed.
219    #[error("failed to negotiate SSH protocol")]
220    SshNegotiationFailed {
221        #[source]
222        source: russh::Error,
223    },
224
225    /// The SSH session channel could not be opened.
226    #[error("failed to open SSH session channel")]
227    ChannelOpenFailed {
228        #[source]
229        source: russh::Error,
230    },
231
232    /// Requesting a PTY failed.
233    #[error("failed to request PTY")]
234    PtyRequestFailed {
235        #[source]
236        source: russh::Error,
237    },
238
239    /// Requesting a shell session failed.
240    #[error("failed to request shell")]
241    ShellRequestFailed {
242        #[source]
243        source: russh::Error,
244    },
245
246    /// A command failed to execute.
247    #[error("remote command execution failed")]
248    ExecFailed {
249        #[source]
250        source: russh::Error,
251    },
252
253    /// Sending data over the SSH channel failed.
254    #[error("failed to send data to remote channel")]
255    DataSendFailed {
256        #[source]
257        source: russh::Error,
258    },
259
260    /// Sending EOF over the SSH channel failed.
261    #[error("failed to send EOF")]
262    EofSendFailed {
263        #[source]
264        source: russh::Error,
265    },
266
267    /// Resizing the PTY window failed.
268    #[error("failed to resize PTY window")]
269    WindowChangeFailed {
270        #[source]
271        source: russh::Error,
272    },
273
274    /// Disconnecting the SSH session failed.
275    #[error("failed to disconnect SSH session")]
276    DisconnectFailed {
277        #[source]
278        source: russh::Error,
279    },
280
281    /// Standard I/O failure on the local terminal.
282    #[error("terminal I/O error")]
283    TerminalIo {
284        #[source]
285        source: std::io::Error,
286    },
287
288    /// The SSH peer disconnected abruptly during the initial handshake.
289    #[error("SSH peer disconnected during handshake")]
290    SshHandshakeDisconnected { detail: Option<String> },
291
292    /// A file upload operation failed.
293    #[error("upload failed: {details}")]
294    UploadFailed { details: String },
295
296    /// A file download operation failed.
297    #[error("download failed: {details}")]
298    DownloadFailed { details: String },
299
300    /// A file-level I/O operation failed.
301    #[error("failed to {operation} at {path}")]
302    FileIo {
303        operation: &'static str,
304        path: PathBuf,
305        #[source]
306        source: std::io::Error,
307    },
308
309    /// The transfer target identifier or path is invalid.
310    #[error("invalid transfer target: {reason}")]
311    TransferTargetInvalid { reason: &'static str },
312
313    /// The remote peer rejected the transfer request.
314    #[error("transfer rejected by remote: {details}")]
315    TransferRejected { details: String },
316
317    /// A transfer-related control operation failed.
318    #[error("transfer control operation failed: {details}")]
319    TransferFailed { details: String },
320
321    /// The session transport is not available (disconnected or not initialized).
322    #[error("transport unavailable: {details}")]
323    TransportUnavailable { details: &'static str },
324
325    /// A port forwarding tunnel failed.
326    #[error("tunnel failed: {details}")]
327    TunnelFailed { details: String },
328}
329
330/// Server-side orchestration errors.
331#[derive(Debug, thiserror::Error)]
332pub enum ServerError {
333    /// The Iroh endpoint failed to bind.
334    #[error("failed to bind server endpoint")]
335    EndpointBind {
336        #[source]
337        source: iroh::endpoint::BindError,
338    },
339
340    /// Identity loading or generation failed.
341    #[error("failed to load server identity")]
342    IdentityLoad {
343        #[source]
344        source: iroh::endpoint::TransportError,
345    },
346
347    /// SSH server configuration failed.
348    #[error("failed to configure SSH server")]
349    SshConfig {
350        #[source]
351        source: russh::keys::ssh_key::Error,
352    },
353
354    /// A shell process failed to start or manage.
355    #[error("remote shell error: {details}")]
356    ShellError { details: String },
357
358    /// A channel-level SSH operation failed.
359    #[error("channel error during {operation}: {details}")]
360    ChannelError {
361        operation: &'static str,
362        details: String,
363    },
364
365    /// A file transfer operation failed on the server.
366    #[error("server transfer error: {details}")]
367    TransferFailed { details: String },
368
369    /// The remote peer provided an invalid transfer path.
370    #[error("invalid transfer path: {details}")]
371    InvalidPath { details: String },
372
373    #[error("failed to format host key")]
374    FormatHostKey {
375        #[source]
376        source: russh::keys::ssh_key::Error,
377    },
378
379    #[error("blocking storage task failed during {operation}")]
380    BlockingTaskFailed {
381        operation: &'static str,
382        #[source]
383        source: tokio::task::JoinError,
384    },
385
386    #[error("failed to query process information for PID {pid}: {details}")]
387    ProcessQueryFailed {
388        pid: u32,
389        details: String,
390        #[source]
391        source: std::io::Error,
392    },
393
394    /// Failure during OS service management (install/start/stop).
395    #[error("Service management failure: {details}")]
396    ServiceManagement { details: String },
397}
398
399/// Top-level crate error unifying all subsystem failures.
400#[derive(Debug, thiserror::Error)]
401pub enum IroshError {
402    #[error("platform not supported: {0}")]
403    PlatformNotSupported(String),
404
405    /// Errors originating from the Iroh transport layer.
406    #[cfg(feature = "transport")]
407    #[error("transport error: {0}")]
408    Transport(#[from] TransportError),
409
410    /// Errors originating from the storage or persistence layer.
411    #[cfg(feature = "storage")]
412    #[error("storage error: {0}")]
413    Storage(#[from] StorageError),
414
415    /// Errors originating from the SSH client session.
416    #[error("client error: {0}")]
417    Client(#[from] ClientError),
418
419    /// Errors originating from the SSH server orchestration.
420    #[error("server error: {0}")]
421    Server(#[from] ServerError),
422
423    /// Direct SSH protocol errors from the underlying library.
424    #[error("ssh protocol error: {0}")]
425    Russh(#[from] russh::Error),
426
427    /// Errors related to connection tickets.
428    #[error("ticket error: {0}")]
429    Ticket(#[from] crate::transport::ticket::TicketError),
430
431    /// Errors originating from the authentication subsystem.
432    #[error("authentication error: {0}")]
433    Auth(#[from] AuthError),
434
435    /// Generic I/O failures.
436    #[error("I/O error: {0}")]
437    Io(#[from] std::io::Error),
438
439    /// Authentication with the remote peer failed.
440    #[error("authentication failed")]
441    AuthenticationFailed,
442
443    /// The remote server identity does not match the pinned trust record.
444    #[error("server host key mismatch (expected {expected}, got {actual})")]
445    ServerKeyMismatch { expected: String, actual: String },
446
447    /// The requested connection target is invalid or unparseable.
448    #[error("invalid connection target: {raw}")]
449    InvalidTarget { raw: String },
450}
451
452/// A specialized `Result` type for irosh library operations.
453pub type Result<T> = std::result::Result<T, IroshError>;