s2n_quic_platform/time/
mod.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Defines time related datatypes and functions
5
6#![allow(dead_code)]
7
8use cfg_if::cfg_if;
9
10cfg_if! {
11    if #[cfg(any(feature = "testing", test))] {
12        pub use if_testing::*;
13    } else if #[cfg(feature = "std")] {
14        pub use if_std::*;
15    } else {
16        pub use if_no_std::*;
17    }
18}
19
20#[cfg(any(feature = "std", test))]
21mod if_std {
22    //! This module implements the clock functionality for the "std" feature
23    //! and environment. In this environment we are directly using `Instant`
24    //! types from the Rust standard library as Timestamps.
25    use lazy_static::lazy_static;
26    use s2n_quic_core::time::{Clock, StdClock, Timestamp};
27
28    lazy_static! {
29        static ref GLOBAL_CLOCK: StdClock = StdClock::default();
30    }
31
32    /// Returns the current [`Timestamp`] according to the system clock
33    pub fn now() -> Timestamp {
34        GLOBAL_CLOCK.get_time()
35    }
36
37    /// Returns a reference to the clock.
38    pub fn clock() -> &'static dyn Clock {
39        &*GLOBAL_CLOCK
40    }
41}
42
43// The no-std version allows to set a global clock manually.
44// It is not possible to overwrite the clock at runtime.
45#[cfg(any(not(feature = "std"), test))]
46mod if_no_std {
47    //! This module implements the clock functionality for the "no-std"
48    //! environments. In those environments we allow to configure a global clock
49    //! via a trait object.
50    //!
51    //! The global clock has to be initialized via a call to
52    //! `init_global_clock`.
53    use core::sync::atomic::{AtomicUsize, Ordering};
54    use s2n_quic_core::time::{Clock, NoopClock, Timestamp};
55
56    /// The configured global clock
57    static mut GLOBAL_CLOCK: &'static dyn Clock = &NoopClock {};
58
59    const CLOCK_UNINITIALIZED: usize = 0;
60    const CLOCK_INITIALIZING: usize = 1;
61    const CLOCK_INITIALIZED: usize = 2;
62
63    /// Tracks whether the global clock had already been initialized
64    static CLOCK_STATE: AtomicUsize = AtomicUsize::new(CLOCK_UNINITIALIZED);
65
66    /// Initialize the global Clock for use in `no-std` mode.
67    /// The configured clock will be queried on all `crate::time::now()` calls.
68    /// The global clock can only be set once.
69    pub fn init_global_clock(clock: &'static dyn Clock) -> Result<(), ()> {
70        unsafe {
71            match CLOCK_STATE.compare_exchange(
72                CLOCK_UNINITIALIZED,
73                CLOCK_INITIALIZING,
74                Ordering::SeqCst,
75                Ordering::SeqCst,
76            ) {
77                Ok(_) => {
78                    GLOBAL_CLOCK = clock;
79                    CLOCK_STATE.store(CLOCK_INITIALIZED, Ordering::SeqCst);
80                    Ok(())
81                }
82                Err(err) if err == CLOCK_INITIALIZING => {
83                    // Wait until a different thread has initialized the clock
84                    while CLOCK_STATE.load(Ordering::SeqCst) != CLOCK_INITIALIZED {}
85                    Err(())
86                }
87                _ => Err(()),
88            }
89        }
90    }
91
92    /// Returns the current [`Timestamp`] according to the system clock
93    pub fn now() -> Timestamp {
94        clock().get_time()
95    }
96
97    /// Returns a reference to the clock.
98    ///
99    /// If a clock has not been set, a no-op implementation is returned.
100    pub fn clock() -> &'static dyn Clock {
101        unsafe {
102            if CLOCK_STATE.load(Ordering::SeqCst) != CLOCK_INITIALIZED {
103                static NOP: NoopClock = NoopClock {};
104                &NOP
105            } else {
106                GLOBAL_CLOCK
107            }
108        }
109    }
110}
111
112/// Clock implementation if the "testing" feature is enabled
113#[cfg(any(feature = "testing", test))]
114mod if_testing {
115    use cfg_if::cfg_if;
116    use core::cell::RefCell;
117    use s2n_quic_core::time::{Clock, Duration, Timestamp};
118    use std::sync::Arc;
119
120    cfg_if! {
121        if #[cfg(feature = "std")] {
122            use super::if_std::now as inner_now;
123        } else {
124            use super::if_no_std::now as inner_now;
125        }
126    }
127
128    thread_local! {
129        static LOCAL_CLOCK: ClockHolder = ClockHolder {
130            inner: RefCell::new(None),
131        }
132    }
133
134    struct ClockHolder {
135        inner: RefCell<Option<Arc<dyn Clock>>>,
136    }
137
138    impl Clock for ClockHolder {
139        fn get_time(&self) -> Timestamp {
140            match &*self.inner.borrow() {
141                Some(clock) => clock.get_time(),
142                None => inner_now(),
143            }
144        }
145    }
146
147    /// Returns the current [`Timestamp`] according to the system clock
148    pub fn now() -> Timestamp {
149        (&LOCAL_CLOCK).get_time()
150    }
151
152    /// Returns a reference to the clock.
153    pub fn clock() -> &'static dyn Clock {
154        &&LOCAL_CLOCK
155    }
156
157    pub mod testing {
158        use super::*;
159        use std::sync::Mutex;
160
161        /// Configures a [`Clock`] which will be utilized for the following
162        /// calls to `crate::time::now()` on the current thread.
163        ///
164        /// Example:
165        ///
166        /// ```ignore
167        /// # use core::time::Duration;
168        /// use std::sync::Arc;
169        /// use s2n_quic_platform::time::{self, testing};
170        /// let clock = Arc::new(testing::MockClock::new());
171        /// testing::set_local_clock(clock.clone());
172        ///
173        /// let before = time::now();
174        /// clock.adjust_by(Duration::from_millis(333));
175        /// let after = time::now();
176        /// assert_eq!(after - before, Duration::from_millis(333));
177        /// ```
178        pub fn set_local_clock(clock: Arc<dyn Clock>) {
179            LOCAL_CLOCK.with(|current_local_clock| {
180                *current_local_clock.inner.borrow_mut() = Some(clock);
181            });
182        }
183
184        /// Resets the local clock.
185        ///
186        /// Following invocations to [`crate::time::now()`]
187        /// will return the system time again.
188        pub fn reset_local_clock() {
189            LOCAL_CLOCK.with(|current_local_clock| {
190                *current_local_clock.inner.borrow_mut() = None;
191            });
192        }
193
194        /// A [`Clock`] for testing purposes.
195        ///
196        /// The timestamp stored by the clock can be adjusted through the
197        /// `adjust_by` and `set_time` functions.
198        /// Following calls to `get_time` return the adjusted timestamp.
199        pub struct MockClock {
200            timestamp: Mutex<Timestamp>,
201        }
202
203        impl Default for MockClock {
204            fn default() -> Self {
205                Self::new()
206            }
207        }
208
209        impl MockClock {
210            /// Creates a new clock instance for testing purposes.
211            ///
212            /// The Clock will default to a default [`Timestamp`], which
213            /// represents the lowest possible [`Timestamp`] which can ever be
214            /// returned by this Clock. It is not allowed to adjust the Clock
215            /// to a [`Timestamp`] before its initial time.
216            pub fn new() -> MockClock {
217                MockClock {
218                    timestamp: Mutex::new(inner_now()),
219                }
220            }
221
222            /// Sets the current time to the given [`Timestamp`].
223            /// Follow-up calls to [`Self::get_time`] will return this [`Timestamp`]
224            /// until the time had been adjusted again.
225            pub fn set_time(&self, timestamp: Timestamp) {
226                let mut guard = self.timestamp.lock().unwrap();
227                *guard = timestamp;
228            }
229
230            /// Adjusts the time which is returned by the clock by the given
231            /// Duration. This will not perform any overflow or underflow checks.
232            /// If the clock went backwards more than to the initial zero
233            /// timestamp, the call will lead to a panic.
234            pub fn adjust_by(&self, duration: Duration) {
235                let mut guard = self.timestamp.lock().unwrap();
236                *guard += duration;
237            }
238        }
239
240        impl Clock for MockClock {
241            fn get_time(&self) -> Timestamp {
242                *self.timestamp.lock().unwrap()
243            }
244        }
245    }
246
247    #[test]
248    fn use_mocked_clock() {
249        use std::sync::Arc;
250
251        let original_time = now();
252
253        // Switch to a mock clock
254        let clock = Arc::new(testing::MockClock::new());
255        testing::set_local_clock(clock.clone());
256
257        let ts1 = now();
258        clock.adjust_by(Duration::from_millis(333));
259        let ts2 = now();
260        assert_eq!(ts2 - ts1, Duration::from_millis(333));
261        clock.adjust_by(Duration::from_millis(111));
262        let ts3 = now();
263        assert_eq!(ts3 - ts1, Duration::from_millis(444));
264
265        clock.set_time(ts1);
266        assert_eq!(ts1, now());
267
268        // Switch back to the original clock
269        testing::reset_local_clock();
270        let restored_time = now();
271        assert!(restored_time - original_time >= Duration::from_millis(0));
272        assert!(restored_time - original_time <= Duration::from_millis(100));
273    }
274}