ssh_key/certificate/
unix_time.rs

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