summaryrefslogtreecommitdiffstats
path: root/third_party/rust/neqo-crypto/src/time.rs
blob: 84dbfdb4a5873c23f0794ca28d0cb67b674f0652 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#![allow(clippy::upper_case_acronyms)]

use std::{
    boxed::Box,
    convert::{TryFrom, TryInto},
    ops::Deref,
    os::raw::c_void,
    pin::Pin,
    time::{Duration, Instant},
};

use crate::{
    agentio::as_c_void,
    err::{Error, Res},
    once::OnceResult,
    ssl::{PRFileDesc, SSLTimeFunc},
};

include!(concat!(env!("OUT_DIR"), "/nspr_time.rs"));

experimental_api!(SSL_SetTimeFunc(
    fd: *mut PRFileDesc,
    cb: SSLTimeFunc,
    arg: *mut c_void,
));

/// This struct holds the zero time used for converting between `Instant` and `PRTime`.
#[derive(Debug)]
struct TimeZero {
    instant: Instant,
    prtime: PRTime,
}

impl TimeZero {
    /// This function sets a baseline from an instance of `Instant`.
    /// This allows for the possibility that code that uses these APIs will create
    /// instances of `Instant` before any of this code is run.  If `Instant`s older than
    /// `BASE_TIME` are used with these conversion functions, they will fail.
    /// To avoid that, we make sure that this sets the base time using the first value
    /// it sees if it is in the past.  If it is not, then use `Instant::now()` instead.
    pub fn baseline(t: Instant) -> Self {
        let now = Instant::now();
        let prnow = unsafe { PR_Now() };

        if now <= t {
            // `t` is in the future, just use `now`.
            Self {
                instant: now,
                prtime: prnow,
            }
        } else {
            let elapsed = Interval::from(now.duration_since(now));
            // An error from these unwrap functions would require
            // ridiculously long application running time.
            let prelapsed: PRTime = elapsed.try_into().unwrap();
            Self {
                instant: t,
                prtime: prnow.checked_sub(prelapsed).unwrap(),
            }
        }
    }
}

static mut BASE_TIME: OnceResult<TimeZero> = OnceResult::new();

fn get_base() -> &'static TimeZero {
    let f = || TimeZero {
        instant: Instant::now(),
        prtime: unsafe { PR_Now() },
    };
    unsafe { BASE_TIME.call_once(f) }
}

pub(crate) fn init() {
    _ = get_base();
}

/// Time wraps Instant and provides conversion functions into `PRTime`.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Time {
    t: Instant,
}

impl Deref for Time {
    type Target = Instant;
    fn deref(&self) -> &Self::Target {
        &self.t
    }
}

impl From<Instant> for Time {
    /// Convert from an Instant into a Time.
    fn from(t: Instant) -> Self {
        // Call `TimeZero::baseline(t)` so that time zero can be set.
        let f = || TimeZero::baseline(t);
        _ = unsafe { BASE_TIME.call_once(f) };
        Self { t }
    }
}

impl TryFrom<PRTime> for Time {
    type Error = Error;
    fn try_from(prtime: PRTime) -> Res<Self> {
        let base = get_base();
        if let Some(delta) = prtime.checked_sub(base.prtime) {
            let d = Duration::from_micros(delta.try_into()?);
            base.instant
                .checked_add(d)
                .map_or(Err(Error::TimeTravelError), |t| Ok(Self { t }))
        } else {
            Err(Error::TimeTravelError)
        }
    }
}

impl TryInto<PRTime> for Time {
    type Error = Error;
    fn try_into(self) -> Res<PRTime> {
        let base = get_base();
        let delta = self
            .t
            .checked_duration_since(base.instant)
            .ok_or(Error::TimeTravelError)?;
        if let Ok(d) = PRTime::try_from(delta.as_micros()) {
            d.checked_add(base.prtime).ok_or(Error::TimeTravelError)
        } else {
            Err(Error::TimeTravelError)
        }
    }
}

impl From<Time> for Instant {
    #[must_use]
    fn from(t: Time) -> Self {
        t.t
    }
}

/// Interval wraps Duration and provides conversion functions into `PRTime`.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Interval {
    d: Duration,
}

impl Deref for Interval {
    type Target = Duration;
    fn deref(&self) -> &Self::Target {
        &self.d
    }
}

impl TryFrom<PRTime> for Interval {
    type Error = Error;
    fn try_from(prtime: PRTime) -> Res<Self> {
        Ok(Self {
            d: Duration::from_micros(u64::try_from(prtime)?),
        })
    }
}

impl From<Duration> for Interval {
    fn from(d: Duration) -> Self {
        Self { d }
    }
}

impl TryInto<PRTime> for Interval {
    type Error = Error;
    fn try_into(self) -> Res<PRTime> {
        Ok(PRTime::try_from(self.d.as_micros())?)
    }
}

/// `TimeHolder` maintains a `PRTime` value in a form that is accessible to the TLS stack.
#[derive(Debug)]
pub struct TimeHolder {
    t: Pin<Box<PRTime>>,
}

impl TimeHolder {
    unsafe extern "C" fn time_func(arg: *mut c_void) -> PRTime {
        let p = arg as *const PRTime;
        *p.as_ref().unwrap()
    }

    pub fn bind(&mut self, fd: *mut PRFileDesc) -> Res<()> {
        unsafe { SSL_SetTimeFunc(fd, Some(Self::time_func), as_c_void(&mut self.t)) }
    }

    pub fn set(&mut self, t: Instant) -> Res<()> {
        *self.t = Time::from(t).try_into()?;
        Ok(())
    }
}

impl Default for TimeHolder {
    fn default() -> Self {
        TimeHolder { t: Box::pin(0) }
    }
}

#[cfg(test)]
mod test {
    use std::{
        convert::{TryFrom, TryInto},
        time::{Duration, Instant},
    };

    use super::{get_base, init, Interval, PRTime, Time};
    use crate::err::Res;

    #[test]
    fn convert_stable() {
        init();
        let now = Time::from(Instant::now());
        let pr: PRTime = now.try_into().expect("convert to PRTime with truncation");
        let t2 = Time::try_from(pr).expect("convert to Instant");
        let pr2: PRTime = t2.try_into().expect("convert to PRTime again");
        assert_eq!(pr, pr2);
        let t3 = Time::try_from(pr2).expect("convert to Instant again");
        assert_eq!(t2, t3);
    }

    #[test]
    fn past_time() {
        init();
        let base = get_base();
        assert!(Time::try_from(base.prtime - 1).is_err());
    }

    #[test]
    fn negative_time() {
        init();
        assert!(Time::try_from(-1).is_err());
    }

    #[test]
    fn negative_interval() {
        init();
        assert!(Interval::try_from(-1).is_err());
    }

    #[test]
    // We allow replace_consts here because
    // std::u64::max_value() isn't available
    // in all of our targets
    fn overflow_interval() {
        init();
        let interval = Interval::from(Duration::from_micros(u64::max_value()));
        let res: Res<PRTime> = interval.try_into();
        assert!(res.is_err());
    }
}