Skip to main content

mls_rs_core/
time.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// Copyright by contributors to this project.
3// SPDX-License-Identifier: (Apache-2.0 OR MIT)
4
5use core::time::Duration;
6use mls_rs_codec::{MlsDecode, MlsEncode, MlsSize};
7
8#[cfg(target_arch = "wasm32")]
9use wasm_bindgen::prelude::*;
10
11/// Wasm-compatible representation of a timestamp.
12///
13/// This type represents a point in time after 1970. The precision is seconds.
14///
15/// Since `MlsTime` always represents a timestamp after 1970, it can be trivially
16/// converted to/from a standard library [`Duration`] value (measuring the time since
17/// the start of the Unix epoch).
18#[derive(
19    Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, MlsSize, MlsEncode, MlsDecode,
20)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
23#[repr(transparent)]
24pub struct MlsTime {
25    seconds: u64,
26}
27
28impl MlsTime {
29    /// Create a timestamp from a duration since unix epoch.
30    pub fn from_duration_since_epoch(duration: Duration) -> MlsTime {
31        Self::from(duration)
32    }
33
34    /// Number of seconds since the unix epoch.
35    pub fn seconds_since_epoch(&self) -> u64 {
36        self.seconds
37    }
38}
39
40impl core::ops::Sub<MlsTime> for MlsTime {
41    type Output = Duration;
42
43    fn sub(self, rhs: Self) -> Duration {
44        Duration::from_secs(self.seconds - rhs.seconds)
45    }
46}
47
48impl core::ops::Sub<Duration> for MlsTime {
49    type Output = MlsTime;
50
51    fn sub(self, rhs: Duration) -> MlsTime {
52        MlsTime::from(self.seconds - rhs.as_secs())
53    }
54}
55
56impl core::ops::Add<Duration> for MlsTime {
57    type Output = MlsTime;
58
59    fn add(self, rhs: Duration) -> MlsTime {
60        MlsTime::from(self.seconds + rhs.as_secs())
61    }
62}
63
64#[cfg(all(not(target_arch = "wasm32"), feature = "std"))]
65impl MlsTime {
66    /// Current system time.
67    pub fn now() -> Self {
68        Self {
69            seconds: std::time::SystemTime::now()
70                .duration_since(std::time::SystemTime::UNIX_EPOCH)
71                .unwrap_or_default()
72                .as_secs(),
73        }
74    }
75}
76
77impl From<u64> for MlsTime {
78    fn from(value: u64) -> Self {
79        Self { seconds: value }
80    }
81}
82
83impl From<Duration> for MlsTime {
84    fn from(value: Duration) -> MlsTime {
85        Self {
86            seconds: value.as_secs(),
87        }
88    }
89}
90
91impl From<MlsTime> for Duration {
92    fn from(value: MlsTime) -> Duration {
93        Duration::from_secs(value.seconds)
94    }
95}
96
97#[cfg(all(not(target_arch = "wasm32"), feature = "std"))]
98#[derive(Debug, thiserror::Error)]
99#[error("Overflow while adding {0:?}")]
100/// Overflow in time conversion.
101pub struct TimeOverflow(Duration);
102
103#[cfg(all(not(target_arch = "wasm32"), feature = "std"))]
104impl TryFrom<MlsTime> for std::time::SystemTime {
105    type Error = TimeOverflow;
106
107    fn try_from(value: MlsTime) -> Result<std::time::SystemTime, Self::Error> {
108        let duration = Duration::from(value);
109        std::time::SystemTime::UNIX_EPOCH
110            .checked_add(duration)
111            .ok_or(TimeOverflow(duration))
112    }
113}
114
115#[cfg(all(not(target_arch = "wasm32"), feature = "std"))]
116impl TryFrom<std::time::SystemTime> for MlsTime {
117    type Error = std::time::SystemTimeError;
118
119    fn try_from(value: std::time::SystemTime) -> Result<MlsTime, Self::Error> {
120        let duration = value.duration_since(std::time::SystemTime::UNIX_EPOCH)?;
121        Ok(MlsTime::from(duration))
122    }
123}
124
125#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
126#[wasm_bindgen(inline_js = r#"
127export function date_now() {
128  return Date.now();
129}"#)]
130extern "C" {
131    fn date_now() -> f64;
132}
133
134#[cfg(all(target_arch = "wasm32", target_os = "emscripten"))]
135extern "C" {
136    #[link_name = "emscripten_date_now"]
137    fn date_now() -> f64;
138}
139
140#[cfg(target_arch = "wasm32")]
141impl MlsTime {
142    pub fn now() -> Self {
143        Self {
144            seconds: (unsafe { date_now() } / 1000.0) as u64,
145        }
146    }
147}