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}