use crate::error::Error;
use crate::floor::EFFECTIVE_FLOOR_SECS;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Time {
pub secs: u64,
pub nanos: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ClockError {
PermissionDenied,
Unsupported,
Os(i32),
}
pub trait SystemClock {
fn get() -> Result<Time, ClockError>;
fn set(time: Time) -> Result<(), ClockError>;
}
pub fn enforce_floor<C: SystemClock>() -> Result<(), Error> {
let now = C::get().map_err(Error::ClockUnavailable)?;
if now.secs < EFFECTIVE_FLOOR_SECS {
C::set(Time {
secs: EFFECTIVE_FLOOR_SECS,
nanos: 0,
})
.map_err(Error::ClockUnavailable)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
extern crate std;
use core::cell::Cell;
use std::thread_local;
use super::{ClockError, EFFECTIVE_FLOOR_SECS, SystemClock, Time, enforce_floor};
use crate::Error;
thread_local! {
static CURRENT: Cell<u64> = const { Cell::new(0) };
static SET_TO: Cell<Option<u64>> = const { Cell::new(None) };
static GET_FAILS: Cell<bool> = const { Cell::new(false) };
static SET_FAILS: Cell<bool> = const { Cell::new(false) };
}
struct MockClock;
impl SystemClock for MockClock {
fn get() -> Result<Time, ClockError> {
if GET_FAILS.get() {
return Err(ClockError::Unsupported);
}
Ok(Time {
secs: CURRENT.get(),
nanos: 0,
})
}
fn set(time: Time) -> Result<(), ClockError> {
if SET_FAILS.get() {
return Err(ClockError::PermissionDenied);
}
SET_TO.set(Some(time.secs));
Ok(())
}
}
fn reset() {
CURRENT.set(0);
SET_TO.set(None);
GET_FAILS.set(false);
SET_FAILS.set(false);
}
#[test]
fn leaves_a_clock_already_past_the_floor_untouched() {
reset();
CURRENT.set(EFFECTIVE_FLOOR_SECS + 100);
enforce_floor::<MockClock>().unwrap();
assert_eq!(SET_TO.get(), None);
}
#[test]
fn ratchets_a_clock_before_the_floor_forward() {
reset();
CURRENT.set(EFFECTIVE_FLOOR_SECS - 100);
enforce_floor::<MockClock>().unwrap();
assert_eq!(SET_TO.get(), Some(EFFECTIVE_FLOOR_SECS));
}
#[test]
fn propagates_a_get_failure() {
reset();
GET_FAILS.set(true);
let err = enforce_floor::<MockClock>().unwrap_err();
assert_eq!(err, Error::ClockUnavailable(ClockError::Unsupported));
}
#[test]
fn propagates_a_set_failure() {
reset();
CURRENT.set(EFFECTIVE_FLOOR_SECS - 100);
SET_FAILS.set(true);
let err = enforce_floor::<MockClock>().unwrap_err();
assert_eq!(err, Error::ClockUnavailable(ClockError::PermissionDenied));
}
}