use std::time::{Instant, SystemTime, UNIX_EPOCH};
use crate::{Clock, EntropyError, EntropySource, Now};
#[derive(Clone, Copy, Debug)]
pub struct StdClock {
epoch: Instant,
}
impl StdClock {
pub fn new() -> Self {
Self {
epoch: Instant::now(),
}
}
pub fn with_epoch(epoch: Instant) -> Self {
Self { epoch }
}
pub fn epoch(&self) -> Instant {
self.epoch
}
}
impl Default for StdClock {
fn default() -> Self {
Self::new()
}
}
impl Clock for StdClock {
fn now(&mut self) -> Now {
let monotonic_ms = u64::try_from(self.epoch.elapsed().as_millis())
.unwrap_or(u64::MAX)
.min(Now::MAX_MONOTONIC_MS);
let unix_seconds = SystemTime::now()
.duration_since(UNIX_EPOCH)
.ok()
.map(|since_epoch| since_epoch.as_secs());
Now {
monotonic_ms,
unix_seconds,
}
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct StdEntropy;
impl StdEntropy {
pub const fn new() -> Self {
Self
}
}
impl EntropySource for StdEntropy {
fn fill_bytes(&mut self, output: &mut [u8]) -> Result<(), EntropyError> {
getrandom::fill(output).map_err(|error| match error.raw_os_error() {
Some(code) => EntropyError::failed_with_code("os entropy source failed", code),
None => EntropyError::failed("os entropy source failed"),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread::sleep;
use std::time::Duration;
#[test]
fn monotonic_time_starts_near_the_epoch_and_advances() {
let mut clock = StdClock::new();
let start = clock.now();
assert!(start.monotonic_ms < 1_000, "expected a fresh timeline");
sleep(Duration::from_millis(5));
let later = clock.now();
assert!(later.monotonic_ms >= start.monotonic_ms + 5);
}
#[test]
fn samples_never_decrease() {
let mut clock = StdClock::new();
let mut previous = clock.now();
for _ in 0..100 {
let current = clock.now();
assert!(current.monotonic_ms >= previous.monotonic_ms);
previous = current;
}
}
#[test]
fn samples_are_measured_from_the_injected_epoch() {
const OFFSET_MS: u64 = 5_000;
let epoch = Instant::now();
let mut shared = StdClock::with_epoch(epoch);
let mut older = StdClock::with_epoch(epoch - Duration::from_millis(OFFSET_MS));
let from_shared = shared.now().monotonic_ms;
let from_older = older.now().monotonic_ms;
let delta = from_older
.checked_sub(from_shared)
.expect("older epoch must read further along the timeline");
assert!(
delta.abs_diff(OFFSET_MS) < 1_000,
"expected ~{OFFSET_MS}ms between epochs, got {delta}ms"
);
}
#[test]
fn clocks_sharing_an_epoch_produce_comparable_samples() {
let epoch = Instant::now();
let mut first = StdClock::with_epoch(epoch);
let mut second = StdClock::with_epoch(epoch);
assert_eq!(first.epoch(), second.epoch());
let before = first.now().monotonic_ms;
sleep(Duration::from_millis(5));
let after = second.now().monotonic_ms;
assert!(
after >= before + 5,
"sleep not observable across clocks: {before} -> {after}"
);
}
#[test]
fn reports_a_plausible_wall_clock() {
let mut clock = StdClock::new();
let unix_seconds = clock.now().unix_seconds.expect("hosts have a wall clock");
assert!(unix_seconds > 1_577_836_800);
}
#[test]
fn entropy_fills_the_whole_buffer() {
const SENTINEL: u8 = 0x5a;
const WINDOW: usize = 16;
let mut entropy = StdEntropy::new();
let mut buffer = [SENTINEL; 256];
entropy.fill_bytes(&mut buffer).expect("os entropy");
for (index, window) in buffer.windows(WINDOW).enumerate() {
assert!(
window.iter().any(|&byte| byte != SENTINEL),
"bytes {index}..{} were left unwritten",
index + WINDOW
);
}
}
#[test]
fn entropy_does_not_repeat_itself() {
let mut entropy = StdEntropy::new();
let first = entropy.next_u64().expect("os entropy");
let second = entropy.next_u64().expect("os entropy");
assert_ne!(first, second);
}
#[test]
fn empty_buffer_is_not_an_error() {
let mut entropy = StdEntropy::new();
entropy.fill_bytes(&mut []).expect("empty fill");
}
}