Skip to main content

rama_boring/
error.rs

1//! Errors returned by OpenSSL library.
2//!
3//! OpenSSL errors are stored in an `ErrorStack`.  Most methods in the crate
4//! returns a `Result<T, ErrorStack>` type.
5//!
6//! # Examples
7//!
8//! ```
9//! use rama_boring::error::ErrorStack;
10//! use rama_boring::bn::BigNum;
11//!
12//! let an_error = BigNum::from_dec_str("Cannot parse letters");
13//! match an_error {
14//!     Ok(_)  => (),
15//!     Err(e) => println!("Parsing Error: {:?}", e),
16//! }
17//! ```
18use crate::libc_types::{c_char, c_int, c_uint};
19use openssl_macros::corresponds;
20use std::borrow::Cow;
21use std::error;
22use std::ffi::CStr;
23use std::ffi::CString;
24use std::fmt;
25use std::io;
26use std::ptr;
27use std::str;
28
29use crate::ffi;
30
31pub use crate::ffi::ErrLib;
32
33/// Collection of [`Error`]s from OpenSSL.
34///
35/// [`Error`]: struct.Error.html
36#[derive(Debug, Clone)]
37pub struct ErrorStack(Vec<Error>);
38
39impl ErrorStack {
40    /// Pops the contents of the OpenSSL error stack, and returns it.
41    ///
42    /// This should be used only immediately after calling Boring FFI functions,
43    /// otherwise the stack may be empty or a leftover from unrelated calls.
44    #[corresponds(ERR_get_error_line_data)]
45    #[must_use = "Use ErrorStack::clear() to drop the error stack"]
46    pub fn get() -> ErrorStack {
47        let mut vec = vec![];
48        while let Some(err) = Error::get() {
49            vec.push(err);
50        }
51        ErrorStack(vec)
52    }
53
54    /// Pushes the errors back onto the OpenSSL error stack.
55    #[corresponds(ERR_put_error)]
56    pub fn put(&self) {
57        for error in self.errors() {
58            error.put();
59        }
60    }
61
62    /// Used to report errors from the Rust crate
63    #[cold]
64    pub(crate) fn internal_error(err: impl error::Error) -> Self {
65        Self(vec![Error::new_internal(Data::String(err.to_string()))])
66    }
67
68    /// Used to report errors from the Rust crate
69    #[cold]
70    pub(crate) fn internal_error_str(message: &'static str) -> Self {
71        Self(vec![Error::new_internal(Data::Static(message))])
72    }
73
74    /// Empties the current thread's error queue.
75    #[corresponds(ERR_clear_error)]
76    pub(crate) fn clear() {
77        unsafe {
78            ffi::ERR_clear_error();
79        }
80    }
81}
82
83impl ErrorStack {
84    /// Returns the errors in the stack.
85    #[must_use]
86    pub fn errors(&self) -> &[Error] {
87        &self.0
88    }
89}
90
91impl fmt::Display for ErrorStack {
92    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
93        if self.0.is_empty() {
94            return fmt.write_str("unknown BoringSSL error");
95        }
96
97        let mut first = true;
98        for err in &self.0 {
99            if !first {
100                fmt.write_str(" ")?;
101            }
102            first = false;
103            write!(
104                fmt,
105                "[{}]",
106                err.reason()
107                    .or_else(|| err.library())
108                    .unwrap_or("unknown reason")
109            )?;
110        }
111        Ok(())
112    }
113}
114
115impl error::Error for ErrorStack {}
116
117impl From<ErrorStack> for io::Error {
118    fn from(e: ErrorStack) -> io::Error {
119        io::Error::other(e)
120    }
121}
122
123impl From<ErrorStack> for fmt::Error {
124    fn from(_: ErrorStack) -> fmt::Error {
125        fmt::Error
126    }
127}
128
129/// A detailed error reported as part of an [`ErrorStack`].
130#[derive(Clone)]
131pub struct Error {
132    code: c_uint,
133    file: *const c_char,
134    line: c_uint,
135    data: Data,
136}
137
138#[derive(Clone)]
139enum Data {
140    None,
141    CString(CString),
142    String(String),
143    Static(&'static str),
144}
145
146unsafe impl Sync for Error {}
147unsafe impl Send for Error {}
148
149static BORING_INTERNAL: &CStr = c"boring-rust";
150
151impl Error {
152    /// Pops the first error off the OpenSSL error stack.
153    #[must_use = "Use ErrorStack::clear() to drop the error stack"]
154    #[corresponds(ERR_get_error_line_data)]
155    pub fn get() -> Option<Error> {
156        unsafe {
157            ffi::init();
158
159            let mut file = ptr::null();
160            let mut line = 0;
161            let mut data = ptr::null();
162            let mut flags = 0;
163            match ffi::ERR_get_error_line_data(&mut file, &mut line, &mut data, &mut flags) {
164                0 => None,
165                code => {
166                    // The memory referenced by data is only valid until that slot is overwritten
167                    // in the error stack, so we'll need to copy it off if it's dynamic
168                    let data = if flags & ffi::ERR_FLAG_STRING != 0 {
169                        Data::CString(CStr::from_ptr(data.cast()).to_owned())
170                    } else {
171                        Data::None
172                    };
173                    Some(Error {
174                        code,
175                        file,
176                        line: line as c_uint,
177                        data,
178                    })
179                }
180            }
181        }
182    }
183
184    /// Pushes the error back onto the OpenSSL error stack.
185    #[corresponds(ERR_put_error)]
186    pub fn put(&self) {
187        unsafe {
188            ffi::ERR_put_error(
189                ffi::ERR_GET_LIB(self.code),
190                ffi::ERR_GET_FUNC(self.code),
191                ffi::ERR_GET_REASON(self.code),
192                self.file,
193                self.line,
194            );
195            if let Some(cstr) = self.data_cstr() {
196                ffi::ERR_add_error_data(1, cstr.as_ptr().cast_mut());
197            }
198        }
199    }
200
201    /// Get `{lib}_R_{reason}` reason code for the given library, or `None` if the error is from a different library.
202    ///
203    /// Libraries are identified by [`ERR_LIB_{name}`(ffi::ERR_LIB_SSL) constants.
204    #[inline]
205    #[must_use]
206    #[track_caller]
207    pub fn library_reason(&self, library_code: ErrLib) -> Option<c_int> {
208        debug_assert!(library_code.0 < ffi::ERR_NUM_LIBS.0);
209        (self.library_code() == library_code.0 as c_int).then_some(self.reason_code())
210    }
211
212    /// Returns a raw OpenSSL **packed** error code for this error, which **can't be reliably compared to any error constant**.
213    ///
214    /// Use [`Error::library_code()`] and [`Error::library_reason()`] instead.
215    /// Packed error codes are different than [SSL error codes](crate::ssl::ErrorCode).
216    #[must_use]
217    #[deprecated(note = "use library_reason() to compare error codes")]
218    pub fn code(&self) -> c_uint {
219        self.code
220    }
221
222    /// Returns the name of the library reporting the error, if available.
223    #[must_use]
224    pub fn library(&self) -> Option<&'static str> {
225        if self.is_internal() {
226            return None;
227        }
228        unsafe {
229            let cstr = ffi::ERR_lib_error_string(self.code);
230            if cstr.is_null() {
231                return None;
232            }
233            CStr::from_ptr(cstr.cast())
234                .to_str()
235                .ok()
236                .filter(|&msg| msg != "unknown library")
237        }
238    }
239
240    /// Returns the raw OpenSSL error constant for the library reporting the error (`ERR_LIB_{name}`).
241    ///
242    /// Error [reason codes](Error::library_reason) are not globally unique, but scoped to each library.
243    #[must_use]
244    pub fn library_code(&self) -> c_int {
245        ffi::ERR_GET_LIB(self.code)
246    }
247
248    /// Returns `None`. Boring doesn't use function codes.
249    pub fn function(&self) -> Option<&'static str> {
250        None
251    }
252
253    /// Returns the reason for the error.
254    #[must_use]
255    pub fn reason(&self) -> Option<&str> {
256        if self.is_internal() {
257            return self.data();
258        }
259        unsafe {
260            let cstr = ffi::ERR_reason_error_string(self.code);
261            if cstr.is_null() {
262                return None;
263            }
264            CStr::from_ptr(cstr.cast()).to_str().ok()
265        }
266    }
267
268    /// Returns [library-specific](Error::library_code) reason code corresponding to some of the `{lib}_R_{reason}` constants.
269    ///
270    /// Reason codes are ambiguous, and different libraries reuse the same numeric values for different errors.
271    /// Use [`Error::library_reason`] to compare error codes.
272    ///
273    /// For `ERR_LIB_SYS` the reason code is `errno`. `ERR_LIB_USER` can use any values.
274    /// Other libraries may use [`ERR_R_*`](ffi::ERR_R_FATAL) or their own codes.
275    #[must_use]
276    pub fn reason_code(&self) -> c_int {
277        ffi::ERR_GET_REASON(self.code)
278    }
279
280    /// Returns the name of the source file which encountered the error.
281    #[must_use]
282    pub fn file(&self) -> &'static str {
283        unsafe {
284            if self.file.is_null() {
285                return "";
286            }
287            CStr::from_ptr(self.file.cast())
288                .to_str()
289                .unwrap_or_default()
290        }
291    }
292
293    /// Returns the line in the source file which encountered the error.
294    ///
295    /// 0 if unknown
296    #[allow(clippy::unnecessary_cast)]
297    #[must_use]
298    pub fn line(&self) -> u32 {
299        self.line as u32
300    }
301
302    /// Returns additional data describing the error.
303    #[must_use]
304    pub fn data(&self) -> Option<&str> {
305        match &self.data {
306            Data::None => None,
307            Data::CString(cstring) => cstring.to_str().ok(),
308            Data::String(s) => Some(s),
309            Data::Static(s) => Some(s),
310        }
311    }
312
313    #[must_use]
314    fn data_cstr(&self) -> Option<Cow<'_, CStr>> {
315        let s = match &self.data {
316            Data::None => return None,
317            Data::CString(cstr) => return Some(Cow::Borrowed(cstr)),
318            Data::String(s) => s.as_str(),
319            Data::Static(s) => s,
320        };
321        CString::new(s).ok().map(Cow::Owned)
322    }
323
324    fn new_internal(msg: Data) -> Self {
325        Self {
326            code: ffi::ERR_PACK(ffi::ERR_LIB_NONE.0 as _, 0, 0) as _,
327            file: BORING_INTERNAL.as_ptr(),
328            line: 0,
329            data: msg,
330        }
331    }
332
333    fn is_internal(&self) -> bool {
334        std::ptr::eq(self.file, BORING_INTERNAL.as_ptr())
335    }
336}
337
338impl fmt::Debug for Error {
339    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
340        let mut builder = fmt.debug_struct("Error");
341        builder.field("code", &self.code);
342        if !self.is_internal() {
343            if let Some(library) = self.library() {
344                builder.field("library", &library);
345            }
346            builder.field("library_code", &self.library_code());
347            if let Some(reason) = self.reason() {
348                builder.field("reason", &reason);
349            }
350            builder.field("reason_code", &self.reason_code());
351            builder.field("file", &self.file());
352            builder.field("line", &self.line());
353        }
354        if let Some(data) = self.data() {
355            builder.field("data", &data);
356        }
357        builder.finish()
358    }
359}
360
361impl fmt::Display for Error {
362    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
363        write!(
364            fmt,
365            "{}\n\nCode: {:08X}\nLoc: {}:{}",
366            self.reason().unwrap_or("unknown TLS error"),
367            &self.code,
368            self.file(),
369            self.line()
370        )
371    }
372}
373
374impl error::Error for Error {}
375
376#[test]
377fn internal_err() {
378    let e = ErrorStack::internal_error(io::Error::other("hello, boring"));
379    assert_eq!(1, e.errors().len());
380    assert!(e.to_string().contains("hello, boring"), "{e} {e:?}");
381
382    e.put();
383    let e = ErrorStack::get();
384    assert!(e.to_string().contains("hello, boring"), "{e} {e:?}");
385}