Skip to main content

sunset/
error.rs

1use core::str::Utf8Error;
2#[allow(unused_imports)]
3use log::{debug, error, info, log, trace, warn};
4
5use core::fmt::Arguments;
6
7use snafu::prelude::*;
8
9use crate::channel::ChanNum;
10
11#[allow(unused_imports)]
12use snafu::{Backtrace, Location};
13
14// TODO: can we make Snafu not require Debug?
15
16/// The Sunset error type.
17#[non_exhaustive]
18#[derive(Snafu, Debug)]
19#[snafu(context(suffix(false)))]
20// TODO: maybe split this into a list of public vs private errors?
21#[snafu(visibility(pub))]
22pub enum Error {
23    /// Output buffer ran out of room
24    NoRoom {
25        #[cfg(feature = "backtrace")]
26        backtrace: Backtrace,
27    },
28
29    /// Input buffer ran out
30    RanOut {
31        #[cfg(feature = "backtrace")]
32        backtrace: Backtrace,
33    },
34
35    /// Not a UTF-8 string
36    BadString,
37
38    /// Not a valid SSH ASCII string
39    BadName,
40
41    /// Key exchange incorrect
42    BadKex,
43
44    /// Packet integrity failed
45    BadDecrypt,
46
47    /// Signature is incorrect
48    BadSig,
49
50    /// Integer overflow in packet
51    BadNumber,
52
53    /// Error in received SSH protocol. Will disconnect.
54    SSHProto {
55        #[cfg(feature = "backtrace")]
56        backtrace: Backtrace,
57    },
58
59    /// Peer sent something we don't handle. Will disconnect.
60    ///
61    /// This differs to `SSHProtoError`. In this case the peer may be
62    /// behaved within the SSH specifications, but Sunset doesn't
63    /// support it.
64    // TODO: 'static disconnect message to return?
65    SSHProtoUnsupported,
66
67    /// Received a key with invalid structure, or too large.
68    BadKeyFormat,
69
70    /// Remote peer isn't SSH 2.0
71    NotSSH,
72
73    /// Bad key format
74    BadKey,
75
76    /// Ran out of channels
77    NoChannels,
78
79    #[snafu(display("Bad channel number {num}"))]
80    BadChannel { num: ChanNum },
81
82    /// Bad channel data type
83    ///
84    /// Returned from an API call that would imply ChanData::Stderr
85    /// being sent to a server. This error will not be returned for
86    /// network data in the incorrect direction, instead that data is dropped.
87    BadChannelData,
88
89    /// Bad application usage
90    ///
91    /// Returned from an API call when the API is used incorrectly.
92    /// Examples could include:
93    /// - A `ChanHandle` is used incorrectly, for example being cloned
94    ///   (millions of times) and not released.
95    // TODO: /// #[snafu(display("Failure from application: {msg}"))]
96    BadUsage {
97        #[cfg(feature = "backtrace")]
98        backtrace: snafu::Backtrace,
99        // TODO
100        // msg: &'static str,
101    },
102
103    /// SSH packet contents doesn't match length
104    WrongPacketLength,
105
106    /// Channel EOF
107    ///
108    /// This is an expected error when a SSH channel completes. Can be returned
109    /// by channel read/write functions. Any further calls in the same direction
110    /// will fail similarly.
111    ChannelEOF,
112
113    /// Session EOF
114    ///
115    /// This is an expected error when the SSH session has finished.
116    SessionEOF,
117
118    // Used for unknown key types etc.
119    #[snafu(display("{what} is not available"))]
120    NotAvailable { what: &'static str },
121
122    #[snafu(display("Unknown packet type {number}"))]
123    UnknownPacket { number: u8 },
124
125    /// Received packet at a disallowed time.
126    // TODO: this is kind of a subset of SSHProtoError, maybe not needed
127    PacketWrong {
128        #[cfg(feature = "backtrace")]
129        backtrace: Backtrace,
130    },
131    // #[snafu(display("Program bug {location}"))]
132    // Bug { location: snafu::Location },
133    #[snafu(display("No matching {algo} algorithm"))]
134    AlgoNoMatch { algo: &'static str },
135
136    #[snafu(display("Packet size {size} too large (or bad decrypt)"))]
137    BigPacket { size: usize },
138
139    /// Ran out of authentication methods to try (as a client)
140    NoAuthMethods,
141
142    /// An unknown SSH name is provided, for a key type, signature type,
143    /// channel name etc.
144    #[snafu(display("Unknown {kind} method"))]
145    UnknownMethod { kind: &'static str },
146
147    #[snafu(display("{msg}"))]
148    // TODO: these could eventually get categorised
149    Custom { msg: &'static str },
150
151    /// IO Error
152    #[cfg(feature = "std")]
153    IoError { source: std::io::Error },
154
155    // This state should not be reached, previous logic should have prevented it.
156    // Create this using [`Error::bug()`] or [`.trap()`](TrapBug::trap).
157    // Location is currently disabled due to bloat.
158    // #[snafu(display("Program bug {location}"))]
159    // Bug { location: snafu::Location },
160    /// Program bug
161    Bug,
162}
163
164impl Error {
165    pub fn msg(m: &'static str) -> Error {
166        Error::Custom { msg: m }
167    }
168
169    #[cold]
170    #[track_caller]
171    /// Panics in debug builds, returns [`Error::Bug`] in release.
172    // TODO: this should return a Result since it's always used as Err(Error::bug())
173    pub fn bug() -> Error {
174        // Easier to track the source of errors in development,
175        // but release builds shouldn't panic.
176        if cfg!(debug_assertions) {
177            panic!("Hit a bug");
178        } else {
179            // let caller = core::panic::Location::caller();
180            Error::Bug
181            // {
182            //     location: snafu::Location::new(
183            //         caller.file(),
184            //         caller.line(),
185            //         caller.column(),
186            //     ),
187            // }
188        }
189    }
190
191    /// Like [`bug()`](Error::bug) but with a message
192    ///
193    /// The message can be used instead of a code comment, is logged at `trace` level.
194    #[cold]
195    pub fn bug_fmt(args: Arguments) -> Error {
196        // Easier to track the source of errors in development,
197        // but release builds shouldn't panic.
198        if cfg!(debug_assertions) {
199            panic!("Hit a bug: {args}");
200        } else {
201            trace!("Hit a bug: {args}");
202            // TODO: this bloats binaries with full paths
203            // https://github.com/rust-lang/rust/issues/95529 is having function
204            // let caller = core::panic::Location::caller();
205            Error::Bug
206            // {
207            //     location: snafu::Location::new(
208            //         caller.file(),
209            //         caller.line(),
210            //         caller.column(),
211            //     ),
212            // }
213        }
214    }
215
216    #[cold]
217    /// TODO: is the generic `T` going to make it bloat?
218    pub fn bug_msg<T>(msg: &str) -> Result<T, Error> {
219        Err(Self::bug_fmt(format_args!("{}", msg)))
220    }
221
222    #[cold]
223    pub fn bug_err_msg(msg: &str) -> Error {
224        Self::bug_fmt(format_args!("{}", msg))
225    }
226}
227
228#[cfg(feature = "embedded-io")]
229impl embedded_io::Error for Error {
230    fn kind(&self) -> embedded_io::ErrorKind {
231        embedded_io::ErrorKind::Other
232    }
233}
234
235/// A Sunset-specific Result type.
236pub type Result<T, E = Error> = core::result::Result<T, E>;
237
238pub trait TrapBug<T> {
239    /// `.trap()` should be used like `.unwrap()`, in situations
240    /// never expected to fail. Instead it calls [`Error::bug()`].
241    /// (or debug builds may panic)
242    fn trap(self) -> Result<T, Error>;
243
244    /// Like `trap()` but with a message, calls [`Error::bug_msg()`]
245    /// The message can be used instead of a comment.
246    fn trap_msg(self, args: Arguments) -> Result<T, Error>;
247}
248
249impl<T, E> TrapBug<T> for Result<T, E> {
250    fn trap(self) -> Result<T, Error> {
251        // call directly so that Location::caller() works
252        if let Ok(i) = self {
253            Ok(i)
254        } else {
255            Err(Error::bug())
256        }
257    }
258    fn trap_msg(self, args: Arguments) -> Result<T, Error> {
259        // call directly so that Location::caller() works
260        if let Ok(i) = self {
261            Ok(i)
262        } else {
263            Err(Error::bug_fmt(args))
264        }
265    }
266}
267
268impl<T> TrapBug<T> for Option<T> {
269    #[track_caller]
270    fn trap(self) -> Result<T, Error> {
271        // call directly so that Location::caller() works
272        if let Some(i) = self {
273            Ok(i)
274        } else {
275            Err(Error::bug())
276        }
277    }
278    fn trap_msg(self, args: Arguments) -> Result<T, Error> {
279        // call directly so that Location::caller() works
280        if let Some(i) = self {
281            Ok(i)
282        } else {
283            Err(Error::bug_fmt(args))
284        }
285    }
286}
287
288impl From<Utf8Error> for Error {
289    fn from(_e: Utf8Error) -> Error {
290        Error::BadString
291    }
292}
293
294#[cfg(feature = "std")]
295impl From<std::io::Error> for Error {
296    fn from(value: std::io::Error) -> Self {
297        Self::IoError { source: value }
298    }
299}
300
301#[cfg(test)]
302pub(crate) mod tests {}