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 {}