Revision control

Copy as Markdown

Other Tools

// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// <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,
AeadTruncated,
CertificateLoading,
CipherInitFailure,
CreateSslSocket,
EchRetry(Vec<u8>),
HkdfError,
InternalError,
IntegerOverflow,
InvalidEpoch,
InvalidInput,
MixedHandshakeMethod,
NoDataAvailable,
NssError {
name: String,
code: PRErrorCode,
desc: String,
},
OverrunError,
SelfEncryptFailure,
StringError,
TimeTravelError,
UnsupportedCipher,
UnsupportedCurve,
UnsupportedHash,
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"),
}
}
}