#![doc(
html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk.png",
html_favicon_url = "https://www.rust-lang.org/favicon.ico"
)]
#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
#![doc(test(attr(allow(unused_variables), deny(warnings))))]
#![no_std]
#[cfg(feature = "std")]
extern crate std;
pub use rand_core;
#[cfg(test)]
macro_rules! doc_comment {
($x:expr) => {
#[doc = $x]
fn _doc_comment() {}
};
}
#[cfg(test)]
doc_comment!(include_str!("../README.md"));
#[allow(unused)]
macro_rules! trace { ($($x:tt)*) => (
#[cfg(feature = "log")] {
log::trace!($($x)*)
}
) }
#[allow(unused)]
macro_rules! debug { ($($x:tt)*) => (
#[cfg(feature = "log")] {
log::debug!($($x)*)
}
) }
#[allow(unused)]
macro_rules! info { ($($x:tt)*) => (
#[cfg(feature = "log")] {
log::info!($($x)*)
}
) }
#[allow(unused)]
macro_rules! warn { ($($x:tt)*) => (
#[cfg(feature = "log")] {
log::warn!($($x)*)
}
) }
#[allow(unused)]
macro_rules! error { ($($x:tt)*) => (
#[cfg(feature = "log")] {
log::error!($($x)*)
}
) }
mod error;
#[cfg(feature = "std")]
mod platform;
pub use crate::error::TimerError;
use core::convert::Infallible;
use rand_core::{Rng, TryRng, utils};
use core::{fmt, mem, ptr};
#[cfg(feature = "std")]
use std::sync::atomic::{AtomicUsize, Ordering};
const MEMORY_BLOCKS: usize = 64;
const MEMORY_BLOCKSIZE: usize = 32;
const MEMORY_SIZE: usize = MEMORY_BLOCKS * MEMORY_BLOCKSIZE;
pub struct JitterRng<F> {
data: u64, rounds: u8,
timer: F,
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<F> fmt::Debug for JitterRng<F> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "JitterRng {{}}")
}
}
impl<F> Clone for JitterRng<F>
where
F: Clone,
{
fn clone(&self) -> JitterRng<F> {
JitterRng {
data: self.data,
rounds: self.rounds,
timer: self.timer.clone(),
mem_prev_index: self.mem_prev_index,
data_half_used: false,
}
}
}
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
static JITTER_ROUNDS: AtomicUsize = AtomicUsize::new(0);
impl JitterRng<()> {
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
pub fn new() -> Result<JitterRng<impl Fn() -> u64 + Send + Sync>, TimerError> {
if cfg!(target_arch = "wasm32") {
return Err(TimerError::NoTimer);
}
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)
}
}
impl<F> JitterRng<F>
where
F: Fn() -> u64 + Send + Sync,
{
pub fn new_with_timer(timer: F) -> JitterRng<F> {
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_u32.div_ceil(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).unsigned_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::TinyVariations);
}
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).div_ceil(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
}
}
fn black_box<T>(dummy: T) -> T {
unsafe {
let ret = ptr::read_volatile(&dummy);
mem::forget(dummy);
ret
}
}
impl<F> TryRng for JitterRng<F>
where
F: Fn() -> u64 + Send + Sync,
{
type Error = Infallible;
fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
if self.data_half_used {
self.data_half_used = false;
Ok((self.data >> 32) as u32)
} else {
self.data = self.next_u64();
self.data_half_used = true;
Ok(self.data as u32)
}
}
fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
self.data_half_used = false;
Ok(self.gen_entropy())
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
utils::fill_bytes_via_next_word(dest, || self.try_next_u64())
}
}