summaryrefslogtreecommitdiffstats
path: root/third_party/rust/neqo-crypto/src/result.rs
blob: e304fcea7f06804307b4daf177f51016790001dd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// 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.

use crate::{
    err::{nspr, Error, PR_ErrorToName, PR_ErrorToString, PR_GetError, Res, PR_LANGUAGE_I_DEFAULT},
    ssl,
};

use std::ffi::CStr;

pub fn result(rv: ssl::SECStatus) -> Res<()> {
    _ = result_helper(rv, false)?;
    Ok(())
}

pub fn result_or_blocked(rv: ssl::SECStatus) -> Res<bool> {
    result_helper(rv, true)
}

fn wrap_str_fn<F>(f: F, dflt: &str) -> String
where
    F: FnOnce() -> *const i8,
{
    unsafe {
        let p = f();
        if p.is_null() {
            return dflt.to_string();
        }
        CStr::from_ptr(p).to_string_lossy().into_owned()
    }
}

fn result_helper(rv: ssl::SECStatus, allow_blocked: bool) -> Res<bool> {
    if rv == ssl::_SECStatus_SECSuccess {
        return Ok(false);
    }

    let code = unsafe { PR_GetError() };
    if allow_blocked && code == nspr::PR_WOULD_BLOCK_ERROR {
        return Ok(true);
    }

    let name = wrap_str_fn(|| unsafe { PR_ErrorToName(code) }, "UNKNOWN_ERROR");
    let desc = wrap_str_fn(
        || unsafe { PR_ErrorToString(code, PR_LANGUAGE_I_DEFAULT) },
        "...",
    );
    Err(Error::NssError { name, code, desc })
}

#[cfg(test)]
mod tests {
    use super::{result, result_or_blocked};
    use crate::{
        err::{self, nspr, Error, PRErrorCode, PR_SetError},
        ssl,
    };
    use test_fixture::fixture_init;

    fn set_error_code(code: PRErrorCode) {
        unsafe { PR_SetError(code, 0) };
    }

    #[test]
    fn is_ok() {
        assert!(result(ssl::SECSuccess).is_ok());
    }

    #[test]
    fn is_err() {
        // This code doesn't work without initializing NSS first.
        fixture_init();

        set_error_code(err::ssl::SSL_ERROR_BAD_MAC_READ);
        let r = result(ssl::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() {
        // This code doesn't work without initializing NSS first.
        fixture_init();

        set_error_code(0);
        let r = result(ssl::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_as_error() {
        // This code doesn't work without initializing NSS first.
        fixture_init();

        set_error_code(nspr::PR_WOULD_BLOCK_ERROR);
        let r = result(ssl::SECFailure);
        assert!(r.is_err());
        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"),
        }
    }

    #[test]
    fn is_blocked() {
        set_error_code(nspr::PR_WOULD_BLOCK_ERROR);
        assert!(result_or_blocked(ssl::SECFailure).unwrap());
    }
}