use rand_core::{RngCore, CryptoRng, Error, ErrorKind, impls};
use core::{fmt, mem, ptr};
#[cfg(all(feature="std", not(target_arch = "wasm32")))]
use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};
const MEMORY_BLOCKS: usize = 64;
const MEMORY_BLOCKSIZE: usize = 32;
const MEMORY_SIZE: usize = MEMORY_BLOCKS * MEMORY_BLOCKSIZE;
pub struct JitterRng {
data: u64, rounds: u8,
timer: fn() -> u64,
mem_prev_index: u16,
data_half_used: bool,
}
struct EcState {
prev_time: u64,
last_delta: i32,
last_delta2: i32,
mem: [u8; MEMORY_SIZE],
}
impl EcState {
fn stuck(&mut self, current_delta: i32) -> bool {
let delta2 = self.last_delta - current_delta;
let delta3 = delta2 - self.last_delta2;
self.last_delta = current_delta;
self.last_delta2 = delta2;
current_delta == 0 || delta2 == 0 || delta3 == 0
}
}
impl fmt::Debug for JitterRng {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "JitterRng {{}}")
}
}
impl Clone for JitterRng {
fn clone(&self) -> JitterRng {
JitterRng {
data: self.data,
rounds: self.rounds,
timer: self.timer,
mem_prev_index: self.mem_prev_index,
data_half_used: false,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TimerError {
NoTimer,
CoarseTimer,
NotMonotonic,
TinyVariantions,
TooManyStuck,
#[doc(hidden)]
__Nonexhaustive,
}
impl TimerError {
fn description(&self) -> &'static str {
match *self {
TimerError::NoTimer => "no timer available",
TimerError::CoarseTimer => "coarse timer",
TimerError::NotMonotonic => "timer not monotonic",
TimerError::TinyVariantions => "time delta variations too small",
TimerError::TooManyStuck => "too many stuck results",
TimerError::__Nonexhaustive => unreachable!(),
}
}
}
impl fmt::Display for TimerError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.description())
}
}
#[cfg(feature="std")]
impl ::std::error::Error for TimerError {
fn description(&self) -> &str {
self.description()
}
}
impl From<TimerError> for Error {
fn from(err: TimerError) -> Error {
Error::with_cause(ErrorKind::Unavailable,
"timer jitter failed basic quality tests", err)
}
}
#[cfg(all(feature="std", not(target_arch = "wasm32")))]
static JITTER_ROUNDS: AtomicUsize = ATOMIC_USIZE_INIT;
impl JitterRng {
#[cfg(all(feature="std", not(target_arch = "wasm32")))]
pub fn new() -> Result<JitterRng, TimerError> {
let mut state = JitterRng::new_with_timer(platform::get_nstime);
let mut rounds = JITTER_ROUNDS.load(Ordering::Relaxed) as u8;
if rounds == 0 {
rounds = state.test_timer()?;
JITTER_ROUNDS.store(rounds as usize, Ordering::Relaxed);
info!("JitterRng: using {} rounds per u64 output", rounds);
}
state.set_rounds(rounds);
state.gen_entropy();
Ok(state)
}
pub fn new_with_timer(timer: fn() -> u64) -> JitterRng {
JitterRng {
data: 0,
rounds: 64,
timer,
mem_prev_index: 0,
data_half_used: false,
}
}
pub fn set_rounds(&mut self, rounds: u8) {
assert!(rounds > 0);
self.rounds = rounds;
}
#[inline(never)]
fn random_loop_cnt(&mut self, n_bits: u32) -> u32 {
let mut rounds = 0;
let mut time = (self.timer)();
time ^= self.data;
let folds = (64 + n_bits - 1) / n_bits;
let mask = (1 << n_bits) - 1;
for _ in 0..folds {
rounds ^= time & mask;
time >>= n_bits;
}
rounds as u32
}
#[inline(never)]
fn lfsr_time(&mut self, time: u64, var_rounds: bool) {
fn lfsr(mut data: u64, time: u64) -> u64{
for i in 1..65 {
let mut tmp = time << (64 - i);
tmp >>= 64 - 1;
data ^= tmp;
data ^= (data >> 63) & 1;
data ^= (data >> 60) & 1;
data ^= (data >> 55) & 1;
data ^= (data >> 30) & 1;
data ^= (data >> 27) & 1;
data ^= (data >> 22) & 1;
data = data.rotate_left(1);
}
data
}
let mut lfsr_loop_cnt = 0;
if var_rounds { lfsr_loop_cnt = self.random_loop_cnt(4) };
let mut throw_away: u64 = 0;
for _ in 0..lfsr_loop_cnt {
throw_away = lfsr(throw_away, time);
}
black_box(throw_away);
self.data = lfsr(self.data, time);
}
#[inline(never)]
fn memaccess(&mut self, mem: &mut [u8; MEMORY_SIZE], var_rounds: bool) {
let mut acc_loop_cnt = 128;
if var_rounds { acc_loop_cnt += self.random_loop_cnt(4) };
let mut index = self.mem_prev_index as usize;
for _ in 0..acc_loop_cnt {
index = (index + MEMORY_BLOCKSIZE - 1) % MEMORY_SIZE;
mem[index] = mem[index].wrapping_add(1);
}
self.mem_prev_index = index as u16;
}
fn measure_jitter(&mut self, ec: &mut EcState) -> Option<()> {
self.memaccess(&mut ec.mem, true);
let time = (self.timer)();
let current_delta = time.wrapping_sub(ec.prev_time) as i64 as i32;
ec.prev_time = time;
self.lfsr_time(current_delta as u64, true);
if ec.stuck(current_delta) { return None };
self.data = self.data.rotate_left(7);
Some(())
}
#[inline(never)]
fn stir_pool(&mut self) {
const CONSTANT: u64 = 0x67452301efcdab89;
let mut mixer = 0x98badcfe10325476;
for i in 0..64 {
let apply = (self.data >> i) & 1;
let mask = !apply.wrapping_sub(1);
mixer ^= CONSTANT & mask;
mixer = mixer.rotate_left(1);
}
self.data ^= mixer;
}
fn gen_entropy(&mut self) -> u64 {
trace!("JitterRng: collecting entropy");
let mut ec = EcState {
prev_time: (self.timer)(),
last_delta: 0,
last_delta2: 0,
mem: [0; MEMORY_SIZE],
};
let _ = self.measure_jitter(&mut ec);
for _ in 0..self.rounds {
while self.measure_jitter(&mut ec).is_none() {}
}
black_box(ec.mem[0]);
self.stir_pool();
self.data
}
pub fn test_timer(&mut self) -> Result<u8, TimerError> {
debug!("JitterRng: testing timer ...");
let mut delta_sum = 0;
let mut old_delta = 0;
let mut time_backwards = 0;
let mut count_mod = 0;
let mut count_stuck = 0;
let mut ec = EcState {
prev_time: (self.timer)(),
last_delta: 0,
last_delta2: 0,
mem: [0; MEMORY_SIZE],
};
const TESTLOOPCOUNT: u64 = 300;
const CLEARCACHE: u64 = 100;
for i in 0..(CLEARCACHE + TESTLOOPCOUNT) {
let time = (self.timer)();
self.memaccess(&mut ec.mem, true);
self.lfsr_time(time, true);
let time2 = (self.timer)();
if time == 0 || time2 == 0 {
return Err(TimerError::NoTimer);
}
let delta = time2.wrapping_sub(time) as i64 as i32;
if delta == 0 {
return Err(TimerError::CoarseTimer);
}
if i < CLEARCACHE { continue; }
if ec.stuck(delta) { count_stuck += 1; }
if !(time2 > time) { time_backwards += 1; }
if (delta % 100) == 0 { count_mod += 1; }
delta_sum += (delta - old_delta).abs() as u64;
old_delta = delta;
}
black_box(ec.mem[0]);
if time_backwards > 3 {
return Err(TimerError::NotMonotonic);
}
if delta_sum < TESTLOOPCOUNT {
return Err(TimerError::TinyVariantions);
}
if count_mod > (TESTLOOPCOUNT * 9 / 10) {
return Err(TimerError::CoarseTimer);
}
if count_stuck > (TESTLOOPCOUNT * 9 / 10) {
return Err(TimerError::TooManyStuck);
}
let delta_average = delta_sum / TESTLOOPCOUNT;
if delta_average >= 16 {
let log2 = 64 - delta_average.leading_zeros();
Ok( ((64u32 * 2 + log2 - 1) / log2) as u8)
} else {
let log2_lookup = [0, 0, 128, 81, 64, 56, 50, 46,
43, 41, 39, 38, 36, 35, 34, 33];
Ok(log2_lookup[delta_average as usize])
}
}
pub fn timer_stats(&mut self, var_rounds: bool) -> i64 {
let mut mem = [0; MEMORY_SIZE];
let time = (self.timer)();
self.memaccess(&mut mem, var_rounds);
self.lfsr_time(time, var_rounds);
let time2 = (self.timer)();
time2.wrapping_sub(time) as i64
}
}
#[cfg(feature="std")]
mod platform {
#[cfg(not(any(target_os = "macos", target_os = "ios",
target_os = "windows",
target_arch = "wasm32")))]
pub fn get_nstime() -> u64 {
use std::time::{SystemTime, UNIX_EPOCH};
let dur = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
dur.as_secs() << 30 | dur.subsec_nanos() as u64
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
pub fn get_nstime() -> u64 {
extern crate libc;
unsafe { libc::mach_absolute_time() }
}
#[cfg(target_os = "windows")]
pub fn get_nstime() -> u64 {
extern crate winapi;
unsafe {
let mut t = super::mem::zeroed();
winapi::um::profileapi::QueryPerformanceCounter(&mut t);
*t.QuadPart() as u64
}
}
}
fn black_box<T>(dummy: T) -> T {
unsafe {
let ret = ptr::read_volatile(&dummy);
mem::forget(dummy);
ret
}
}
impl RngCore for JitterRng {
fn next_u32(&mut self) -> u32 {
if self.data_half_used {
self.data_half_used = false;
(self.data >> 32) as u32
} else {
self.data = self.next_u64();
self.data_half_used = true;
self.data as u32
}
}
fn next_u64(&mut self) -> u64 {
self.data_half_used = false;
self.gen_entropy()
}
fn fill_bytes(&mut self, dest: &mut [u8]) {
impls::fill_bytes_via_next(self, dest)
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
Ok(self.fill_bytes(dest))
}
}
impl CryptoRng for JitterRng {}
#[cfg(test)]
mod test_jitter_init {
use jitter::JitterRng;
#[cfg(all(feature="std", not(target_arch = "wasm32")))]
#[test]
fn test_jitter_init() {
use RngCore;
match JitterRng::new() {
Ok(ref mut rng) => {
assert!(rng.next_u32() | rng.next_u32() != 0);
},
Err(_) => {},
}
}
#[test]
fn test_jitter_bad_timer() {
fn bad_timer() -> u64 { 0 }
let mut rng = JitterRng::new_with_timer(bad_timer);
assert!(rng.test_timer().is_err());
}
}