Coverage Report

Created: 2026-05-18 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}