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
136
137
138
139
140
141
142
143
use core::{
    convert::TryFrom,
    fmt::{self, Display, Formatter},
};

/// Errors in this library
#[repr(u8)]
#[derive(Debug, Copy, Clone)]
pub enum ErrorCode {
    /// The hardware instruction is not supported
    UnsupportedInstruction,
    /// There was a hardware failure
    HardwareFailure,
}

impl ErrorCode {
    #[cfg(not(feature = "std"))]
    const fn as_randcore_code(self) -> core::num::NonZeroU32 {
        /// Arbitrary, off top of head bitmask for error codes that come from rdrand
        const RDRAND_TAG: u32 = rand_core::Error::CUSTOM_START + 0x3D34_7D00;
        unsafe { core::num::NonZeroU32::new_unchecked(RDRAND_TAG + self as u32) }
    }
}

#[cfg(not(feature = "std"))]
impl From<ErrorCode> for rand_core::Error {
    fn from(code: ErrorCode) -> rand_core::Error {
        code.as_randcore_code().into()
    }
}

#[cfg(feature = "std")]
impl From<ErrorCode> for rand_core::Error {
    fn from(code: ErrorCode) -> rand_core::Error {
        rand_core::Error::new(code)
    }
}

#[cfg(feature = "std")]
impl std::error::Error for ErrorCode {}

impl Display for ErrorCode {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        f.write_str(match self {
            ErrorCode::UnsupportedInstruction => "the hardware instruction is not supported",
            ErrorCode::HardwareFailure => "hardware generator failure",
        })
    }
}

#[derive(Copy, Clone, Debug)]
pub struct NotAnErrorCode;

#[cfg(feature = "std")]
impl std::error::Error for NotAnErrorCode {}

impl Display for NotAnErrorCode {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        f.write_str("the error is not an rdrand error")
    }
}

impl TryFrom<&rand_core::Error> for ErrorCode {
    type Error = NotAnErrorCode;
    #[cfg(feature = "std")]
    fn try_from(error: &rand_core::Error) -> Result<Self, Self::Error> {
        error
            .inner()
            .downcast_ref::<ErrorCode>()
            .copied()
            .ok_or(NotAnErrorCode)
    }
    #[cfg(not(feature = "std"))]
    fn try_from(error: &rand_core::Error) -> Result<Self, Self::Error> {
        let code = error.code().ok_or(NotAnErrorCode)?;
        if code == ErrorCode::UnsupportedInstruction.as_randcore_code() {
            Ok(ErrorCode::UnsupportedInstruction)
        } else if code == ErrorCode::HardwareFailure.as_randcore_code() {
            Ok(ErrorCode::HardwareFailure)
        } else {
            Err(NotAnErrorCode)
        }
    }
}

impl TryFrom<rand_core::Error> for ErrorCode {
    type Error = NotAnErrorCode;
    fn try_from(error: rand_core::Error) -> Result<Self, Self::Error> {
        <ErrorCode as TryFrom<&rand_core::Error>>::try_from(&error)
    }
}

#[cfg(test)]
mod test {
    use super::ErrorCode;
    use core::convert::TryInto;
    use rand_core::Error;

    #[test]
    fn error_code_send() {
        fn assert_send<T: Send>() {}
        assert_send::<ErrorCode>();
    }

    #[test]
    fn error_code_sync() {
        fn assert_sync<T: Sync>() {}
        assert_sync::<ErrorCode>();
    }

    #[test]
    fn error_code_copy() {
        fn assert_copy<T: Copy>() {}
        assert_copy::<ErrorCode>();
    }

    #[test]
    fn error_code_clone() {
        fn assert_clone<T: Clone>() {}
        assert_clone::<ErrorCode>();
    }

    #[test]
    #[cfg(feature = "std")]
    fn error_code_error() {
        fn assert_error<T: std::error::Error>() {}
        assert_error::<ErrorCode>();
    }

    #[test]
    fn conversion_roundtrip_unsupported_hardware() {
        let core_rand: Error = ErrorCode::UnsupportedInstruction.into();
        let code: ErrorCode = core_rand.try_into().expect("should convert back");
        assert!(matches!(code, ErrorCode::UnsupportedInstruction));
    }

    #[test]
    fn conversion_roundtrip_hardware_failure() {
        let core_rand: Error = ErrorCode::HardwareFailure.into();
        let code: ErrorCode = core_rand.try_into().expect("should convert back");
        assert!(matches!(code, ErrorCode::HardwareFailure));
    }
}