diff options
Diffstat (limited to 'third_party/rust/nss-gk-api/src/err.rs')
-rw-r--r-- | third_party/rust/nss-gk-api/src/err.rs | 257 |
1 files changed, 257 insertions, 0 deletions
diff --git a/third_party/rust/nss-gk-api/src/err.rs b/third_party/rust/nss-gk-api/src/err.rs new file mode 100644 index 0000000000..cc52b51f63 --- /dev/null +++ b/third_party/rust/nss-gk-api/src/err.rs @@ -0,0 +1,257 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(dead_code)] +#![allow(clippy::upper_case_acronyms)] + +use std::os::raw::c_char; +use std::str::Utf8Error; + +use crate::nss_prelude::*; +use crate::prtypes::*; + +include!(concat!(env!("OUT_DIR"), "/nspr_error.rs")); +mod codes { + #![allow(non_snake_case)] + include!(concat!(env!("OUT_DIR"), "/nss_secerr.rs")); + include!(concat!(env!("OUT_DIR"), "/nss_sslerr.rs")); + include!(concat!(env!("OUT_DIR"), "/mozpkix.rs")); +} +pub use codes::mozilla_pkix_ErrorCode as mozpkix; +pub use codes::SECErrorCodes as sec; +pub use codes::SSLErrorCodes as ssl; +pub mod nspr { + include!(concat!(env!("OUT_DIR"), "/nspr_err.rs")); +} + +pub type Res<T> = Result<T, Error>; + +#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq)] +pub enum Error { + AeadError, + CertificateLoading, + CipherInitFailure, + CreateSslSocket, + EchRetry(Vec<u8>), + HkdfError, + InternalError, + IntegerOverflow, + InvalidEpoch, + MixedHandshakeMethod, + NoDataAvailable, + NssError { + name: String, + code: PRErrorCode, + desc: String, + }, + OverrunError, + SelfEncryptFailure, + StringError, + TimeTravelError, + UnsupportedCipher, + UnsupportedVersion, +} + +impl Error { + pub(crate) fn last_nss_error() -> Self { + Self::from(unsafe { PR_GetError() }) + } +} + +impl std::error::Error for Error { + #[must_use] + fn cause(&self) -> Option<&dyn std::error::Error> { + None + } + #[must_use] + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + None + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Error: {:?}", self) + } +} + +impl From<std::num::TryFromIntError> for Error { + #[must_use] + fn from(_: std::num::TryFromIntError) -> Self { + Self::IntegerOverflow + } +} +impl From<std::ffi::NulError> for Error { + #[must_use] + fn from(_: std::ffi::NulError) -> Self { + Self::InternalError + } +} +impl From<Utf8Error> for Error { + fn from(_: Utf8Error) -> Self { + Self::StringError + } +} +impl From<PRErrorCode> for Error { + fn from(code: PRErrorCode) -> Self { + let name = wrap_str_fn(|| unsafe { PR_ErrorToName(code) }, "UNKNOWN_ERROR"); + let desc = wrap_str_fn( + || unsafe { PR_ErrorToString(code, PR_LANGUAGE_I_DEFAULT) }, + "...", + ); + Self::NssError { name, code, desc } + } +} + +use std::ffi::CStr; + +fn wrap_str_fn<F>(f: F, dflt: &str) -> String +where + F: FnOnce() -> *const c_char, +{ + unsafe { + let p = f(); + if p.is_null() { + return dflt.to_string(); + } + CStr::from_ptr(p).to_string_lossy().into_owned() + } +} + +pub fn is_blocked(result: &Res<()>) -> bool { + match result { + Err(Error::NssError { code, .. }) => *code == nspr::PR_WOULD_BLOCK_ERROR, + _ => false, + } +} + +pub trait IntoResult +{ + /// The `Ok` type for the result. + type Ok; + + /// Unsafe in our implementors because they take a pointer and have no way + /// to ensure that the pointer is valid. An invalid pointer could cause UB + /// in `impl Drop for Scoped`. + unsafe fn into_result(self) -> Result<Self::Ok, Error>; +} + +pub unsafe fn into_result<P>(ptr: *mut P) -> Result<*mut P, Error> { + if ptr.is_null() { + Err(Error::last_nss_error()) + } else { + Ok(ptr) + } +} + +// This can be used to implement `IntoResult` for pointer types that do not make +// sense as smart pointers. For smart pointers use `scoped_ptr!`. +macro_rules! impl_into_result { + ($pointer:ty) => { + impl $crate::err::IntoResult for *mut $pointer { + type Ok = *mut $pointer; + + unsafe fn into_result(self) -> Result<Self::Ok, $crate::err::Error> { + $crate::err::into_result(self) + } + } + } +} + +impl IntoResult for SECStatus { + type Ok = (); + + unsafe fn into_result(self) -> Result<(), Error> { + if self == SECSuccess { + Ok(()) + } else { + Err(Error::last_nss_error()) + } + } +} + +pub fn secstatus_to_res(code: SECStatus) -> Res<()> { + // Unsafe in the trait, but this impl should be safe. + unsafe { SECStatus::into_result(code) } +} + +#[cfg(test)] +mod tests { + use crate::err::{self, is_blocked, secstatus_to_res, Error, PRErrorCode, PR_SetError}; + use crate::ssl::{SECFailure, SECSuccess}; + use test_fixture::fixture_init; + + fn set_error_code(code: PRErrorCode) { + // This code doesn't work without initializing NSS first. + fixture_init(); + unsafe { + PR_SetError(code, 0); + } + } + + #[test] + fn error_code() { + fixture_init(); + assert_eq!(15 - 0x3000, err::ssl::SSL_ERROR_BAD_MAC_READ); + assert_eq!(166 - 0x2000, err::sec::SEC_ERROR_LIBPKIX_INTERNAL); + assert_eq!(-5998, err::nspr::PR_WOULD_BLOCK_ERROR); + } + + #[test] + fn is_ok() { + assert!(secstatus_to_res(SECSuccess).is_ok()); + } + + #[test] + fn is_err() { + set_error_code(err::ssl::SSL_ERROR_BAD_MAC_READ); + let r = secstatus_to_res(SECFailure); + assert!(r.is_err()); + match r.unwrap_err() { + Error::NssError { name, code, desc } => { + assert_eq!(name, "SSL_ERROR_BAD_MAC_READ"); + assert_eq!(code, -12273); + assert_eq!( + desc, + "SSL received a record with an incorrect Message Authentication Code." + ); + } + _ => unreachable!(), + } + } + + #[test] + fn is_err_zero_code() { + set_error_code(0); + let r = secstatus_to_res(SECFailure); + assert!(r.is_err()); + match r.unwrap_err() { + Error::NssError { name, code, .. } => { + assert_eq!(name, "UNKNOWN_ERROR"); + assert_eq!(code, 0); + // Note that we don't test |desc| here because that comes from + // strerror(0), which is platform-dependent. + } + _ => unreachable!(), + } + } + + #[test] + fn blocked() { + set_error_code(err::nspr::PR_WOULD_BLOCK_ERROR); + let r = secstatus_to_res(SECFailure); + assert!(r.is_err()); + assert!(is_blocked(&r)); + match r.unwrap_err() { + Error::NssError { name, code, desc } => { + assert_eq!(name, "PR_WOULD_BLOCK_ERROR"); + assert_eq!(code, -5998); + assert_eq!(desc, "The operation would have blocked"); + } + _ => panic!("bad error type"), + } + } +} |