Skip to main content

aocl_securerng/
lib.rs

1//! Safe wrappers for AOCL-SecureRNG.
2//!
3//! Wraps the AMD secure-RNG entry points around the x86 `RDRAND`
4//! (already conditioned, suitable for general-purpose random) and
5//! `RDSEED` (raw entropy, suitable for seeding cryptographic generators)
6//! instructions.
7
8#![warn(missing_debug_implementations)]
9#![cfg_attr(docsrs, feature(doc_cfg))]
10
11pub use aocl_error::{Error, Result};
12use aocl_securerng_sys as sys;
13
14const SECRNG_SUCCESS: i32 = 2;
15const SECRNG_SUPPORTED: i32 = 1;
16const SECRNG_NOT_SUPPORTED: i32 = -1;
17const SECRNG_FAILURE: i32 = -2;
18const SECRNG_INVALID_INPUT: i32 = -3;
19
20/// Default retry budget AOCL recommends for transient failures of `RDRAND` /
21/// `RDSEED`.
22pub const DEFAULT_RETRY_COUNT: u32 = 10;
23
24fn check_status(component: &'static str, raw: i32) -> Result<()> {
25    if raw == SECRNG_SUCCESS {
26        return Ok(());
27    }
28    let message = match raw {
29        SECRNG_NOT_SUPPORTED => "instruction not supported on this CPU",
30        SECRNG_FAILURE => "RNG instruction failed after all retries",
31        SECRNG_INVALID_INPUT => "invalid input",
32        _ => "unknown SecureRNG status",
33    }
34    .to_string();
35    Err(Error::Status {
36        component,
37        code: raw as i64,
38        message,
39    })
40}
41
42/// Returns `true` if the host CPU exposes the `RDRAND` instruction.
43pub fn is_rdrand_supported() -> bool {
44    unsafe { sys::is_RDRAND_supported() == SECRNG_SUPPORTED }
45}
46
47/// Returns `true` if the host CPU exposes the `RDSEED` instruction.
48pub fn is_rdseed_supported() -> bool {
49    unsafe { sys::is_RDSEED_supported() == SECRNG_SUPPORTED }
50}
51
52/// Read a single random `u16` from `RDRAND`.
53pub fn rdrand_u16(retry_count: u32) -> Result<u16> {
54    let mut v: u16 = 0;
55    let raw = unsafe { sys::get_rdrand16u(&mut v, retry_count) };
56    check_status("securerng", raw)?;
57    Ok(v)
58}
59
60/// Read a single random `u32` from `RDRAND`.
61pub fn rdrand_u32(retry_count: u32) -> Result<u32> {
62    let mut v: u32 = 0;
63    let raw = unsafe { sys::get_rdrand32u(&mut v, retry_count) };
64    check_status("securerng", raw)?;
65    Ok(v)
66}
67
68/// Read a single random `u64` from `RDRAND`.
69pub fn rdrand_u64(retry_count: u32) -> Result<u64> {
70    let mut v: u64 = 0;
71    let raw = unsafe { sys::get_rdrand64u(&mut v, retry_count) };
72    check_status("securerng", raw)?;
73    Ok(v)
74}
75
76/// Read a single random `u16` from `RDSEED`.
77pub fn rdseed_u16(retry_count: u32) -> Result<u16> {
78    let mut v: u16 = 0;
79    let raw = unsafe { sys::get_rdseed16u(&mut v, retry_count) };
80    check_status("securerng", raw)?;
81    Ok(v)
82}
83
84/// Read a single random `u32` from `RDSEED`.
85pub fn rdseed_u32(retry_count: u32) -> Result<u32> {
86    let mut v: u32 = 0;
87    let raw = unsafe { sys::get_rdseed32u(&mut v, retry_count) };
88    check_status("securerng", raw)?;
89    Ok(v)
90}
91
92/// Read a single random `u64` from `RDSEED`.
93pub fn rdseed_u64(retry_count: u32) -> Result<u64> {
94    let mut v: u64 = 0;
95    let raw = unsafe { sys::get_rdseed64u(&mut v, retry_count) };
96    check_status("securerng", raw)?;
97    Ok(v)
98}
99
100/// Fill `out` with random bytes from `RDRAND`.
101pub fn rdrand_bytes(out: &mut [u8], retry_count: u32) -> Result<()> {
102    if out.is_empty() {
103        return Ok(());
104    }
105    let n: u32 = out.len().try_into().map_err(|_| {
106        Error::InvalidArgument(format!(
107            "rdrand_bytes: length {} exceeds u32::MAX",
108            out.len()
109        ))
110    })?;
111    let raw = unsafe { sys::get_rdrand_bytes_arr(out.as_mut_ptr(), n, retry_count) };
112    check_status("securerng", raw)
113}
114
115/// Fill `out` with random bytes from `RDSEED`.
116pub fn rdseed_bytes(out: &mut [u8], retry_count: u32) -> Result<()> {
117    if out.is_empty() {
118        return Ok(());
119    }
120    let n: u32 = out.len().try_into().map_err(|_| {
121        Error::InvalidArgument(format!(
122            "rdseed_bytes: length {} exceeds u32::MAX",
123            out.len()
124        ))
125    })?;
126    let raw = unsafe { sys::get_rdseed_bytes_arr(out.as_mut_ptr(), n, retry_count) };
127    check_status("securerng", raw)
128}
129
130/// Fill `out` with random `u32`s from `RDRAND`.
131pub fn rdrand_u32_array(out: &mut [u32], retry_count: u32) -> Result<()> {
132    if out.is_empty() {
133        return Ok(());
134    }
135    let n: u32 = out.len().try_into().map_err(|_| {
136        Error::InvalidArgument(format!(
137            "rdrand_u32_array: length {} exceeds u32::MAX",
138            out.len()
139        ))
140    })?;
141    let raw = unsafe { sys::get_rdrand32u_arr(out.as_mut_ptr(), n, retry_count) };
142    check_status("securerng", raw)
143}
144
145/// Fill `out` with random `u64`s from `RDRAND`.
146pub fn rdrand_u64_array(out: &mut [u64], retry_count: u32) -> Result<()> {
147    if out.is_empty() {
148        return Ok(());
149    }
150    let n: u32 = out.len().try_into().map_err(|_| {
151        Error::InvalidArgument(format!(
152            "rdrand_u64_array: length {} exceeds u32::MAX",
153            out.len()
154        ))
155    })?;
156    let raw = unsafe { sys::get_rdrand64u_arr(out.as_mut_ptr(), n, retry_count) };
157    check_status("securerng", raw)
158}
159
160/// Library version reported by AOCL-SecureRNG.
161pub fn version() -> Option<String> {
162    unsafe {
163        let p = sys::get_secrngversion();
164        if p.is_null() {
165            return None;
166        }
167        Some(std::ffi::CStr::from_ptr(p).to_string_lossy().into_owned())
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174
175    #[test]
176    fn cpu_support_query_is_consistent() {
177        let _ = is_rdrand_supported();
178        let _ = is_rdseed_supported();
179    }
180
181    #[test]
182    fn rdrand_yields_distinct_values_when_supported() {
183        if !is_rdrand_supported() {
184            eprintln!("RDRAND not supported on this CPU; skipping");
185            return;
186        }
187        let a = rdrand_u64(DEFAULT_RETRY_COUNT).unwrap();
188        let b = rdrand_u64(DEFAULT_RETRY_COUNT).unwrap();
189        assert_ne!(a, b, "RDRAND returned the same u64 twice");
190    }
191
192    #[test]
193    fn rdrand_bytes_fills_buffer() {
194        if !is_rdrand_supported() {
195            return;
196        }
197        let mut buf = [0u8; 32];
198        rdrand_bytes(&mut buf, DEFAULT_RETRY_COUNT).unwrap();
199        assert!(buf.iter().any(|&b| b != 0));
200    }
201
202    #[test]
203    fn empty_inputs_are_ok() {
204        let mut empty: [u8; 0] = [];
205        rdrand_bytes(&mut empty, DEFAULT_RETRY_COUNT).unwrap();
206        let mut empty32: [u32; 0] = [];
207        rdrand_u32_array(&mut empty32, DEFAULT_RETRY_COUNT).unwrap();
208    }
209
210    #[test]
211    fn version_string_is_non_empty() {
212        if let Some(v) = version() {
213            assert!(!v.is_empty());
214        }
215    }
216}