use Rng;
use core::{fmt, mem, ptr};
#[cfg(feature="std")]
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: u32,
timer: fn() -> u64,
prev_time: u64,
last_delta: i64,
last_delta2: i64,
mem_prev_index: usize,
mem: [u8; MEMORY_SIZE],
data_remaining: Option<u32>,
}
impl fmt::Debug for JitterRng {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "JitterRng {{}}")
}
}
#[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()
}
}
#[cfg(feature="std")]
static JITTER_ROUNDS: AtomicUsize = ATOMIC_USIZE_INIT;
impl JitterRng {
#[cfg(feature="std")]
pub fn new() -> Result<JitterRng, TimerError> {
let mut ec = JitterRng::new_with_timer(platform::get_nstime);
let mut rounds = JITTER_ROUNDS.load(Ordering::Relaxed) as u32;
if rounds == 0 {
rounds = ec.test_timer()?;
JITTER_ROUNDS.store(rounds as usize, Ordering::Relaxed);
}
ec.set_rounds(rounds);
Ok(ec)
}
pub fn new_with_timer(timer: fn() -> u64) -> JitterRng {
let mut ec = JitterRng {
data: 0,
rounds: 64,
timer: timer,
prev_time: 0,
last_delta: 0,
last_delta2: 0,
mem_prev_index: 0,
mem: [0; MEMORY_SIZE],
data_remaining: None,
};
ec.prev_time = timer();
ec.gen_entropy();
black_box(ec.mem[0]);
ec
}
pub fn set_rounds(&mut self, rounds: u32) {
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 = 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 = 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, 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;
for _ in 0..acc_loop_cnt {
index = (index + MEMORY_BLOCKSIZE - 1) % MEMORY_SIZE;
let tmp = self.mem[index];
self.mem[index] = tmp.wrapping_add(1);
}
self.mem_prev_index = index;
}
fn stuck(&mut self, current_delta: i64) -> 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
}
fn measure_jitter(&mut self) -> Option<()> {
self.memaccess(true);
let time = (self.timer)();
let current_delta = time.wrapping_sub(self.prev_time) as i64;
self.prev_time = time;
self.lfsr_time(current_delta as u64, true);
if self.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 {
let _ = self.measure_jitter();
for _ in 0..self.rounds {
while self.measure_jitter().is_none() {}
}
self.stir_pool();
self.data
}
pub fn test_timer(&mut self) -> Result<u32, TimerError> {
#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
return Err(TimerError::NoTimer);
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;
const TESTLOOPCOUNT: u64 = 300;
const CLEARCACHE: u64 = 100;
for i in 0..(CLEARCACHE + TESTLOOPCOUNT) {
let time = (self.timer)();
self.memaccess(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;
if delta == 0 {
return Err(TimerError::CoarseTimer);
}
if i < CLEARCACHE { continue; }
if self.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;
}
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;
const FACTOR: u32 = 3;
fn log2(x: u64) -> u32 { 64 - x.leading_zeros() }
Ok(64 * 2 * FACTOR / (log2(delta_average.pow(FACTOR)) + 1))
}
#[cfg(feature="std")]
pub fn timer_stats(&mut self, var_rounds: bool) -> i64 {
let time = platform::get_nstime();
self.memaccess(var_rounds);
self.lfsr_time(time, var_rounds);
let time2 = platform::get_nstime();
time2.wrapping_sub(time) as i64
}
}
#[cfg(feature="std")]
mod platform {
#[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "windows", all(target_arch = "wasm32", not(target_os = "emscripten")))))]
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
}
}
#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
pub fn get_nstime() -> u64 {
unreachable!()
}
}
fn black_box<T>(dummy: T) -> T {
unsafe {
let ret = ptr::read_volatile(&dummy);
mem::forget(dummy);
ret
}
}
impl Rng for JitterRng {
fn next_u32(&mut self) -> u32 {
if let Some(high) = self.data_remaining.take() {
high
} else {
let data = self.next_u64();
self.data_remaining = Some((data >> 32) as u32);
data as u32
}
}
fn next_u64(&mut self) -> u64 {
self.gen_entropy()
}
fn fill_bytes(&mut self, dest: &mut [u8]) {
let mut left = dest;
while left.len() >= 8 {
let (l, r) = {left}.split_at_mut(8);
left = r;
let chunk: [u8; 8] = unsafe {
mem::transmute(self.next_u64().to_le())
};
l.copy_from_slice(&chunk);
}
let n = left.len();
if n > 0 {
let chunk: [u8; 8] = unsafe {
mem::transmute(self.next_u64().to_le())
};
left.copy_from_slice(&chunk[..n]);
}
}
}