| 1 | #[cfg (feature = "std" )] |
| 2 | extern crate std; |
| 3 | |
| 4 | use core::fmt; |
| 5 | |
| 6 | // This private alias mirrors `std::io::RawOsError`: |
| 7 | // https://doc.rust-lang.org/std/io/type.RawOsError.html) |
| 8 | cfg_if::cfg_if!( |
| 9 | if #[cfg(target_os = "uefi" )] { |
| 10 | // See the UEFI spec for more information: |
| 11 | // https://uefi.org/specs/UEFI/2.10/Apx_D_Status_Codes.html |
| 12 | type RawOsError = usize; |
| 13 | type NonZeroRawOsError = core::num::NonZeroUsize; |
| 14 | const UEFI_ERROR_FLAG: RawOsError = 1 << (RawOsError::BITS - 1); |
| 15 | } else { |
| 16 | type RawOsError = i32; |
| 17 | type NonZeroRawOsError = core::num::NonZeroI32; |
| 18 | } |
| 19 | ); |
| 20 | |
| 21 | /// A small and `no_std` compatible error type |
| 22 | /// |
| 23 | /// The [`Error::raw_os_error()`] will indicate if the error is from the OS, and |
| 24 | /// if so, which error code the OS gave the application. If such an error is |
| 25 | /// encountered, please consult with your system documentation. |
| 26 | /// |
| 27 | /// *If this crate's `"std"` Cargo feature is enabled*, then: |
| 28 | /// - [`getrandom::Error`][Error] implements |
| 29 | /// [`std::error::Error`](https://doc.rust-lang.org/std/error/trait.Error.html) |
| 30 | /// - [`std::io::Error`](https://doc.rust-lang.org/std/io/struct.Error.html) implements |
| 31 | /// [`From<getrandom::Error>`](https://doc.rust-lang.org/std/convert/trait.From.html). |
| 32 | |
| 33 | // note: on non-UEFI targets OS errors are represented as negative integers, |
| 34 | // while on UEFI targets OS errors have the highest bit set to 1. |
| 35 | #[derive (Copy, Clone, Eq, PartialEq)] |
| 36 | pub struct Error(NonZeroRawOsError); |
| 37 | |
| 38 | impl Error { |
| 39 | /// This target/platform is not supported by `getrandom`. |
| 40 | pub const UNSUPPORTED: Error = Self::new_internal(0); |
| 41 | /// The platform-specific `errno` returned a non-positive value. |
| 42 | pub const ERRNO_NOT_POSITIVE: Error = Self::new_internal(1); |
| 43 | /// Encountered an unexpected situation which should not happen in practice. |
| 44 | pub const UNEXPECTED: Error = Self::new_internal(2); |
| 45 | |
| 46 | /// Internal errors can be in the range of 2^16..2^17 |
| 47 | const INTERNAL_START: RawOsError = 1 << 16; |
| 48 | /// Custom errors can be in the range of 2^17..(2^17 + 2^16) |
| 49 | const CUSTOM_START: RawOsError = 1 << 17; |
| 50 | |
| 51 | /// Creates a new instance of an `Error` from a negative error code. |
| 52 | #[cfg (not(target_os = "uefi" ))] |
| 53 | #[allow (dead_code)] |
| 54 | pub(super) fn from_neg_error_code(code: RawOsError) -> Self { |
| 55 | if code < 0 { |
| 56 | let code = NonZeroRawOsError::new(code).expect("`code` is negative" ); |
| 57 | Self(code) |
| 58 | } else { |
| 59 | Error::UNEXPECTED |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | /// Creates a new instance of an `Error` from an UEFI error code. |
| 64 | #[cfg (target_os = "uefi" )] |
| 65 | #[allow (dead_code)] |
| 66 | pub(super) fn from_uefi_code(code: RawOsError) -> Self { |
| 67 | if code & UEFI_ERROR_FLAG != 0 { |
| 68 | let code = NonZeroRawOsError::new(code).expect("The highest bit of `code` is set to 1" ); |
| 69 | Self(code) |
| 70 | } else { |
| 71 | Self::UNEXPECTED |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | /// Extract the raw OS error code (if this error came from the OS) |
| 76 | /// |
| 77 | /// This method is identical to [`std::io::Error::raw_os_error()`][1], except |
| 78 | /// that it works in `no_std` contexts. On most targets this method returns |
| 79 | /// `Option<i32>`, but some platforms (e.g. UEFI) may use a different primitive |
| 80 | /// type like `usize`. Consult with the [`RawOsError`] docs for more information. |
| 81 | /// |
| 82 | /// If this method returns `None`, the error value can still be formatted via |
| 83 | /// the `Display` implementation. |
| 84 | /// |
| 85 | /// [1]: https://doc.rust-lang.org/std/io/struct.Error.html#method.raw_os_error |
| 86 | /// [`RawOsError`]: https://doc.rust-lang.org/std/io/type.RawOsError.html |
| 87 | #[inline ] |
| 88 | pub fn raw_os_error(self) -> Option<RawOsError> { |
| 89 | let code = self.0.get(); |
| 90 | |
| 91 | // note: in this method we need to cover only backends which rely on |
| 92 | // `Error::{from_error_code, from_errno, from_uefi_code}` methods, |
| 93 | // on all other backends this method always returns `None`. |
| 94 | |
| 95 | #[cfg (target_os = "uefi" )] |
| 96 | { |
| 97 | if code & UEFI_ERROR_FLAG != 0 { |
| 98 | Some(code) |
| 99 | } else { |
| 100 | None |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | #[cfg (not(target_os = "uefi" ))] |
| 105 | { |
| 106 | // On most targets `std` expects positive error codes while retrieving error strings: |
| 107 | // - `libc`-based targets use `strerror_r` which expects positive error codes. |
| 108 | // - Hermit relies on the `hermit-abi` crate, which expects positive error codes: |
| 109 | // https://docs.rs/hermit-abi/0.4.0/src/hermit_abi/errno.rs.html#400-532 |
| 110 | // - WASIp1 uses the same conventions as `libc`: |
| 111 | // https://github.com/rust-lang/rust/blob/1.85.0/library/std/src/sys/pal/wasi/os.rs#L57-L67 |
| 112 | // |
| 113 | // The only exception is Solid, `std` expects negative system error codes, see: |
| 114 | // https://github.com/rust-lang/rust/blob/1.85.0/library/std/src/sys/pal/solid/error.rs#L5-L31 |
| 115 | if code >= 0 { |
| 116 | None |
| 117 | } else if cfg!(not(target_os = "solid_asp3" )) { |
| 118 | code.checked_neg() |
| 119 | } else { |
| 120 | Some(code) |
| 121 | } |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | /// Creates a new instance of an `Error` from a particular custom error code. |
| 126 | pub const fn new_custom(n: u16) -> Error { |
| 127 | // SAFETY: code > 0 as CUSTOM_START > 0 and adding `n` won't overflow `RawOsError`. |
| 128 | let code = Error::CUSTOM_START + (n as RawOsError); |
| 129 | Error(unsafe { NonZeroRawOsError::new_unchecked(code) }) |
| 130 | } |
| 131 | |
| 132 | /// Creates a new instance of an `Error` from a particular internal error code. |
| 133 | pub(crate) const fn new_internal(n: u16) -> Error { |
| 134 | // SAFETY: code > 0 as INTERNAL_START > 0 and adding `n` won't overflow `RawOsError`. |
| 135 | let code = Error::INTERNAL_START + (n as RawOsError); |
| 136 | Error(unsafe { NonZeroRawOsError::new_unchecked(code) }) |
| 137 | } |
| 138 | |
| 139 | fn internal_desc(&self) -> Option<&'static str> { |
| 140 | let desc = match *self { |
| 141 | Error::UNSUPPORTED => "getrandom: this target is not supported" , |
| 142 | Error::ERRNO_NOT_POSITIVE => "errno: did not return a positive value" , |
| 143 | Error::UNEXPECTED => "unexpected situation" , |
| 144 | #[cfg (any( |
| 145 | target_os = "ios" , |
| 146 | target_os = "visionos" , |
| 147 | target_os = "watchos" , |
| 148 | target_os = "tvos" , |
| 149 | ))] |
| 150 | Error::IOS_RANDOM_GEN => "SecRandomCopyBytes: iOS Security framework failure" , |
| 151 | #[cfg (all(windows, target_vendor = "win7" ))] |
| 152 | Error::WINDOWS_RTL_GEN_RANDOM => "RtlGenRandom: Windows system function failure" , |
| 153 | #[cfg (all(feature = "wasm_js" , getrandom_backend = "wasm_js" ))] |
| 154 | Error::WEB_CRYPTO => "Web Crypto API is unavailable" , |
| 155 | #[cfg (target_os = "vxworks" )] |
| 156 | Error::VXWORKS_RAND_SECURE => "randSecure: VxWorks RNG module is not initialized" , |
| 157 | |
| 158 | #[cfg (any( |
| 159 | getrandom_backend = "rdrand" , |
| 160 | all(target_arch = "x86_64" , target_env = "sgx" ) |
| 161 | ))] |
| 162 | Error::FAILED_RDRAND => "RDRAND: failed multiple times: CPU issue likely" , |
| 163 | #[cfg (any( |
| 164 | getrandom_backend = "rdrand" , |
| 165 | all(target_arch = "x86_64" , target_env = "sgx" ) |
| 166 | ))] |
| 167 | Error::NO_RDRAND => "RDRAND: instruction not supported" , |
| 168 | |
| 169 | #[cfg (getrandom_backend = "rndr" )] |
| 170 | Error::RNDR_FAILURE => "RNDR: Could not generate a random number" , |
| 171 | #[cfg (getrandom_backend = "rndr" )] |
| 172 | Error::RNDR_NOT_AVAILABLE => "RNDR: Register not supported" , |
| 173 | _ => return None, |
| 174 | }; |
| 175 | Some(desc) |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | impl fmt::Debug for Error { |
| 180 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 181 | let mut dbg: DebugStruct<'_, '_> = f.debug_struct(name:"Error" ); |
| 182 | if let Some(errno: i32) = self.raw_os_error() { |
| 183 | dbg.field(name:"os_error" , &errno); |
| 184 | #[cfg (feature = "std" )] |
| 185 | dbg.field(name:"description" , &std::io::Error::from_raw_os_error(code:errno)); |
| 186 | } else if let Some(desc: &'static str) = self.internal_desc() { |
| 187 | dbg.field(name:"internal_code" , &self.0.get()); |
| 188 | dbg.field(name:"description" , &desc); |
| 189 | } else { |
| 190 | dbg.field(name:"unknown_code" , &self.0.get()); |
| 191 | } |
| 192 | dbg.finish() |
| 193 | } |
| 194 | } |
| 195 | |
| 196 | impl fmt::Display for Error { |
| 197 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 198 | if let Some(errno: i32) = self.raw_os_error() { |
| 199 | cfg_if! { |
| 200 | if #[cfg(feature = "std" )] { |
| 201 | std::io::Error::from_raw_os_error(errno).fmt(f) |
| 202 | } else { |
| 203 | write!(f, "OS Error: {}" , errno) |
| 204 | } |
| 205 | } |
| 206 | } else if let Some(desc: &'static str) = self.internal_desc() { |
| 207 | f.write_str(data:desc) |
| 208 | } else { |
| 209 | write!(f, "Unknown Error: {}" , self.0.get()) |
| 210 | } |
| 211 | } |
| 212 | } |
| 213 | |