Skip to main content

netconf_rust/
error.rs

1use std::io;
2use thiserror::Error;
3
4#[derive(Debug, Error)]
5pub enum FramingError {
6    #[error("unexpected EOF before message complete")]
7    UnexpectedEof,
8
9    #[error("message exceeds size limit ({received} bytes, limit {limit})")]
10    MessageTooLarge { limit: usize, received: usize },
11
12    #[error("invalid chunk size: {0:?}")]
13    InvalidChunkSize(String),
14
15    #[error("invalid framing header: expected {expected}, got {got:#02x?}")]
16    InvalidHeader {
17        expected: &'static str,
18        got: Vec<u8>,
19    },
20
21    #[error("I/O error: {0}")]
22    Io(#[from] std::io::Error),
23
24    #[error("invalid utf-8 in message")]
25    InvalidUtf8,
26}
27
28/// Transport layer errors (SSH connection, authentication).
29#[derive(Error, Debug)]
30pub enum TransportError {
31    /// Failed to connect to host
32    #[error("Connection failed to {host}:{port}: {source}")]
33    ConnectionFailed {
34        host: String,
35        port: u16,
36        #[source]
37        source: io::Error,
38    },
39
40    /// SSH handshake or protocol error
41    #[error("SSH error: {0}")]
42    Ssh(#[from] russh::Error),
43
44    /// Authentication failed
45    #[error("Authentication failed for user '{user}'")]
46    AuthenticationFailed { user: String },
47
48    /// SSH key error
49    #[error("SSH key error: {0}")]
50    Key(String),
51
52    /// Connection was closed unexpectedly
53    #[error("Connection disconnected")]
54    Disconnected,
55
56    /// Operation timed out
57    #[error("Operation timed out after {0:?}")]
58    Timeout(std::time::Duration),
59
60    /// Host key not found in known_hosts (strict mode)
61    #[error("Host key for {host}:{port} not found in known_hosts")]
62    HostKeyUnknown { host: String, port: u16 },
63
64    /// Host key changed from what's recorded in known_hosts
65    #[error(
66        "Host key for {host}:{port} has CHANGED (known_hosts line {line}). This could indicate a man-in-the-middle attack."
67    )]
68    HostKeyChanged {
69        host: String,
70        port: u16,
71        line: usize,
72    },
73
74    /// Error accessing or parsing known_hosts file
75    #[error("Known hosts error: {0}")]
76    KnownHosts(String),
77
78    /// I/O error
79    #[error("I/O error: {0}")]
80    Io(#[from] io::Error),
81}
82
83#[derive(Debug, Error)]
84pub enum Error {
85    #[error("I/O error: {0}")]
86    Io(#[from] std::io::Error),
87
88    #[error("XML error: {0}")]
89    Xml(#[from] quick_xml::Error),
90
91    #[error("framing error: {0}")]
92    Framing(#[from] FramingError),
93
94    #[error("authentication failed")]
95    AuthFailed,
96
97    #[error("hello exchange failed: {0}")]
98    HelloFailed(String),
99
100    #[error("unexpected response: {0}")]
101    UnexpectedResponse(String),
102
103    #[error("RPC error (message-id={message_id}): {error}")]
104    Rpc { message_id: u32, error: String },
105
106    #[error("Transport error: {0}")]
107    Transport(#[from] TransportError),
108
109    #[error("invalid session state: expected Ready, got: {0}")]
110    InvalidState(String),
111
112    #[error("session closed")]
113    SessionClosed,
114}
115
116/// Route russh::Error through TransportError so all SSH errors
117/// go through a single path.
118impl From<russh::Error> for Error {
119    fn from(e: russh::Error) -> Self {
120        Error::Transport(TransportError::Ssh(e))
121    }
122}
123
124pub type Result<T> = std::result::Result<T, Error>;