/rust/git/checkouts/nss-rs-71e20fe79ef91440/9b94ca3/src/time.rs
Line | Count | Source |
1 | | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
2 | | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
3 | | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
4 | | // option. This file may not be copied, modified, or distributed |
5 | | // except according to those terms. |
6 | | |
7 | | #![expect( |
8 | | clippy::unwrap_used, |
9 | | reason = "Let's assume the use of `unwrap` was checked when the use of `unsafe` was reviewed." |
10 | | )] |
11 | | |
12 | | use std::{ |
13 | | convert::{TryFrom as _, TryInto as _}, |
14 | | ops::Deref, |
15 | | os::raw::c_void, |
16 | | pin::Pin, |
17 | | time::{Duration, Instant}, |
18 | | }; |
19 | | |
20 | | use once_cell::sync::OnceCell; |
21 | | |
22 | | use crate::{ |
23 | | agent::as_c_void, |
24 | | err::{Error, Res}, |
25 | | prio::PRFileDesc, |
26 | | ssl::SSLTimeFunc, |
27 | | }; |
28 | | |
29 | | include!(concat!(env!("OUT_DIR"), "/nspr_time.rs")); |
30 | | |
31 | | experimental_api!(SSL_SetTimeFunc( |
32 | | fd: *mut PRFileDesc, |
33 | | cb: SSLTimeFunc, |
34 | | arg: *mut c_void, |
35 | | )); |
36 | | |
37 | | /// This struct holds the zero time used for converting between `Instant` and `PRTime`. |
38 | | #[derive(Debug)] |
39 | | struct TimeZero { |
40 | | instant: Instant, |
41 | | prtime: PRTime, |
42 | | } |
43 | | |
44 | | impl TimeZero { |
45 | | /// This function sets a baseline from an instance of `Instant`. |
46 | | /// This allows for the possibility that code that uses these APIs will create |
47 | | /// instances of `Instant` before any of this code is run. If `Instant`s older than |
48 | | /// `BASE_TIME` are used with these conversion functions, they will fail. |
49 | | /// To avoid that, we make sure that this sets the base time using the first value |
50 | | /// it sees if it is in the past. If it is not, then use `Instant::now()` instead. |
51 | 0 | pub fn baseline(t: Instant) -> Self { |
52 | | #[expect( |
53 | | clippy::disallowed_methods, |
54 | | reason = "Special handling for NSPR time conversion" |
55 | | )] |
56 | 0 | let now = Instant::now(); |
57 | 0 | let prnow = unsafe { PR_Now() }; |
58 | | |
59 | 0 | if now <= t { |
60 | | // `t` is in the future, just use `now`. |
61 | 0 | Self { |
62 | 0 | instant: now, |
63 | 0 | prtime: prnow, |
64 | 0 | } |
65 | | } else { |
66 | 0 | let elapsed = Interval::from(now.duration_since(now)); |
67 | | // An error from these unwrap functions would require |
68 | | // ridiculously long application running time. |
69 | 0 | let prelapsed: PRTime = elapsed.try_into().unwrap(); |
70 | 0 | Self { |
71 | 0 | instant: t, |
72 | 0 | prtime: prnow.checked_sub(prelapsed).unwrap(), |
73 | 0 | } |
74 | | } |
75 | 0 | } |
76 | | } |
77 | | |
78 | | static BASE_TIME: OnceCell<TimeZero> = OnceCell::new(); |
79 | | |
80 | 3.06k | fn get_base() -> &'static TimeZero { |
81 | 3.06k | BASE_TIME.get_or_init(|| TimeZero { |
82 | | #[expect( |
83 | | clippy::disallowed_methods, |
84 | | reason = "Special handling for NSPR time conversion" |
85 | | )] |
86 | 6 | instant: Instant::now(), |
87 | 6 | prtime: unsafe { PR_Now() }, |
88 | 6 | }) |
89 | 3.06k | } |
90 | | |
91 | 6 | pub fn init() { |
92 | 6 | _ = get_base(); |
93 | 6 | } |
94 | | |
95 | | /// Time wraps Instant and provides conversion functions into `PRTime`. |
96 | | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
97 | | pub struct Time { |
98 | | t: Instant, |
99 | | } |
100 | | |
101 | | impl Deref for Time { |
102 | | type Target = Instant; |
103 | 0 | fn deref(&self) -> &Self::Target { |
104 | 0 | &self.t |
105 | 0 | } |
106 | | } |
107 | | |
108 | | impl From<Instant> for Time { |
109 | | /// Convert from an Instant into a Time. |
110 | 3.06k | fn from(t: Instant) -> Self { |
111 | | // Call `TimeZero::baseline(t)` so that time zero can be set. |
112 | 3.06k | BASE_TIME.get_or_init(|| TimeZero::baseline(t)); |
113 | 3.06k | Self { t } |
114 | 3.06k | } |
115 | | } |
116 | | |
117 | | impl TryFrom<PRTime> for Time { |
118 | | type Error = Error; |
119 | 0 | fn try_from(prtime: PRTime) -> Res<Self> { |
120 | 0 | let base = get_base(); |
121 | 0 | let delta = prtime.checked_sub(base.prtime).ok_or(Error::TimeTravel)?; |
122 | 0 | let d = Duration::from_micros(u64::try_from(delta.abs())?); |
123 | 0 | let t = if delta >= 0 { |
124 | 0 | base.instant.checked_add(d) |
125 | | } else { |
126 | 0 | base.instant.checked_sub(d) |
127 | | }; |
128 | 0 | let t = t.ok_or(Error::TimeTravel)?; |
129 | 0 | Ok(Self { t }) |
130 | 0 | } |
131 | | } |
132 | | |
133 | | impl TryInto<PRTime> for Time { |
134 | | type Error = Error; |
135 | 3.06k | fn try_into(self) -> Res<PRTime> { |
136 | 3.06k | let base = get_base(); |
137 | | |
138 | 3.06k | self.t.checked_duration_since(base.instant).map_or_else( |
139 | 0 | || { |
140 | | // Try to go backwards from the base time. |
141 | 0 | let backwards = base.instant - self.t; // infallible |
142 | 0 | PRTime::try_from(backwards.as_micros()).map_or(Err(Error::TimeTravel), |d| { |
143 | 0 | base.prtime.checked_sub(d).ok_or(Error::TimeTravel) |
144 | 0 | }) |
145 | 0 | }, |
146 | 3.06k | |delta| { |
147 | 3.06k | PRTime::try_from(delta.as_micros()).map_or(Err(Error::TimeTravel), |d| { |
148 | 3.06k | d.checked_add(base.prtime).ok_or(Error::TimeTravel) |
149 | 3.06k | }) |
150 | 3.06k | }, |
151 | | ) |
152 | 3.06k | } |
153 | | } |
154 | | |
155 | | impl From<Time> for Instant { |
156 | 0 | fn from(t: Time) -> Self { |
157 | 0 | t.t |
158 | 0 | } |
159 | | } |
160 | | |
161 | | /// Interval wraps Duration and provides conversion functions into `PRTime`. |
162 | | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
163 | | pub struct Interval { |
164 | | d: Duration, |
165 | | } |
166 | | |
167 | | impl TryFrom<PRTime> for Interval { |
168 | | type Error = Error; |
169 | 0 | fn try_from(prtime: PRTime) -> Res<Self> { |
170 | | Ok(Self { |
171 | 0 | d: Duration::from_micros(u64::try_from(prtime)?), |
172 | | }) |
173 | 0 | } |
174 | | } |
175 | | |
176 | | impl From<Duration> for Interval { |
177 | 1.28k | fn from(d: Duration) -> Self { |
178 | 1.28k | Self { d } |
179 | 1.28k | } |
180 | | } |
181 | | |
182 | | impl TryInto<PRTime> for Interval { |
183 | | type Error = Error; |
184 | 1.28k | fn try_into(self) -> Res<PRTime> { |
185 | 1.28k | Ok(PRTime::try_from(self.d.as_micros())?) |
186 | 1.28k | } |
187 | | } |
188 | | |
189 | | /// `TimeHolder` maintains a `PRTime` value in a form that is accessible to the TLS stack. |
190 | | #[derive(Debug)] |
191 | | pub struct TimeHolder { |
192 | | t: Pin<Box<PRTime>>, |
193 | | } |
194 | | |
195 | | impl TimeHolder { |
196 | 3.06k | const unsafe extern "C" fn time_func(arg: *mut c_void) -> PRTime { |
197 | 3.06k | let p = arg as *const PRTime; |
198 | 3.06k | unsafe { *p.as_ref().unwrap() } |
199 | 3.06k | } |
200 | | |
201 | | #[expect(clippy::not_unsafe_ptr_arg_deref)] |
202 | 2.57k | pub fn bind(&mut self, fd: *mut PRFileDesc) -> Res<()> { |
203 | 2.57k | unsafe { SSL_SetTimeFunc(fd, Some(Self::time_func), as_c_void(&mut self.t)) } |
204 | 2.57k | } |
205 | | |
206 | 1.77k | pub fn set(&mut self, t: Instant) -> Res<()> { |
207 | 1.77k | *self.t = Time::from(t).try_into()?; |
208 | 1.77k | Ok(()) |
209 | 1.77k | } |
210 | | } |
211 | | |
212 | | impl Default for TimeHolder { |
213 | 2.57k | fn default() -> Self { |
214 | 2.57k | Self { t: Box::pin(0) } |
215 | 2.57k | } |
216 | | } |
217 | | |
218 | | #[cfg(test)] |
219 | | #[cfg_attr(coverage_nightly, coverage(off))] |
220 | | mod test { |
221 | | use std::{ |
222 | | convert::{TryFrom as _, TryInto as _}, |
223 | | time::{Duration, Instant}, |
224 | | }; |
225 | | |
226 | | use super::{Interval, PRTime, Time, TimeZero, get_base, init}; |
227 | | use crate::err::Res; |
228 | | |
229 | | #[test] |
230 | | fn convert_stable() { |
231 | | init(); |
232 | | let now = Time::from(test_fixture::now()); |
233 | | let pr: PRTime = now.try_into().expect("convert to PRTime with truncation"); |
234 | | let t2 = Time::try_from(pr).expect("convert to Instant"); |
235 | | let pr2: PRTime = t2.try_into().expect("convert to PRTime again"); |
236 | | assert_eq!(pr, pr2); |
237 | | let t3 = Time::try_from(pr2).expect("convert to Instant again"); |
238 | | assert_eq!(t2, t3); |
239 | | } |
240 | | |
241 | | #[test] |
242 | | fn past_prtime() { |
243 | | const DELTA: Duration = Duration::from_secs(1); |
244 | | init(); |
245 | | let base = get_base(); |
246 | | let delta_micros = PRTime::try_from(DELTA.as_micros()).unwrap(); |
247 | | println!("{} - {delta_micros}", base.prtime); |
248 | | let t = Time::try_from(base.prtime - delta_micros).unwrap(); |
249 | | assert_eq!(Instant::from(t) + DELTA, base.instant); |
250 | | } |
251 | | |
252 | | #[test] |
253 | | fn past_instant() { |
254 | | const DELTA: Duration = Duration::from_secs(1); |
255 | | init(); |
256 | | let base = get_base(); |
257 | | let t = Time::from(base.instant.checked_sub(DELTA).unwrap()); |
258 | | assert_eq!(Instant::from(t) + DELTA, base.instant); |
259 | | |
260 | | // Convert back to PRTime to cover the backwards conversion path. |
261 | | let pr: PRTime = t.try_into().expect("convert past time to PRTime"); |
262 | | let delta_micros = PRTime::try_from(DELTA.as_micros()).unwrap(); |
263 | | assert_eq!(pr, base.prtime - delta_micros); |
264 | | } |
265 | | |
266 | | #[test] |
267 | | #[expect(clippy::disallowed_methods, reason = "Test for special time handling")] |
268 | | fn timezero_baseline_future() { |
269 | | let tz = TimeZero::baseline(Instant::now() + Duration::from_secs(10)); |
270 | | let now = Instant::now(); |
271 | | assert!(tz.instant <= now && tz.instant + Duration::from_millis(100) > now); |
272 | | } |
273 | | |
274 | | #[test] |
275 | | #[expect(clippy::disallowed_methods, reason = "Test for special time handling")] |
276 | | fn timezero_baseline_past() { |
277 | | let past = Instant::now().checked_sub(Duration::from_secs(5)).unwrap(); |
278 | | assert_eq!(TimeZero::baseline(past).instant, past); |
279 | | } |
280 | | |
281 | | #[test] |
282 | | fn negative_interval() { |
283 | | init(); |
284 | | assert!(Interval::try_from(-1).is_err()); |
285 | | } |
286 | | |
287 | | #[test] |
288 | | // We allow replace_consts here because |
289 | | // std::u64::MAX isn't available |
290 | | // in all of our targets |
291 | | fn overflow_interval() { |
292 | | init(); |
293 | | let interval = Interval::from(Duration::from_micros(u64::MAX)); |
294 | | let res: Res<PRTime> = interval.try_into(); |
295 | | assert!(res.is_err()); |
296 | | } |
297 | | } |