diff options
Diffstat (limited to 'getrandom/src/error.rs')
-rw-r--r-- | getrandom/src/error.rs | 178 |
1 files changed, 178 insertions, 0 deletions
diff --git a/getrandom/src/error.rs b/getrandom/src/error.rs new file mode 100644 index 0000000..b2cb9a8 --- /dev/null +++ b/getrandom/src/error.rs @@ -0,0 +1,178 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +use core::fmt; +use core::num::NonZeroU32; + +/// A small and `no_std` compatible error type. +/// +/// The [`Error::raw_os_error()`] will indicate if the error is from the OS, and +/// if so, which error code the OS gave the application. If such an error is +/// encountered, please consult with your system documentation. +/// +/// Internally this type is a NonZeroU32, with certain values reserved for +/// certain purposes, see [`Error::INTERNAL_START`] and [`Error::CUSTOM_START`]. +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct Error(NonZeroU32); + +impl Error { + #[deprecated(since = "0.1.7")] + /// Unknown error. + pub const UNKNOWN: Error = UNSUPPORTED; + #[deprecated(since = "0.1.7")] + /// System entropy source is unavailable. + pub const UNAVAILABLE: Error = UNSUPPORTED; + + /// Codes below this point represent OS Errors (i.e. positive i32 values). + /// Codes at or above this point, but below [`Error::CUSTOM_START`] are + /// reserved for use by the `rand` and `getrandom` crates. + pub const INTERNAL_START: u32 = 1 << 31; + + /// Codes at or above this point can be used by users to define their own + /// custom errors. + pub const CUSTOM_START: u32 = (1 << 31) + (1 << 30); + + /// Extract the raw OS error code (if this error came from the OS) + /// + /// This method is identical to `std::io::Error::raw_os_error()`, except + /// that it works in `no_std` contexts. If this method returns `None`, the + /// error value can still be formatted via the `Display` implementation. + #[inline] + pub fn raw_os_error(self) -> Option<i32> { + if self.0.get() < Self::INTERNAL_START { + Some(self.0.get() as i32) + } else { + None + } + } + + /// Extract the bare error code. + /// + /// This code can either come from the underlying OS, or be a custom error. + /// Use [`Error::raw_os_error()`] to disambiguate. + #[inline] + pub fn code(self) -> NonZeroU32 { + self.0 + } +} + +cfg_if! { + if #[cfg(unix)] { + fn os_err_desc(errno: i32, buf: &mut [u8]) -> Option<&str> { + let buf_ptr = buf.as_mut_ptr() as *mut libc::c_char; + if unsafe { libc::strerror_r(errno, buf_ptr, buf.len()) } != 0 { + return None; + } + + // Take up to trailing null byte + let n = buf.len(); + let idx = buf.iter().position(|&b| b == 0).unwrap_or(n); + core::str::from_utf8(&buf[..idx]).ok() + } + } else if #[cfg(target_os = "wasi")] { + fn os_err_desc(errno: i32, _buf: &mut [u8]) -> Option<&str> { + core::num::NonZeroU16::new(errno as u16) + .and_then(wasi::wasi_unstable::error_str) + } + } else { + fn os_err_desc(_errno: i32, _buf: &mut [u8]) -> Option<&str> { + None + } + } +} + +impl fmt::Debug for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut dbg = f.debug_struct("Error"); + if let Some(errno) = self.raw_os_error() { + dbg.field("os_error", &errno); + let mut buf = [0u8; 128]; + if let Some(desc) = os_err_desc(errno, &mut buf) { + dbg.field("description", &desc); + } + } else if let Some(desc) = internal_desc(*self) { + dbg.field("internal_code", &self.0.get()); + dbg.field("description", &desc); + } else { + dbg.field("unknown_code", &self.0.get()); + } + dbg.finish() + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(errno) = self.raw_os_error() { + let mut buf = [0u8; 128]; + match os_err_desc(errno, &mut buf) { + Some(desc) => f.write_str(desc), + None => write!(f, "OS Error: {}", errno), + } + } else if let Some(desc) = internal_desc(*self) { + f.write_str(desc) + } else { + write!(f, "Unknown Error: {}", self.0.get()) + } + } +} + +impl From<NonZeroU32> for Error { + fn from(code: NonZeroU32) -> Self { + Self(code) + } +} + +// TODO: Convert to a function when min_version >= 1.33 +macro_rules! internal_error { + ($n:expr) => { + Error(unsafe { NonZeroU32::new_unchecked(Error::INTERNAL_START + $n as u16 as u32) }) + }; +} + +/// Internal Error constants +pub(crate) const UNSUPPORTED: Error = internal_error!(0); +pub(crate) const ERRNO_NOT_POSITIVE: Error = internal_error!(1); +pub(crate) const UNKNOWN_IO_ERROR: Error = internal_error!(2); +pub(crate) const SEC_RANDOM_FAILED: Error = internal_error!(3); +pub(crate) const RTL_GEN_RANDOM_FAILED: Error = internal_error!(4); +pub(crate) const FAILED_RDRAND: Error = internal_error!(5); +pub(crate) const NO_RDRAND: Error = internal_error!(6); +pub(crate) const BINDGEN_CRYPTO_UNDEF: Error = internal_error!(7); +pub(crate) const BINDGEN_GRV_UNDEF: Error = internal_error!(8); +pub(crate) const STDWEB_NO_RNG: Error = internal_error!(9); +pub(crate) const STDWEB_RNG_FAILED: Error = internal_error!(10); +pub(crate) const RAND_SECURE_FATAL: Error = internal_error!(11); + +fn internal_desc(error: Error) -> Option<&'static str> { + match error { + UNSUPPORTED => Some("getrandom: this target is not supported"), + ERRNO_NOT_POSITIVE => Some("errno: did not return a positive value"), + UNKNOWN_IO_ERROR => Some("Unknown std::io::Error"), + SEC_RANDOM_FAILED => Some("SecRandomCopyBytes: call failed"), + RTL_GEN_RANDOM_FAILED => Some("RtlGenRandom: call failed"), + FAILED_RDRAND => Some("RDRAND: failed multiple times: CPU issue likely"), + NO_RDRAND => Some("RDRAND: instruction not supported"), + BINDGEN_CRYPTO_UNDEF => Some("wasm-bindgen: self.crypto is undefined"), + BINDGEN_GRV_UNDEF => Some("wasm-bindgen: crypto.getRandomValues is undefined"), + STDWEB_NO_RNG => Some("stdweb: no randomness source available"), + STDWEB_RNG_FAILED => Some("stdweb: failed to get randomness"), + RAND_SECURE_FATAL => Some("randSecure: random number generator module is not initialized"), + _ => None, + } +} + +#[cfg(test)] +mod tests { + use super::Error; + use core::mem::size_of; + + #[test] + fn test_size() { + assert_eq!(size_of::<Error>(), 4); + assert_eq!(size_of::<Result<(), Error>>(), 4); + } +} |