diff --git a/Cargo.toml b/Cargo.toml index d79a95d0..302ea636 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,23 @@ [package] name = "getrandom" -version = "0.0.0" +version = "0.1.0" authors = ["The Rand Project Developers"] -license = "MIT/Apache-2.0" -edition = "2015" +license = "MIT OR Apache-2.0" description = "A small cross-platform library to securely get random data (entropy)" -[dependencies] +[badges] +travis-ci = { repository = "rust-random/getrandom" } +appveyor = { repository = "rust-random/getrandom" } + +[target.'cfg(unix)'.dependencies] +libc = "0.2" + +[target.'cfg(windows)'.dependencies] +winapi = { version = "0.3", features = ["minwindef", "ntsecapi", "winnt"] } + +[target.'cfg(fuchsia)'.dependencies] +fuchsia-cprng = "0.1" + +[target.wasm32-unknown-unknown.dependencies] +wasm-bindgen = { version = "0.2.12", optional = true } +stdweb = { version = "0.4", optional = true } diff --git a/benches/mod.rs b/benches/mod.rs new file mode 100644 index 00000000..cf9747aa --- /dev/null +++ b/benches/mod.rs @@ -0,0 +1,24 @@ +#![feature(test)] +extern crate test; +extern crate getrandom; + +#[bench] +fn bench_64(b: &mut test::Bencher) { + let mut buf = [0u8; 64]; + b.iter(|| { + getrandom::getrandom(&mut buf[..]).unwrap(); + test::black_box(&buf); + }); + b.bytes = buf.len() as u64; +} + +#[bench] +fn bench_65536(b: &mut test::Bencher) { + let mut buf = [0u8; 65536]; + b.iter(|| { + getrandom::getrandom(&mut buf[..]).unwrap(); + test::black_box(&buf); + }); + b.bytes = buf.len() as u64; +} + diff --git a/src/cloudabi.rs b/src/cloudabi.rs new file mode 100644 index 00000000..77515050 --- /dev/null +++ b/src/cloudabi.rs @@ -0,0 +1,23 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +use error::Error; + +extern "C" { + fn cloudabi_sys_random_get(buf: *mut u8, len: usize) -> u16; +} + +pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> { + let errno = unsafe { cloudabi_sys_random_get(dest.as_ptr(), dest.len()) }; + if errno == 0 { + Ok(()) + } else { + Err(Error(unsafe { + NonZeroU32::new_unchecked(errno as u32) + })) + } +} diff --git a/src/dragonfly_haiku.rs b/src/dragonfly_haiku.rs new file mode 100644 index 00000000..7691616e --- /dev/null +++ b/src/dragonfly_haiku.rs @@ -0,0 +1,25 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation for DragonFly / Haiku +use super::Error; +use super::utils::use_init; +use std::fs::File; +use std::io::Read; +use std::cell::RefCell; + +thread_local!(static RNG_FILE: RefCell> = RefCell::new(None)); + +pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> { + RNG_FILE.with(|f| { + use_init(f, + || File::open("/dev/random").map_err(From::from), + |f| f.read_exact(dest).map_err(From::from), + ) + }) +} diff --git a/src/dummy.rs b/src/dummy.rs new file mode 100644 index 00000000..41fa39fd --- /dev/null +++ b/src/dummy.rs @@ -0,0 +1,15 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! A dummy implementation for unsupported targets which always returns +//! `Err(UNAVAILABLE_ERROR)` +use super::UNAVAILABLE_ERROR; + +pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> { + Err(UNAVAILABLE_ERROR) +} diff --git a/src/emscripten.rs b/src/emscripten.rs new file mode 100644 index 00000000..446e4634 --- /dev/null +++ b/src/emscripten.rs @@ -0,0 +1,31 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation for Emscripten +use super::Error; +use std::fs::File; +use std::io::Read; +use std::cell::RefCell; +use super::utils::use_init; + +thread_local!(static RNG_FILE: RefCell> = RefCell::new(None)); + +pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> { + // `Crypto.getRandomValues` documents `dest` should be at most 65536 + // bytes. `crypto.randomBytes` documents: "To minimize threadpool + // task length variation, partition large randomBytes requests when + // doing so as part of fulfilling a client request. + RNG_FILE.with(|f| { + use_init(f, || File::open("/dev/random").map_err(From::from), |f| { + for chunk in dest.chunks_mut(65536) { + f.read_exact(chunk)?; + } + Ok(()) + }) + }) +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 00000000..a6dd3d7e --- /dev/null +++ b/src/error.rs @@ -0,0 +1,67 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use core::num::NonZeroU32; +use core::convert::From; +use core::fmt; +#[cfg(not(target_env = "sgx"))] +use std::{io, error}; + +pub const UNKNOWN_ERROR: Error = Error(unsafe { + NonZeroU32::new_unchecked(0x756e6b6e) // "unkn" +}); + +pub const UNAVAILABLE_ERROR: Error = Error(unsafe { + NonZeroU32::new_unchecked(0x4e416e61) // "NAna" +}); + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct Error(NonZeroU32); + +impl Error { + pub fn code(&self) -> NonZeroU32 { + self.0 + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match *self { + UNKNOWN_ERROR => write!(f, "Getrandom Error: unknown"), + UNAVAILABLE_ERROR => write!(f, "Getrandom Error: unavailable"), + code => write!(f, "Getrandom Error: {}", code.0.get()), + } + } +} + +#[cfg(not(target_env = "sgx"))] +impl From for Error { + fn from(err: io::Error) -> Self { + err.raw_os_error() + .and_then(|code| NonZeroU32::new(code as u32)) + .map(|code| Error(code)) + // in practice this should never happen + .unwrap_or(UNKNOWN_ERROR) + } +} + +#[cfg(not(target_env = "sgx"))] +impl Into for Error { + fn into(self) -> io::Error { + match self { + UNKNOWN_ERROR => io::Error::new(io::ErrorKind::Other, + "getrandom error: unknown"), + UNAVAILABLE_ERROR => io::Error::new(io::ErrorKind::Other, + "getrandom error: entropy source is unavailable"), + code => io::Error::from_raw_os_error(code.0.get() as i32), + } + } +} + +#[cfg(not(target_env = "sgx"))] +impl error::Error for Error { } diff --git a/src/freebsd.rs b/src/freebsd.rs new file mode 100644 index 00000000..ac990f3f --- /dev/null +++ b/src/freebsd.rs @@ -0,0 +1,32 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation for FreeBSD +extern crate libc; + +use super::Error; +use core::ptr; +use std::io; + +pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> { + let mib = [libc::CTL_KERN, libc::KERN_ARND]; + // kern.arandom permits a maximum buffer size of 256 bytes + for chunk in dest.chunks_mut(256) { + let mut len = chunk.len(); + let ret = unsafe { + libc::sysctl( + mib.as_ptr(), mib.len() as libc::c_uint, + chunk.as_mut_ptr() as *mut _, &mut len, ptr::null(), 0, + ) + }; + if ret == -1 || len != chunk.len() { + return Err(io::Error::last_os_error().into()); + } + } + Ok(()) +} diff --git a/src/fuchsia.rs b/src/fuchsia.rs new file mode 100644 index 00000000..b152cde1 --- /dev/null +++ b/src/fuchsia.rs @@ -0,0 +1,17 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation for Fuchsia Zircon +extern crate fuchsia_cprng; + +use super::Error; + +pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> { + fuchsia_cprng::cprng_draw(dest); + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index cb7eb8bb..104b5884 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,3 +5,98 @@ // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. +#![no_std] + +#[cfg(not(target_env = "sgx"))] +#[macro_use] extern crate std; + +#[cfg(any( + target_os = "android", + target_os = "netbsd", + target_os = "solaris", + target_os = "redox", + target_os = "dragonfly", + target_os = "haiku", + target_os = "emscripten", + target_os = "linux", +))] +mod utils; +mod error; +pub use error::{Error, UNKNOWN_ERROR, UNAVAILABLE_ERROR}; + +macro_rules! mod_use { + ($cond:meta, $module:ident) => { + #[$cond] + mod $module; + #[$cond] + pub use $module::getrandom; + } +} + +mod_use!(cfg(target_os = "android"), linux_android); +mod_use!(cfg(target_os = "bitrig"), openbsd_bitrig); +mod_use!(cfg(target_os = "cloudabi"), cloudabi); +mod_use!(cfg(target_os = "dragonfly"), dragonfly_haiku); +mod_use!(cfg(target_os = "emscripten"), emscripten); +mod_use!(cfg(target_os = "freebsd"), freebsd); +mod_use!(cfg(target_os = "fuchsia"), fuchsia); +mod_use!(cfg(target_os = "haiku"), dragonfly_haiku); +mod_use!(cfg(target_os = "ios"), macos); +mod_use!(cfg(target_os = "linux"), linux_android); +mod_use!(cfg(target_os = "macos"), macos); +mod_use!(cfg(target_os = "netbsd"), netbsd); +mod_use!(cfg(target_os = "openbsd"), openbsd_bitrig); +mod_use!(cfg(target_os = "redox"), redox); +mod_use!(cfg(target_os = "solaris"), solaris); +mod_use!(cfg(windows), windows); +mod_use!(cfg(target_env = "sgx"), sgx); + +mod_use!( + cfg(all( + target_arch = "wasm32", + not(target_os = "emscripten"), + feature = "wasm-bindgen" + )), + wasm32_bindgen +); + +mod_use!( + cfg(all( + target_arch = "wasm32", + not(target_os = "emscripten"), + not(feature = "wasm-bindgen"), + feature = "stdweb", + )), + wasm32_stdweb +); + +mod_use!( + cfg(not(any( + target_os = "android", + target_os = "bitrig", + target_os = "cloudabi", + target_os = "dragonfly", + target_os = "emscripten", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "redox", + target_os = "solaris", + target_env = "sgx", + windows, + all( + target_arch = "wasm32", + any( + target_os = "emscripten", + feature = "wasm-bindgen", + feature = "stdweb", + ), + ), + ))), + dummy +); diff --git a/src/linux_android.rs b/src/linux_android.rs new file mode 100644 index 00000000..db6c6e48 --- /dev/null +++ b/src/linux_android.rs @@ -0,0 +1,82 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation for Linux / Android +extern crate libc; + +use super::Error; +use super::utils::use_init; +use std::fs::File; +use std::io; +use std::io::Read; +use std::cell::RefCell; +use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering}; + +static RNG_INIT: AtomicBool = ATOMIC_BOOL_INIT; + +enum RngSource { + GetRandom, + Device(File), +} + +thread_local!( + static RNG_SOURCE: RefCell> = RefCell::new(None); +); + +fn syscall_getrandom(dest: &mut [u8]) -> Result<(), io::Error> { + let ret = unsafe { + libc::syscall(libc::SYS_getrandom, dest.as_mut_ptr(), dest.len(), 0) + }; + if ret == -1 || ret != dest.len() as i64 { + return Err(io::Error::last_os_error()); + } + Ok(()) +} + +pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> { + RNG_SOURCE.with(|f| { + use_init(f, + || { + let s = if is_getrandom_available() { + RngSource::GetRandom + } else { + // read one byte from "/dev/random" to ensure that + // OS RNG has initialized + if !RNG_INIT.load(Ordering::Relaxed) { + File::open("/dev/random")?.read_exact(&mut [0u8; 1])?; + RNG_INIT.store(true, Ordering::Relaxed) + } + RngSource::Device(File::open("/dev/urandom")?) + }; + Ok(s) + }, |f| { + match f { + RngSource::GetRandom => syscall_getrandom(dest), + RngSource::Device(f) => f.read_exact(dest), + }.map_err(From::from) + }) + }) +} + +fn is_getrandom_available() -> bool { + use std::sync::{Once, ONCE_INIT}; + + static CHECKER: Once = ONCE_INIT; + static AVAILABLE: AtomicBool = ATOMIC_BOOL_INIT; + + CHECKER.call_once(|| { + let mut buf: [u8; 0] = []; + let available = match syscall_getrandom(&mut buf) { + Ok(()) => true, + Err(err) => err.raw_os_error() != Some(libc::ENOSYS), + }; + AVAILABLE.store(available, Ordering::Relaxed); + }); + + AVAILABLE.load(Ordering::Relaxed) +} diff --git a/src/macos.rs b/src/macos.rs new file mode 100644 index 00000000..3855f1ac --- /dev/null +++ b/src/macos.rs @@ -0,0 +1,37 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation for MacOS / iOS +use super::Error; +use std::io; + +// TODO: check correctness +#[allow(non_upper_case_globals)] +const kSecRandomDefault: *const SecRandom = 0 as *const SecRandom; + +#[link(name = "Security", kind = "framework")] +extern { + fn SecRandomCopyBytes( + rnd: *const SecRandom, count: size_t, bytes: *mut u8, + ) -> c_int; +} + +pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> { + let ret = unsafe { + SecRandomCopyBytes( + kSecRandomDefault, + dest.len() as size_t, + dest.as_mut_ptr(), + ) + }; + if ret == -1 { + Err(io::Error::last_os_error().into()) + } else { + Ok(()) + } +} diff --git a/src/netbsd.rs b/src/netbsd.rs new file mode 100644 index 00000000..f2aa68ca --- /dev/null +++ b/src/netbsd.rs @@ -0,0 +1,34 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation for NetBSD + +use super::Error; +use super::utils::use_init; +use std::fs::File; +use std::io::Read; +use std::cell::RefCell; +use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering}; + +static RNG_INIT: AtomicBool = ATOMIC_BOOL_INIT; + +thread_local!(static RNG_FILE: RefCell> = RefCell::new(None)); + +pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> { + RNG_FILE.with(|f| { + use_init(f, || { + // read one byte from "/dev/random" to ensure that + // OS RNG has initialized + if !RNG_INIT.load(Ordering::Relaxed) { + File::open("/dev/random")?.read_exact(&mut [0u8; 1])?; + RNG_INIT.store(true, Ordering::Relaxed) + } + File::open("/dev/urandom").map_err(From::from) + }, |f| f.read_exact(dest).map_err(From::from)) + }) +} diff --git a/src/openbsd_bitrig.rs b/src/openbsd_bitrig.rs new file mode 100644 index 00000000..f8096fa2 --- /dev/null +++ b/src/openbsd_bitrig.rs @@ -0,0 +1,28 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation for OpenBSD / Bitrig +extern crate libc; + +use super::Error; +use std::io; + +pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> { + for chunk in dest.chunks_mut(256) { + let ret = unsafe { + libc::getentropy( + chunk.as_mut_ptr() as *mut libc::c_void, + chunk.len() + ) + }; + if ret == -1 { + return Err(io::Error::last_os_error().into()); + } + } + Ok(()) +} diff --git a/src/redox.rs b/src/redox.rs new file mode 100644 index 00000000..0c7e8578 --- /dev/null +++ b/src/redox.rs @@ -0,0 +1,25 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation for Redox +use super::Error; +use super::utils::use_init; +use std::fs::File; +use std::io::Read; +use std::cell::RefCell; + +thread_local!(static RNG_FILE: RefCell> = RefCell::new(None)); + +pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> { + RNG_FILE.with(|f| { + use_init(f, + || File::open("rand:").map_err(From::from), + |f| f.read_exact(dest).map_err(From::from), + ) + }).map_err(From::from) +} diff --git a/src/sgx.rs b/src/sgx.rs new file mode 100644 index 00000000..a98f55f1 --- /dev/null +++ b/src/sgx.rs @@ -0,0 +1,48 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation for SGX using RDRAND instruction +use super::{Error, UNKNOWN_ERROR}; + +use core::{mem, ptr}; +use core::arch::x86_64::_rdrand64_step; + +#[cfg(not(target_feature = "rdrand"))] +compile_error!("enable rdrand target feature!"); + +const RETRY_LIMIT: usize = 32; + +fn get_rand_u64() -> Result { + for _ in 0..RETRY_LIMIT { + unsafe { + let mut el = mem::uninitialized(); + if _rdrand64_step(&mut el) == 1 { + return Ok(el); + } + }; + } + Err(UNKNOWN_ERROR) +} + +pub fn getrandom(mut dest: &mut [u8]) -> Result<(), Error> { + while dest.len() >= 8 { + let (chunk, left) = {dest}.split_at_mut(8); + dest = left; + let r = get_rand_u64()?; + unsafe { + ptr::write_unaligned(chunk.as_mut_ptr() as *mut u64, r) + } + } + let n = dest.len(); + if n != 0 { + let r = get_rand_u64()?; + let r: [u8; 8] = unsafe { mem::transmute(r) }; + dest.copy_from_slice(&r[..n]); + } + Ok(()) +} diff --git a/src/solaris.rs b/src/solaris.rs new file mode 100644 index 00000000..e27e4bc0 --- /dev/null +++ b/src/solaris.rs @@ -0,0 +1,99 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation for the Solaris family +//! +//! Read from `/dev/random`, with chunks of limited size (1040 bytes). +//! `/dev/random` uses the Hash_DRBG with SHA512 algorithm from NIST SP 800-90A. +//! `/dev/urandom` uses the FIPS 186-2 algorithm, which is considered less +//! secure. We choose to read from `/dev/random`. +//! +//! Since Solaris 11.3 the `getrandom` syscall is available. To make sure we can +//! compile on both Solaris and on OpenSolaris derivatives, that do not have the +//! function, we do a direct syscall instead of calling a library function. +//! +//! We have no way to differentiate between Solaris, illumos, SmartOS, etc. +extern crate libc; + +use super::Error; +use std::fs::File; +use std::io; +use std::io::Read; +use std::cell::RefCell; +use std::ops::DerefMut; + +enum RngSource { + GetRandom, + Device(File), +} + +thread_local!( + static RNG_SOURCE: RefCell> = RefCell::new(None); +); + +fn syscall_getrandom(dest: &mut [u8]) -> Result<(), Error> { + // repalce with libc? + const SYS_GETRANDOM: libc::c_long = 143; + + extern "C" { + fn syscall(number: libc::c_long, ...) -> libc::c_long; + } + + let ret = unsafe { + syscall(SYS_GETRANDOM, dest.as_mut_ptr(), dest.len(), 0) + }; + if ret == -1 || ret != dest.len() as i64 { + return Err(io::Error::last_os_error().from()); + } + Ok(()) +} + +pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> { + // The documentation says 1024 is the maximum for getrandom + // and 1040 for /dev/random. + RNG_SOURCE.with(|f| { + use_init(f, + || { + let s = if is_getrandom_available() { + RngSource::GetRandom + } else { + RngSource::Device(File::open("/dev/random")?) + }; + Ok(s) + }, |f| { + match f { + RngSource::GetRandom => for chunk in dest.chunks_mut(1024) { + syscall_getrandom(chunk) + }, + RngSource::Device(f) => for chunk in dest.chunks_mut(1040) { + f.read_exact(dest).map_err(From::from) + }, + } + }) + }).map_err(|_| Error::Unknown) +} + +fn is_getrandom_available() -> bool { + use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering}; + use std::sync::{Once, ONCE_INIT}; + + static CHECKER: Once = ONCE_INIT; + static AVAILABLE: AtomicBool = ATOMIC_BOOL_INIT; + + CHECKER.call_once(|| { + let mut buf: [u8; 0] = []; + let available = match syscall_getrandom(&mut buf) { + Ok(()) => true, + Err(ref err) if err.raw_os_error() == Some(libc::ENOSYS) => false, + Err(_) => true, + }; + AVAILABLE.store(available, Ordering::Relaxed); + }); + + AVAILABLE.load(Ordering::Relaxed) +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 00000000..5b70bd15 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,30 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +use super::Error; +use core::cell::RefCell; +use core::ops::DerefMut; + +/// If `f` contains `Some(T)` call `use_f` using contents of `f` as an argument, +/// otherwise initialize `f` value using `init_f`, store resulting value in `f` +/// and call `use_f`. +pub(crate) fn use_init(f: &RefCell>, init_f: F, mut use_f: U) + -> Result<(), Error> + where F: FnOnce() -> Result, U: FnMut(&mut T) -> Result<(), Error> +{ + let mut f = f.borrow_mut(); + let f: &mut Option = f.deref_mut(); + match f { + None => *f = Some(init_f()?), + _ => (), + } + + match f { + Some(f) => use_f(f), + None => unreachable!(), + } +} diff --git a/src/wasm32_bindgen.rs b/src/wasm32_bindgen.rs new file mode 100644 index 00000000..6de8f046 --- /dev/null +++ b/src/wasm32_bindgen.rs @@ -0,0 +1,9 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation for WASM via wasm-bindgen diff --git a/src/wasm32_stdweb.rs b/src/wasm32_stdweb.rs new file mode 100644 index 00000000..7db8afea --- /dev/null +++ b/src/wasm32_stdweb.rs @@ -0,0 +1,9 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation for WASM via stdweb diff --git a/src/windows.rs b/src/windows.rs new file mode 100644 index 00000000..6507b6cd --- /dev/null +++ b/src/windows.rs @@ -0,0 +1,24 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation for Windows +extern crate winapi; + +use self::winapi::shared::minwindef::ULONG; +use self::winapi::um::ntsecapi::RtlGenRandom; +use self::winapi::um::winnt::PVOID; +use std::io; +use super::Error; + +pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> { + let ret = unsafe { + RtlGenRandom(dest.as_mut_ptr() as PVOID, dest.len() as ULONG) + }; + if ret == 0 { return Err(io::Error::last_os_error().into()); } + Ok(()) +} diff --git a/tests/mod.rs b/tests/mod.rs new file mode 100644 index 00000000..61719e5b --- /dev/null +++ b/tests/mod.rs @@ -0,0 +1,55 @@ +extern crate getrandom; + +use getrandom::getrandom; + +#[test] +fn test_diff() { + let mut v1 = [0u8; 1000]; + getrandom(&mut v1).unwrap(); + + let mut v2 = [0u8; 1000]; + getrandom(&mut v2).unwrap(); + + let mut n_diff_bits = 0; + for i in 0..v1.len() { + n_diff_bits += (v1[i] ^ v2[i]).count_ones(); + } + + // Check at least 1 bit per byte differs. p(failure) < 1e-1000 with random input. + assert!(n_diff_bits >= v1.len() as u32); +} + +#[test] +fn test_huge() { + let mut huge = [0u8; 100_000]; + getrandom(&mut huge).unwrap(); +} + +#[cfg(any(unix, windows, target_os="redox", target_os = "fuchsia"))] +#[test] +fn test_multithreading() { + use std::sync::mpsc::channel; + use std::thread; + + let mut txs = vec!(); + for _ in 0..20 { + let (tx, rx) = channel(); + txs.push(tx); + + thread::spawn(move|| { + // wait until all the tasks are ready to go. + rx.recv().unwrap(); + let mut v = [0u8; 1000]; + + for _ in 0..100 { + getrandom(&mut v).unwrap(); + thread::yield_now(); + } + }); + } + + // start all the tasks + for tx in txs.iter() { + tx.send(()).unwrap(); + } +}