Source code

Revision control

Copy as Markdown

Other Tools

// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// <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 crate::nss_prelude::PRInt64;
use crate::err::{Error, Res};
//use crate::prio::PRFileDesc;
//use crate::ssl::SSLTimeFunc;
use once_cell::sync::OnceCell;
//use std::boxed::Box;
use std::convert::{TryFrom, TryInto};
use std::ops::Deref;
//use std::os::raw::c_void;
//use std::pin::Pin;
use std::time::{Duration, Instant};
include!(concat!(env!("OUT_DIR"), "/nspr_time.rs"));
/* TODO move to exp module
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 BASE_TIME: OnceCell<TimeZero> = OnceCell::new();
fn get_base() -> &'static TimeZero {
BASE_TIME.get_or_init(|| TimeZero {
instant: Instant::now(),
prtime: unsafe { PR_Now() },
})
}
pub fn init() {
let _ = 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.
BASE_TIME.get_or_init(|| TimeZero::baseline(t));
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())?)
}
}
/* TODO: restore, experimental only.
/// `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), &mut *self.t as *mut _ as *mut c_void) }
}
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 super::{get_base, init, Interval, PRTime, Time};
use crate::err::Res;
use std::convert::{TryFrom, TryInto};
use std::time::{Duration, Instant};
#[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());
}
}