#![allow(dead_code)]
use cfg_if::cfg_if;
cfg_if! {
if #[cfg(any(feature = "testing", test))] {
pub use if_testing::*;
} else if #[cfg(feature = "std")] {
pub use if_std::*;
} else {
pub use if_no_std::*;
}
}
#[cfg(any(feature = "std", test))]
mod if_std {
use lazy_static::lazy_static;
use s2n_quic_core::time::{Clock, StdClock, Timestamp};
lazy_static! {
static ref GLOBAL_CLOCK: StdClock = StdClock::default();
}
pub fn now() -> Timestamp {
GLOBAL_CLOCK.get_time()
}
pub fn clock() -> &'static dyn Clock {
&*GLOBAL_CLOCK
}
}
#[cfg(any(not(feature = "std"), test))]
mod if_no_std {
use core::sync::atomic::{AtomicUsize, Ordering};
use s2n_quic_core::time::{Clock, NoopClock, Timestamp};
static mut GLOBAL_CLOCK: &'static dyn Clock = &NoopClock {};
const CLOCK_UNINITIALIZED: usize = 0;
const CLOCK_INITIALIZING: usize = 1;
const CLOCK_INITIALIZED: usize = 2;
static CLOCK_STATE: AtomicUsize = AtomicUsize::new(CLOCK_UNINITIALIZED);
pub fn init_global_clock(clock: &'static dyn Clock) -> Result<(), ()> {
unsafe {
match CLOCK_STATE.compare_exchange(
CLOCK_UNINITIALIZED,
CLOCK_INITIALIZING,
Ordering::SeqCst,
Ordering::SeqCst,
) {
Ok(_) => {
GLOBAL_CLOCK = clock;
CLOCK_STATE.store(CLOCK_INITIALIZED, Ordering::SeqCst);
Ok(())
}
Err(err) if err == CLOCK_INITIALIZING => {
while CLOCK_STATE.load(Ordering::SeqCst) != CLOCK_INITIALIZED {
std::hint::spin_loop()
}
Err(())
}
_ => Err(()),
}
}
}
pub fn now() -> Timestamp {
clock().get_time()
}
pub fn clock() -> &'static dyn Clock {
unsafe {
if CLOCK_STATE.load(Ordering::SeqCst) != CLOCK_INITIALIZED {
static NOP: NoopClock = NoopClock {};
&NOP
} else {
GLOBAL_CLOCK
}
}
}
}
#[cfg(any(feature = "testing", test))]
mod if_testing {
use cfg_if::cfg_if;
use core::cell::RefCell;
use s2n_quic_core::time::{Clock, Duration, Timestamp};
use std::sync::Arc;
cfg_if! {
if #[cfg(feature = "std")] {
use super::if_std::now as inner_now;
} else {
use super::if_no_std::now as inner_now;
}
}
thread_local! {
static LOCAL_CLOCK: ClockHolder = ClockHolder {
inner: RefCell::new(None),
}
}
struct ClockHolder {
inner: RefCell<Option<Arc<dyn Clock>>>,
}
impl Clock for ClockHolder {
fn get_time(&self) -> Timestamp {
match &*self.inner.borrow() {
Some(clock) => clock.get_time(),
None => inner_now(),
}
}
}
pub fn now() -> Timestamp {
(&LOCAL_CLOCK).get_time()
}
pub fn clock() -> &'static dyn Clock {
&&LOCAL_CLOCK
}
pub mod testing {
use super::*;
use std::sync::Mutex;
pub fn set_local_clock(clock: Arc<dyn Clock>) {
LOCAL_CLOCK.with(|current_local_clock| {
*current_local_clock.inner.borrow_mut() = Some(clock);
});
}
pub fn reset_local_clock() {
LOCAL_CLOCK.with(|current_local_clock| {
*current_local_clock.inner.borrow_mut() = None;
});
}
pub struct MockClock {
timestamp: Mutex<Timestamp>,
}
impl Default for MockClock {
fn default() -> Self {
Self::new()
}
}
impl MockClock {
pub fn new() -> MockClock {
MockClock {
timestamp: Mutex::new(inner_now()),
}
}
pub fn set_time(&self, timestamp: Timestamp) {
let mut guard = self.timestamp.lock().unwrap();
*guard = timestamp;
}
pub fn adjust_by(&self, duration: Duration) {
let mut guard = self.timestamp.lock().unwrap();
*guard += duration;
}
}
impl Clock for MockClock {
fn get_time(&self) -> Timestamp {
*self.timestamp.lock().unwrap()
}
}
}
#[test]
fn use_mocked_clock() {
use std::sync::Arc;
let original_time = now();
let clock = Arc::new(testing::MockClock::new());
testing::set_local_clock(clock.clone());
let ts1 = now();
clock.adjust_by(Duration::from_millis(333));
let ts2 = now();
assert_eq!(ts2 - ts1, Duration::from_millis(333));
clock.adjust_by(Duration::from_millis(111));
let ts3 = now();
assert_eq!(ts3 - ts1, Duration::from_millis(444));
clock.set_time(ts1);
assert_eq!(ts1, now());
testing::reset_local_clock();
let restored_time = now();
assert!(restored_time - original_time >= Duration::from_millis(0));
assert!(restored_time - original_time <= Duration::from_millis(100));
}
}