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
pub use self::SslError::*;
pub use self::OpensslError::*;

use libc::c_ulong;
use std::error;
use std::ffi::c_str_to_bytes;
use std::io::IoError;

use ffi;

/// An SSL error
#[derive(Show, Clone, PartialEq, Eq)]
pub enum SslError {
    /// The underlying stream reported an error
    StreamError(IoError),
    /// The SSL session has been closed by the other end
    SslSessionClosed,
    /// An error in the OpenSSL library
    OpenSslErrors(Vec<OpensslError>)
}

impl error::Error for SslError {
    fn description(&self) -> &str {
        match *self {
            StreamError(_) => "The underlying stream reported an error",
            SslSessionClosed => "The SSL session has been closed by the other end",
            OpenSslErrors(_) => "An error in the OpenSSL library",
        }
    }

    fn cause(&self) -> Option<&error::Error> {
        match *self {
            StreamError(ref err) => Some(err as &error::Error),
            _ => None
        }
    }
}

/// An error from the OpenSSL library
#[derive(Show, Clone, PartialEq, Eq)]
pub enum OpensslError {
    /// An unknown error
    UnknownError {
        /// The library reporting the error
        library: String,
        /// The function reporting the error
        function: String,
        /// The reason for the error
        reason: String
    }
}

fn get_lib(err: c_ulong) -> String {
    unsafe {
        let bytes = c_str_to_bytes(&ffi::ERR_lib_error_string(err)).to_vec();
        String::from_utf8(bytes).unwrap()
    }
}

fn get_func(err: c_ulong) -> String {
    unsafe {
        let bytes = c_str_to_bytes(&ffi::ERR_func_error_string(err)).to_vec();
        String::from_utf8(bytes).unwrap()
    }
}

fn get_reason(err: c_ulong) -> String {
    unsafe {
        let bytes = c_str_to_bytes(&ffi::ERR_reason_error_string(err)).to_vec();
        String::from_utf8(bytes).unwrap()
    }
}

impl SslError {
    /// Creates a new `OpenSslErrors` with the current contents of the error
    /// stack.
    pub fn get() -> SslError {
        let mut errs = vec!();
        loop {
            match unsafe { ffi::ERR_get_error() } {
                0 => break,
                err => errs.push(SslError::from_error_code(err))
            }
        }
        OpenSslErrors(errs)
    }

    /// Creates an `SslError` from the raw numeric error code.
    pub fn from_error(err: c_ulong) -> SslError {
        OpenSslErrors(vec![SslError::from_error_code(err)])
    }

    fn from_error_code(err: c_ulong) -> OpensslError {
        ffi::init();
        UnknownError {
            library: get_lib(err),
            function: get_func(err),
            reason: get_reason(err)
        }
    }
}

#[test]
fn test_uknown_error_should_have_correct_messages() {
    let errs = match SslError::from_error(336032784) {
        OpenSslErrors(errs) => errs,
        _ => panic!("This should always be an `OpenSslErrors` variant.")
    };

    let UnknownError { ref library, ref function, ref reason } = errs[0];

    assert_eq!(library.as_slice(), "SSL routines");
    assert_eq!(function.as_slice(), "SSL23_GET_SERVER_HELLO");
    assert_eq!(reason.as_slice(), "sslv3 alert handshake failure");
}