Coverage Report

Created: 2026-05-18 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/git/checkouts/nss-rs-71e20fe79ef91440/9b94ca3/src/err.rs
Line
Count
Source
1
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
2
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
3
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
4
// option. This file may not be copied, modified, or distributed
5
// except according to those terms.
6
7
use std::{os::raw::c_char, str::Utf8Error};
8
9
use crate::nss_prelude::*;
10
11
include!(concat!(env!("OUT_DIR"), "/nspr_error.rs"));
12
#[expect(non_snake_case, dead_code, reason = "Code is bindgen-generated.")]
13
mod codes {
14
    include!(concat!(env!("OUT_DIR"), "/nss_secerr.rs"));
15
    include!(concat!(env!("OUT_DIR"), "/nss_sslerr.rs"));
16
}
17
pub use codes::{SECErrorCodes as sec, SSLErrorCodes as ssl};
18
use thiserror::Error;
19
20
#[expect(dead_code, reason = "Code is bindgen-generated.")]
21
pub mod nspr {
22
    include!(concat!(env!("OUT_DIR"), "/nspr_err.rs"));
23
}
24
25
#[cfg_attr(not(test), expect(dead_code, reason = "Some constants are not used."))]
26
pub mod mozpkix {
27
    // These are manually extracted from the many bindings generated
28
    // by bindgen when provided with the simple header:
29
    // #include "mozpkix/pkixnss.h"
30
31
    #[expect(non_camel_case_types, reason = "Code is bindgen-generated.")]
32
    pub type mozilla_pkix_ErrorCode = ::std::os::raw::c_int;
33
    pub const MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE: mozilla_pkix_ErrorCode = -16384;
34
    pub const MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY: mozilla_pkix_ErrorCode = -16383;
35
    pub const MOZILLA_PKIX_ERROR_INADEQUATE_KEY_SIZE: mozilla_pkix_ErrorCode = -16382;
36
    pub const MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA: mozilla_pkix_ErrorCode = -16381;
37
    pub const MOZILLA_PKIX_ERROR_NO_RFC822NAME_MATCH: mozilla_pkix_ErrorCode = -16380;
38
    pub const MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE: mozilla_pkix_ErrorCode = -16379;
39
    pub const MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE: mozilla_pkix_ErrorCode = -16378;
40
    pub const MOZILLA_PKIX_ERROR_SIGNATURE_ALGORITHM_MISMATCH: mozilla_pkix_ErrorCode = -16377;
41
    pub const MOZILLA_PKIX_ERROR_OCSP_RESPONSE_FOR_CERT_MISSING: mozilla_pkix_ErrorCode = -16376;
42
    pub const MOZILLA_PKIX_ERROR_VALIDITY_TOO_LONG: mozilla_pkix_ErrorCode = -16375;
43
    pub const MOZILLA_PKIX_ERROR_REQUIRED_TLS_FEATURE_MISSING: mozilla_pkix_ErrorCode = -16374;
44
    pub const MOZILLA_PKIX_ERROR_INVALID_INTEGER_ENCODING: mozilla_pkix_ErrorCode = -16373;
45
    pub const MOZILLA_PKIX_ERROR_EMPTY_ISSUER_NAME: mozilla_pkix_ErrorCode = -16372;
46
    pub const MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED: mozilla_pkix_ErrorCode =
47
        -16371;
48
    pub const MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT: mozilla_pkix_ErrorCode = -16370;
49
    pub const MOZILLA_PKIX_ERROR_MITM_DETECTED: mozilla_pkix_ErrorCode = -16369;
50
    pub const END_OF_LIST: mozilla_pkix_ErrorCode = -16368;
51
}
52
53
pub type Res<T> = Result<T, Error>;
54
55
#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Error)]
56
pub enum Error {
57
    #[error("AEAD error")]
58
    Aead,
59
    #[error("Aead truncated")]
60
    AeadTruncated,
61
    #[error("Certificate decoding error")]
62
    CertificateDecoding,
63
    #[error("Certificate encoding error")]
64
    CertificateEncoding,
65
    #[error("Certificate loading error")]
66
    CertificateLoading,
67
    #[error("Cipher initialization error")]
68
    CipherInit,
69
    #[error("Failed to create SSL socket")]
70
    CreateSslSocket,
71
    #[error("ECH error, retry needed")]
72
    EchRetry(Vec<u8>),
73
    #[error("HKDF error")]
74
    Hkdf,
75
    #[error("Internal error")]
76
    Internal,
77
    #[error("Integer overflow")]
78
    IntegerOverflow,
79
    #[error("Invalid ALPN")]
80
    InvalidAlpn,
81
    #[error("Invalid epoch")]
82
    InvalidEpoch,
83
    #[error("Invalid certificate compression ID")]
84
    InvalidCertificateCompressionID,
85
    #[error("Invalid input")]
86
    InvalidInput,
87
    #[error("Invalid state for this operation")]
88
    InvalidState,
89
    #[error("Mixed handshake method")]
90
    MixedHandshakeMethod,
91
    #[error("No data available")]
92
    NoDataAvailable,
93
    #[error("NSS error: {name} ({code}): {desc}")]
94
    Nss {
95
        name: String,
96
        code: PRErrorCode,
97
        desc: String,
98
    },
99
    #[error("Self encryption error")]
100
    SelfEncrypt,
101
    #[error("String conversion error")]
102
    String,
103
    #[error("Time travel detected")]
104
    TimeTravel,
105
    #[error("Unsupported cipher")]
106
    UnsupportedCipher,
107
    #[error("Unsupported curve")]
108
    UnsupportedCurve,
109
    #[error("Unsupported hash")]
110
    UnsupportedHash,
111
    #[error("Unsupported version")]
112
    UnsupportedVersion,
113
}
114
115
impl Error {
116
2.28k
    pub(crate) fn last_nss_error() -> Self {
117
2.28k
        Self::from(unsafe { PR_GetError() })
118
2.28k
    }
119
}
120
121
impl From<std::num::TryFromIntError> for Error {
122
0
    fn from(_: std::num::TryFromIntError) -> Self {
123
0
        Self::IntegerOverflow
124
0
    }
125
}
126
impl From<std::ffi::NulError> for Error {
127
0
    fn from(_: std::ffi::NulError) -> Self {
128
0
        Self::Internal
129
0
    }
130
}
131
impl From<Utf8Error> for Error {
132
0
    fn from(_: Utf8Error) -> Self {
133
0
        Self::String
134
0
    }
135
}
136
impl From<PRErrorCode> for Error {
137
2.42k
    fn from(code: PRErrorCode) -> Self {
138
2.42k
        let name = wrap_str_fn(|| unsafe { PR_ErrorToName(code) }, "UNKNOWN_ERROR");
139
2.42k
        let desc = wrap_str_fn(
140
2.42k
            || unsafe { PR_ErrorToString(code, PR_LANGUAGE_I_DEFAULT) },
141
2.42k
            "...",
142
        );
143
2.42k
        Self::Nss { name, code, desc }
144
2.42k
    }
145
}
146
147
use std::ffi::CStr;
148
149
4.85k
fn wrap_str_fn<F>(f: F, dflt: &str) -> String
150
4.85k
where
151
4.85k
    F: FnOnce() -> *const c_char,
