getrandom/
error.rs

1use core::{fmt, num::NonZeroU32};
2
3/// A small and `no_std` compatible error type
4///
5/// The [`Error::raw_os_error()`] will indicate if the error is from the OS, and
6/// if so, which error code the OS gave the application. If such an error is
7/// encountered, please consult with your system documentation.
8///
9/// Internally this type is a NonZeroU32, with certain values reserved for
10/// certain purposes, see [`Error::INTERNAL_START`] and [`Error::CUSTOM_START`].
11///
12/// *If this crate's `"std"` Cargo feature is enabled*, then:
13/// - [`getrandom::Error`][Error] implements
14///   [`std::error::Error`](https://doc.rust-lang.org/std/error/trait.Error.html)
15/// - [`std::io::Error`](https://doc.rust-lang.org/std/io/struct.Error.html) implements
16///   [`From<getrandom::Error>`](https://doc.rust-lang.org/std/convert/trait.From.html).
17#[derive(Copy, Clone, Eq, PartialEq)]
18pub struct Error(NonZeroU32);
19
20const fn internal_error(n: u16) -> Error {
21    // SAFETY: code > 0 as INTERNAL_START > 0 and adding n won't overflow a u32.
22    let code = Error::INTERNAL_START + (n as u32);
23    Error(unsafe { NonZeroU32::new_unchecked(code) })
24}
25
26impl Error {
27    /// This target/platform is not supported by `getrandom`.
28    pub const UNSUPPORTED: Error = internal_error(0);
29    /// The platform-specific `errno` returned a non-positive value.
30    pub const ERRNO_NOT_POSITIVE: Error = internal_error(1);
31    /// Encountered an unexpected situation which should not happen in practice.
32    pub const UNEXPECTED: Error = internal_error(2);
33    /// Call to [`CCRandomGenerateBytes`](https://opensource.apple.com/source/CommonCrypto/CommonCrypto-60074/include/CommonRandom.h.auto.html) failed
34    /// on iOS, tvOS, or waatchOS.
35    // TODO: Update this constant name in the next breaking release.
36    pub const IOS_SEC_RANDOM: Error = internal_error(3);
37    /// Call to Windows [`RtlGenRandom`](https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom) failed.
38    pub const WINDOWS_RTL_GEN_RANDOM: Error = internal_error(4);
39    /// RDRAND instruction failed due to a hardware issue.
40    pub const FAILED_RDRAND: Error = internal_error(5);
41    /// RDRAND instruction unsupported on this target.
42    pub const NO_RDRAND: Error = internal_error(6);
43    /// The environment does not support the Web Crypto API.
44    pub const WEB_CRYPTO: Error = internal_error(7);
45    /// Calling Web Crypto API `crypto.getRandomValues` failed.
46    pub const WEB_GET_RANDOM_VALUES: Error = internal_error(8);
47    /// On VxWorks, call to `randSecure` failed (random number generator is not yet initialized).
48    pub const VXWORKS_RAND_SECURE: Error = internal_error(11);
49    /// Node.js does not have the `crypto` CommonJS module.
50    pub const NODE_CRYPTO: Error = internal_error(12);
51    /// Calling Node.js function `crypto.randomFillSync` failed.
52    pub const NODE_RANDOM_FILL_SYNC: Error = internal_error(13);
53    /// Called from an ES module on Node.js. This is unsupported, see:
54    /// <https://docs.rs/getrandom#nodejs-es-module-support>.
55    pub const NODE_ES_MODULE: Error = internal_error(14);
56
57    /// Codes below this point represent OS Errors (i.e. positive i32 values).
58    /// Codes at or above this point, but below [`Error::CUSTOM_START`] are
59    /// reserved for use by the `rand` and `getrandom` crates.
60    pub const INTERNAL_START: u32 = 1 << 31;
61
62    /// Codes at or above this point can be used by users to define their own
63    /// custom errors.
64    pub const CUSTOM_START: u32 = (1 << 31) + (1 << 30);
65
66    /// Extract the raw OS error code (if this error came from the OS)
67    ///
68    /// This method is identical to [`std::io::Error::raw_os_error()`][1], except
69    /// that it works in `no_std` contexts. If this method returns `None`, the
70    /// error value can still be formatted via the `Display` implementation.
71    ///
72    /// [1]: https://doc.rust-lang.org/std/io/struct.Error.html#method.raw_os_error
73    #[inline]
74    pub fn raw_os_error(self) -> Option<i32> {
75        if self.0.get() < Self::INTERNAL_START {
76            match () {
77                #[cfg(target_os = "solid_asp3")]
78                // On SOLID, negate the error code again to obtain the original
79                // error code.
80                () => Some(-(self.0.get() as i32)),
81                #[cfg(not(target_os = "solid_asp3"))]
82                () => Some(self.0.get() as i32),
83            }
84        } else {
85            None
86        }
87    }
88
89    /// Extract the bare error code.
90    ///
91    /// This code can either come from the underlying OS, or be a custom error.
92    /// Use [`Error::raw_os_error()`] to disambiguate.
93    #[inline]
94    pub const fn code(self) -> NonZeroU32 {
95        self.0
96    }
97}
98
99cfg_if! {
100    if #[cfg(unix)] {
101        fn os_err(errno: i32, buf: &mut [u8]) -> Option<&str> {
102            let buf_ptr = buf.as_mut_ptr() as *mut libc::c_char;
103            if unsafe { libc::strerror_r(errno, buf_ptr, buf.len()) } != 0 {
104                return None;
105            }
106
107            // Take up to trailing null byte
108            let n = buf.len();
109            let idx = buf.iter().position(|&b| b == 0).unwrap_or(n);
110            core::str::from_utf8(&buf[..idx]).ok()
111        }
112    } else {
113        fn os_err(_errno: i32, _buf: &mut [u8]) -> Option<&str> {
114            None
115        }
116    }
117}
118
119impl fmt::Debug for Error {
120    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121        let mut dbg = f.debug_struct("Error");
122        if let Some(errno) = self.raw_os_error() {
123            dbg.field("os_error", &errno);
124            let mut buf = [0u8; 128];
125            if let Some(err) = os_err(errno, &mut buf) {
126                dbg.field("description", &err);
127            }
128        } else if let Some(desc) = internal_desc(*self) {
129            dbg.field("internal_code", &self.0.get());
130            dbg.field("description", &desc);
131        } else {
132            dbg.field("unknown_code", &self.0.get());
133        }
134        dbg.finish()
135    }
136}
137
138impl fmt::Display for Error {
139    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140        if let Some(errno) = self.raw_os_error() {
141            let mut buf = [0u8; 128];
142            match os_err(errno, &mut buf) {
143                Some(err) => err.fmt(f),
144                None => write!(f, "OS Error: {}", errno),
145            }
146        } else if let Some(desc) = internal_desc(*self) {
147            f.write_str(desc)
148        } else {
149            write!(f, "Unknown Error: {}", self.0.get())
150        }
151    }
152}
153
154impl From<NonZeroU32> for Error {
155    fn from(code: NonZeroU32) -> Self {
156        Self(code)
157    }
158}
159
160fn internal_desc(error: Error) -> Option<&'static str> {
161    match error {
162        Error::UNSUPPORTED => Some("getrandom: this target is not supported"),
163        Error::ERRNO_NOT_POSITIVE => Some("errno: did not return a positive value"),
164        Error::UNEXPECTED => Some("unexpected situation"),
165        Error::IOS_SEC_RANDOM => Some("SecRandomCopyBytes: iOS Security framework failure"),
166        Error::WINDOWS_RTL_GEN_RANDOM => Some("RtlGenRandom: Windows system function failure"),
167        Error::FAILED_RDRAND => Some("RDRAND: failed multiple times: CPU issue likely"),
168        Error::NO_RDRAND => Some("RDRAND: instruction not supported"),
169        Error::WEB_CRYPTO => Some("Web Crypto API is unavailable"),
170        Error::WEB_GET_RANDOM_VALUES => Some("Calling Web API crypto.getRandomValues failed"),
171        Error::VXWORKS_RAND_SECURE => Some("randSecure: VxWorks RNG module is not initialized"),
172        Error::NODE_CRYPTO => Some("Node.js crypto CommonJS module is unavailable"),
173        Error::NODE_RANDOM_FILL_SYNC => Some("Calling Node.js API crypto.randomFillSync failed"),
174        Error::NODE_ES_MODULE => Some("Node.js ES modules are not directly supported, see https://docs.rs/getrandom#nodejs-es-module-support"),
175        _ => None,
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use super::Error;
182    use core::mem::size_of;
183
184    #[test]
185    fn test_size() {
186        assert_eq!(size_of::<Error>(), 4);
187        assert_eq!(size_of::<Result<(), Error>>(), 4);
188    }
189}