ssh_key/certificate/
unix_time.rs

1//! Unix timestamps.
2
3use crate::{Error, Result};
4use core::fmt;
5use core::fmt::Formatter;
6use encoding::{Decode, Encode, Reader, Writer};
7
8#[cfg(feature = "std")]
9use std::time::{Duration, SystemTime, UNIX_EPOCH};
10
11/// Maximum allowed value for a Unix timestamp.
12pub const MAX_SECS: u64 = i64::MAX as u64;
13
14/// Unix timestamps as used in OpenSSH certificates.
15#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord)]
16pub(super) struct UnixTime {
17    /// Number of seconds since the Unix epoch
18    secs: u64,
19
20    /// System time corresponding to this Unix timestamp
21    #[cfg(feature = "std")]
22    time: SystemTime,
23}
24
25impl UnixTime {
26    /// Create a new Unix timestamp.
27    ///
28    /// `secs` is the number of seconds since the Unix epoch and must be less
29    /// than or equal to `i64::MAX`.
30    #[cfg(not(feature = "std"))]
31    pub fn new(secs: u64) -> Result<Self> {
32        if secs <= MAX_SECS {
33            Ok(Self { secs })
34        } else {
35            Err(Error::Time)
36        }
37    }
38
39    /// Create a new Unix timestamp.
40    ///
41    /// This version requires `std` and ensures there's a valid `SystemTime`
42    /// representation with an infallible conversion (which also improves the
43    /// `Debug` output)
44    #[cfg(feature = "std")]
45    pub fn new(secs: u64) -> Result<Self> {
46        if secs > MAX_SECS {
47            return Err(Error::Time);
48        }
49
50        match UNIX_EPOCH.checked_add(Duration::from_secs(secs)) {
51            Some(time) => Ok(Self { secs, time }),
52            None => Err(Error::Time),
53        }
54    }
55
56    /// Get the current time as a Unix timestamp.
57    #[cfg(feature = "std")]
58    pub fn now() -> Result<Self> {
59        SystemTime::now().try_into()
60    }
61}
62
63impl Decode for UnixTime {
64    type Error = Error;
65
66    fn decode(reader: &mut impl Reader) -> Result<Self> {
67        u64::decode(reader)?.try_into()
68    }
69}
70
71impl Encode for UnixTime {
72    fn encoded_len(&self) -> encoding::Result<usize> {
73        self.secs.encoded_len()
74    }
75
76    fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
77        self.secs.encode(writer)?;
78        Ok(())
79    }
80}
81
82impl From<UnixTime> for u64 {
83    fn from(unix_time: UnixTime) -> u64 {
84        unix_time.secs
85    }
86}
87
88#[cfg(feature = "std")]
89impl From<UnixTime> for SystemTime {
90    fn from(unix_time: UnixTime) -> SystemTime {
91        unix_time.time
92    }
93}
94
95impl TryFrom<u64> for UnixTime {
96    type Error = Error;
97
98    fn try_from(unix_secs: u64) -> Result<UnixTime> {
99        Self::new(unix_secs)
100    }
101}
102
103#[cfg(feature = "std")]
104impl TryFrom<SystemTime> for UnixTime {
105    type Error = Error;
106
107    fn try_from(time: SystemTime) -> Result<UnixTime> {
108        Self::new(time.duration_since(UNIX_EPOCH)?.as_secs())
109    }
110}
111
112impl fmt::Debug for UnixTime {
113    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
114        write!(f, "{}", self.secs)
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::{UnixTime, MAX_SECS};
121    use crate::Error;
122
123    #[test]
124    fn new_with_max_secs() {
125        assert!(UnixTime::new(MAX_SECS).is_ok());
126    }
127
128    #[test]
129    fn new_over_max_secs_returns_error() {
130        assert_eq!(UnixTime::new(MAX_SECS + 1), Err(Error::Time));
131    }
132}