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#[cfg_attr(all(feature = "ffi", not(test)), safer_ffi_gen::ffi_type)]
19#[derive(
20    Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, MlsSize, MlsEncode, MlsDecode,
21)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
23#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
24#[repr(transparent)]
25pub struct MlsTime {
26    seconds: u64,
27}
28
29impl MlsTime {
30    /// Create a timestamp from a duration since unix epoch.
31    pub fn from_duration_since_epoch(duration: Duration) -> MlsTime {
32        Self::from(duration)
33    }
34
35    /// Number of seconds since the unix epoch.
36    pub fn seconds_since_epoch(&self) -> u64 {
37        self.seconds
38    }
39}
40
41impl core::ops::Sub<MlsTime> for MlsTime {
42    type Output = Duration;
43
44    fn sub(self, rhs: Self) -> Duration {
45        Duration::from_secs(self.seconds - rhs.seconds)
46    }
47}
48
49impl core::ops::Sub<Duration> for MlsTime {
50    type Output = MlsTime;
51
52    fn sub(self, rhs: Duration) -> MlsTime {
53        MlsTime::from(self.seconds - rhs.as_secs())
54    }
55}
56
57impl core::ops::Add<Duration> for MlsTime {
58    type Output = MlsTime;
59
60    fn add(self, rhs: Duration) -> MlsTime {
61        MlsTime::from(self.seconds + rhs.as_secs())
62    }
63}
64
65#[cfg(all(not(target_arch = "wasm32"), feature = "std"))]
66impl MlsTime {
67    /// Current system time.
68    pub fn now() -> Self {
69        Self {
70            seconds: std::time::SystemTime::now()
71                .duration_since(std::time::SystemTime::UNIX_EPOCH)
72                .unwrap_or_default()
73                .as_secs(),
74        }
75    }
76}
77
78impl From<u64> for MlsTime {
79    fn from(value: u64) -> Self {
80        Self { seconds: value }
81    }
82}
83
84impl From<Duration> for MlsTime {
85    fn from(value: Duration) -> MlsTime {
86        Self {
87            seconds: value.as_secs(),
88        }
89    }
90}
91
92impl From<MlsTime> for Duration {
93    fn from(value: MlsTime) -> Duration {
94        Duration::from_secs(value.seconds)
95    }
96}
97
98#[cfg(all(not(target_arch = "wasm32"), feature = "std"))]
99#[derive(Debug, thiserror::Error)]
100#[error("Overflow while adding {0:?}")]
101/// Overflow in time conversion.
102pub struct TimeOverflow(Duration);
103
104#[cfg(all(not(target_arch = "wasm32"), feature = "std"))]
105impl TryFrom<MlsTime> for std::time::SystemTime {
106    type Error = TimeOverflow;
107
108    fn try_from(value: MlsTime) -> Result<std::time::SystemTime, Self::Error> {
109        let duration = Duration::from(value);
110        std::time::SystemTime::UNIX_EPOCH
111            .checked_add(duration)
112            .ok_or(TimeOverflow(duration))
113    }
114}
115
116#[cfg(all(not(target_arch = "wasm32"), feature = "std"))]
117impl TryFrom<std::time::SystemTime> for MlsTime {
118    type Error = std::time::SystemTimeError;
119
120    fn try_from(value: std::time::SystemTime) -> Result<MlsTime, Self::Error> {
121        let duration = value.duration_since(std::time::SystemTime::UNIX_EPOCH)?;
122        Ok(MlsTime::from(duration))
123    }
124}
125
126#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
127#[wasm_bindgen(inline_js = r#"
128export function date_now() {
129  return Date.now();
130}"#)]
131extern "C" {
132    fn date_now() -> f64;
133}
134
135#[cfg(all(target_arch = "wasm32", target_os = "emscripten"))]
136extern "C" {
137    #[link_name = "emscripten_date_now"]
138    fn date_now() -> f64;
139}
140
141#[cfg(target_arch = "wasm32")]
142impl MlsTime {
143    pub fn now() -> Self {
144        Self {
145            seconds: (unsafe { date_now() } / 1000.0) as u64,
146        }
147    }
148}