152
{
153
    unsafe {
154
4.85k
        let p = f();
155
4.85k
        if p.is_null() {
156
0
            return dflt.to_string();
157
4.85k
        }
158
4.85k
        CStr::from_ptr(p).to_string_lossy().into_owned()
159
    }
160
4.85k
}
nss_rs::err::wrap_str_fn::<<nss_rs::err::Error as core::convert::From<i32>>::from::{closure#0}>
Line
Count
Source
149
2.42k
fn wrap_str_fn<F>(f: F, dflt: &str) -> String
150
2.42k
where
151
2.42k
    F: FnOnce() -> *const c_char,
152
{
153
    unsafe {
154
2.42k
        let p = f();
155
2.42k
        if p.is_null() {
156
0
            return dflt.to_string();
157
2.42k
        }
158
2.42k
        CStr::from_ptr(p).to_string_lossy().into_owned()
159
    }
160
2.42k
}
nss_rs::err::wrap_str_fn::<<nss_rs::err::Error as core::convert::From<i32>>::from::{closure#1}>
Line
Count
Source
149
2.42k
fn wrap_str_fn<F>(f: F, dflt: &str) -> String
150
2.42k
where
151
2.42k
    F: FnOnce() -> *const c_char,
152
{
153
    unsafe {
154
2.42k
        let p = f();
155
2.42k
        if p.is_null() {
156
0
            return dflt.to_string();
157
2.42k
        }
158
2.42k
        CStr::from_ptr(p).to_string_lossy().into_owned()
159
    }
160
2.42k
}
161
162
1.77k
pub const fn is_blocked(result: &Res<()>) -> bool {
163
1.77k
    match result {
164
1.77k
        Err(Error::Nss { code, .. }) => *code == nspr::PR_WOULD_BLOCK_ERROR,
165
0
        _ => false,
166
    }
167
1.77k
}
168
169
pub trait IntoResult {
170
    /// The `Ok` type for the result.
171
    type Ok;
172
173
    /// Unsafe in our implementors because they take a pointer and have no way
174
    /// to ensure that the pointer is valid. An invalid pointer could cause UB
175
    /// in `impl Drop for Scoped`.
176
    fn into_result(self) -> Result<Self::Ok, Error>;
177
}
178
179
128k
pub fn into_result<P>(ptr: *mut P) -> Result<*mut P, Error> {
180
128k
    if ptr.is_null() {
181
0
        Err(Error::last_nss_error())
182
    } else {
183
128k
        Ok(ptr)
184
    }
185
128k
}
Unexecuted instantiation: nss_rs::err::into_result::<nss_rs::nss_prelude::SECItemStr>
Unexecuted instantiation: nss_rs::err::into_result::<nss_rs::nss_prelude::SECItemArrayStr>
nss_rs::err::into_result::<nss_rs::replay::SSLAntiReplayContext>
Line
Count
Source
179
1.28k
pub fn into_result<P>(ptr: *mut P) -> Result<*mut P, Error> {
180
1.28k
    if ptr.is_null() {
181
0
        Err(Error::last_nss_error())
182
    } else {
183
1.28k
        Ok(ptr)
184
    }
185
1.28k
}
nss_rs::err::into_result::<nss_rs::p11::nss_p11::PK11SymKeyStr>
Line
Count
Source
179
72.7k
pub fn into_result<P>(ptr: *mut P) -> Result<*mut P, Error> {
180
72.7k
    if ptr.is_null() {
181
0
        Err(Error::last_nss_error())
182
    } else {
183
72.7k
        Ok(ptr)
184
    }
185
72.7k
}
Unexecuted instantiation: nss_rs::err::into_result::<nss_rs::p11::nss_p11::SECOidDataStr>
nss_rs::err::into_result::<nss_rs::p11::nss_p11::PK11ContextStr>
Line
Count
Source
179
32.4k
pub fn into_result<P>(ptr: *mut P) -> Result<*mut P, Error> {
180
32.4k
    if ptr.is_null() {
181
0
        Err(Error::last_nss_error())
182
    } else {
183
32.4k
        Ok(ptr)
184
    }
185
32.4k
}
Unexecuted instantiation: nss_rs::err::into_result::<nss_rs::p11::nss_p11::CERTCertListStr>
nss_rs::err::into_result::<nss_rs::p11::nss_p11::PK11SlotInfoStr>
Line
Count
Source
179
19.2k
pub fn into_result<P>(ptr: *mut P) -> Result<*mut P, Error> {
180
19.2k
    if ptr.is_null() {
181
0
        Err(Error::last_nss_error())
182
    } else {
183
19.2k
        Ok(ptr)
184
    }
185
19.2k
}
nss_rs::err::into_result::<nss_rs::p11::nss_p11::CERTCertificateStr>
Line
Count
Source
179
1.28k
pub fn into_result<P>(ptr: *mut P) -> Result<*mut P, Error> {
180
1.28k
    if ptr.is_null() {
181
0
        Err(Error::last_nss_error())
182
    } else {
183
1.28k
        Ok(ptr)
184
    }
185
1.28k
}
Unexecuted instantiation: nss_rs::err::into_result::<nss_rs::p11::nss_p11::SECKEYPublicKeyStr>
nss_rs::err::into_result::<nss_rs::p11::nss_p11::SECKEYPrivateKeyStr>
Line
Count
Source
179
1.28k
pub fn into_result<P>(ptr: *mut P) -> Result<*mut P, Error> {
180
1.28k
    if ptr.is_null() {
181
0
        Err(Error::last_nss_error())
182
    } else {
183
1.28k
        Ok(ptr)
184
    }
185
1.28k
}
Unexecuted instantiation: nss_rs::err::into_result::<nss_rs::p11::nss_p11::CERTSubjectPublicKeyInfoStr>
186
187
// This can be used to implement `IntoResult` for pointer types that do not make
188
// sense as smart pointers. For smart pointers use `scoped_ptr!`.
189
macro_rules! impl_into_result {
190
    ($pointer:ty) => {
191
        impl $crate::err::IntoResult for *mut $pointer {
192
            type Ok = *mut $pointer;
193
194
0
            fn into_result(self) -> Result<Self::Ok, $crate::err::Error> {
195
0
                $crate::err::into_result(self)
196
0
            }
197
        }
198
    };
199
}
200
201
impl IntoResult for SECStatus {
202
    type Ok = ();
203
204
328k
    fn into_result(self) -> Result<(), Error> {
205
328k
        if self == SECSuccess {
206
326k
            Ok(())
207
        } else {
208
2.28k
            Err(Error::last_nss_error())
209
        }
210
328k
    }
211
}
212
213
328k
pub fn secstatus_to_res(code: SECStatus) -> Res<()> {
214
328k
    SECStatus::into_result(code)
215
328k
}
216
217
#[cfg(test)]
218
#[cfg_attr(coverage_nightly, coverage(off))]
219
mod tests {
220
    use test_fixture::fixture_init;
221
222
    use crate::{
223
        err::{self, Error, PR_SetError, PRErrorCode, is_blocked, secstatus_to_res},
224
        ssl::{SECFailure, SECSuccess},
225
    };
226
227
    fn set_error_code(code: PRErrorCode) {
228
        // This code doesn't work without initializing NSS first.
229
        fixture_init();
230
        unsafe {
231
            PR_SetError(code, 0);
232
        }
233
    }
234
235
    #[test]
236
    fn error_code() {
237
        fixture_init();
238
        assert_eq!(15 - 0x3000, err::ssl::SSL_ERROR_BAD_MAC_READ);
239
        assert_eq!(166 - 0x2000, err::sec::SEC_ERROR_LIBPKIX_INTERNAL);
240
        assert_eq!(-5998, err::nspr::PR_WOULD_BLOCK_ERROR);
241
    }
242
243
    #[test]
244
    fn is_ok() {
245
        assert!(secstatus_to_res(SECSuccess).is_ok());
246
    }
247
248
    #[test]
249
    fn is_err() {
250
        set_error_code(err::ssl::SSL_ERROR_BAD_MAC_READ);
251
        let r = secstatus_to_res(SECFailure);
252
        assert!(r.is_err());
253
        match r.unwrap_err() {
254
            Error::Nss { name, code, desc } => {
255
                assert_eq!(name, "SSL_ERROR_BAD_MAC_READ");
256
                assert_eq!(code, -12273);
257
                assert_eq!(
258
                    desc,
259
                    "SSL received a record with an incorrect Message Authentication Code."
260
                );
261
            }
262
            _ => unreachable!(),
263
        }
264
    }
265
266
    #[test]
267
    fn is_err_zero_code() {
268
        set_error_code(0);
269
        let r = secstatus_to_res(SECFailure);
270
        assert!(r.is_err());
271
        match r.unwrap_err() {
272
            Error::Nss { name, code, .. } => {
273
                assert_eq!(name, "UNKNOWN_ERROR");
274
                assert_eq!(code, 0);
275
                // Note that we don't test |desc| here because that comes from
276
                // strerror(0), which is platform-dependent.
277
            }
278
            _ => unreachable!(),
279
        }
280
    }
281
282
    #[test]
283
    fn blocked() {
284
        set_error_code(err::nspr::PR_WOULD_BLOCK_ERROR);
285
        let r = secstatus_to_res(SECFailure);
286
        assert!(r.is_err());
287
        assert!(is_blocked(&r));
288
        match r.unwrap_err() {
289
            Error::Nss { name, code, desc } => {
290
                assert_eq!(name, "PR_WOULD_BLOCK_ERROR");
291
                assert_eq!(code, -5998);
292
                assert_eq!(desc, "The operation would have blocked");
293
            }
294
            _ => panic!("bad error type"),
295
        }
296
    }
297
298
    #[test]
299
    #[expect(invalid_from_utf8, reason = "Testing error conversion.")]
300
    fn error_from_std_errors() {
301
        use std::ffi::CString;
302
        assert_eq!(
303
            Error::from(CString::new("a\0b").unwrap_err()),
304
            Error::Internal
305
        );
306
        assert_eq!(
307
            Error::from(std::str::from_utf8(&[0xff]).unwrap_err()),
308
            Error::String
309
        );
310
    }
311
312
    #[test]
313
    fn mozpkix_error_codes_are_negative() {
314
        use crate::err::mozpkix::*;
315
        let codes = [
316
            MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE,
317
            MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY,
318
            MOZILLA_PKIX_ERROR_INADEQUATE_KEY_SIZE,
319
            MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA,
320
            MOZILLA_PKIX_ERROR_NO_RFC822NAME_MATCH,
321
            MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE,
322
            MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE,
323
            MOZILLA_PKIX_ERROR_SIGNATURE_ALGORITHM_MISMATCH,
324
            MOZILLA_PKIX_ERROR_OCSP_RESPONSE_FOR_CERT_MISSING,
325
            MOZILLA_PKIX_ERROR_VALIDITY_TOO_LONG,
326
            MOZILLA_PKIX_ERROR_REQUIRED_TLS_FEATURE_MISSING,
327
            MOZILLA_PKIX_ERROR_INVALID_INTEGER_ENCODING,
328
            MOZILLA_PKIX_ERROR_EMPTY_ISSUER_NAME,
329
            MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED,
330
            MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT,
331
            MOZILLA_PKIX_ERROR_MITM_DETECTED,
332
            END_OF_LIST,
333
        ];
334
        for (i, &code) in codes.iter().enumerate() {
335
            assert_eq!(
336
                code,
337
                -16384 + i32::try_from(i).unwrap(),
338
                "mozpkix code at index {i}"
339
            );
340
        }
341
    }
342
}