1
//! ASN.1 `GeneralizedTime` support.
2
#![cfg_attr(feature = "arbitrary", allow(clippy::integer_arithmetic))]
3

            
4
use crate::{
5
    datetime::{self, DateTime},
6
    ord::OrdIsValueOrd,
7
    DecodeValue, EncodeValue, ErrorKind, FixedTag, Header, Length, Reader, Result, Tag, Writer,
8
};
9
use core::time::Duration;
10

            
11
#[cfg(feature = "std")]
12
use {
13
    crate::{asn1::AnyRef, Error},
14
    std::time::SystemTime,
15
};
16

            
17
#[cfg(feature = "time")]
18
use time::PrimitiveDateTime;
19

            
20
/// ASN.1 `GeneralizedTime` type.
21
///
22
/// This type implements the validity requirements specified in
23
/// [RFC 5280 Section 4.1.2.5.2][1], namely:
24
///
25
/// > For the purposes of this profile, GeneralizedTime values MUST be
26
/// > expressed in Greenwich Mean Time (Zulu) and MUST include seconds
27
/// > (i.e., times are `YYYYMMDDHHMMSSZ`), even where the number of seconds
28
/// > is zero.  GeneralizedTime values MUST NOT include fractional seconds.
29
///
30
/// [1]: https://tools.ietf.org/html/rfc5280#section-4.1.2.5.2
31
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
32
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
33
pub struct GeneralizedTime(DateTime);
34

            
35
impl GeneralizedTime {
36
    /// Length of an RFC 5280-flavored ASN.1 DER-encoded [`GeneralizedTime`].
37
    const LENGTH: usize = 15;
38

            
39
    /// Create a [`GeneralizedTime`] from a [`DateTime`].
40
    pub const fn from_date_time(datetime: DateTime) -> Self {
41
        Self(datetime)
42
    }
43

            
44
    /// Convert this [`GeneralizedTime`] into a [`DateTime`].
45
    pub fn to_date_time(&self) -> DateTime {
46
        self.0
47
    }
48

            
49
    /// Create a new [`GeneralizedTime`] given a [`Duration`] since `UNIX_EPOCH`
50
    /// (a.k.a. "Unix time")
51
    pub fn from_unix_duration(unix_duration: Duration) -> Result<Self> {
52
        DateTime::from_unix_duration(unix_duration)
53
            .map(Into::into)
54
            .map_err(|_| Self::TAG.value_error())
55
    }
56

            
57
    /// Get the duration of this timestamp since `UNIX_EPOCH`.
58
    pub fn to_unix_duration(&self) -> Duration {
59
        self.0.unix_duration()
60
    }
61

            
62
    /// Instantiate from [`SystemTime`].
63
    #[cfg(feature = "std")]
64
    pub fn from_system_time(time: SystemTime) -> Result<Self> {
65
        DateTime::try_from(time)
66
            .map(Into::into)
67
            .map_err(|_| Self::TAG.value_error())
68
    }
69

            
70
    /// Convert to [`SystemTime`].
71
    #[cfg(feature = "std")]
72
    pub fn to_system_time(&self) -> SystemTime {
73
        self.0.to_system_time()
74
    }
75
}
76

            
77
impl_any_conversions!(GeneralizedTime);
78

            
79
impl<'a> DecodeValue<'a> for GeneralizedTime {
80
    fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self> {
81
        if Self::LENGTH != usize::try_from(header.length)? {
82
            return Err(Self::TAG.value_error());
83
        }
84

            
85
        let mut bytes = [0u8; Self::LENGTH];
86
        reader.read_into(&mut bytes)?;
87

            
88
        match bytes {
89
            // RFC 5280 requires mandatory seconds and Z-normalized time zone
90
            [y1, y2, y3, y4, mon1, mon2, day1, day2, hour1, hour2, min1, min2, sec1, sec2, b'Z'] => {
91
                let year = u16::from(datetime::decode_decimal(Self::TAG, y1, y2)?)
92
                    .checked_mul(100)
93
                    .and_then(|y| {
94
                        y.checked_add(datetime::decode_decimal(Self::TAG, y3, y4).ok()?.into())
95
                    })
96
                    .ok_or(ErrorKind::DateTime)?;
97
                let month = datetime::decode_decimal(Self::TAG, mon1, mon2)?;
98
                let day = datetime::decode_decimal(Self::TAG, day1, day2)?;
99
                let hour = datetime::decode_decimal(Self::TAG, hour1, hour2)?;
100
                let minute = datetime::decode_decimal(Self::TAG, min1, min2)?;
101
                let second = datetime::decode_decimal(Self::TAG, sec1, sec2)?;
102

            
103
                DateTime::new(year, month, day, hour, minute, second)
104
                    .map_err(|_| Self::TAG.value_error())
105
                    .and_then(|dt| Self::from_unix_duration(dt.unix_duration()))
106
            }
107
            _ => Err(Self::TAG.value_error()),
108
        }
109
    }
110
}
111

            
112
impl EncodeValue for GeneralizedTime {
113
    fn value_len(&self) -> Result<Length> {
114
        Self::LENGTH.try_into()
115
    }
116

            
117
    fn encode_value(&self, writer: &mut impl Writer) -> Result<()> {
118
        let year_hi = u8::try_from(self.0.year() / 100)?;
119
        let year_lo = u8::try_from(self.0.year() % 100)?;
120

            
121
        datetime::encode_decimal(writer, Self::TAG, year_hi)?;
122
        datetime::encode_decimal(writer, Self::TAG, year_lo)?;
123
        datetime::encode_decimal(writer, Self::TAG, self.0.month())?;
124
        datetime::encode_decimal(writer, Self::TAG, self.0.day())?;
125
        datetime::encode_decimal(writer, Self::TAG, self.0.hour())?;
126
        datetime::encode_decimal(writer, Self::TAG, self.0.minutes())?;
127
        datetime::encode_decimal(writer, Self::TAG, self.0.seconds())?;
128
        writer.write_byte(b'Z')
129
    }
130
}
131

            
132
impl FixedTag for GeneralizedTime {
133
    const TAG: Tag = Tag::GeneralizedTime;
134
}
135

            
136
impl OrdIsValueOrd for GeneralizedTime {}
137

            
138
impl From<&GeneralizedTime> for GeneralizedTime {
139
    fn from(value: &GeneralizedTime) -> GeneralizedTime {
140
        *value
141
    }
142
}
143

            
144
impl From<GeneralizedTime> for DateTime {
145
    fn from(utc_time: GeneralizedTime) -> DateTime {
146
        utc_time.0
147
    }
148
}
149

            
150
impl From<&GeneralizedTime> for DateTime {
151
    fn from(utc_time: &GeneralizedTime) -> DateTime {
152
        utc_time.0
153
    }
154
}
155

            
156
impl From<DateTime> for GeneralizedTime {
157
    fn from(datetime: DateTime) -> Self {
158
        Self::from_date_time(datetime)
159
    }
160
}
161

            
162
impl From<&DateTime> for GeneralizedTime {
163
    fn from(datetime: &DateTime) -> Self {
164
        Self::from_date_time(*datetime)
165
    }
166
}
167

            
168
impl<'a> DecodeValue<'a> for DateTime {
169
    fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self> {
170
        Ok(GeneralizedTime::decode_value(reader, header)?.into())
171
    }
172
}
173

            
174
impl EncodeValue for DateTime {
175
    fn value_len(&self) -> Result<Length> {
176
        GeneralizedTime::from(self).value_len()
177
    }
178

            
179
    fn encode_value(&self, writer: &mut impl Writer) -> Result<()> {
180
        GeneralizedTime::from(self).encode_value(writer)
181
    }
182
}
183

            
184
impl FixedTag for DateTime {
185
    const TAG: Tag = Tag::GeneralizedTime;
186
}
187

            
188
impl OrdIsValueOrd for DateTime {}
189

            
190
#[cfg(feature = "std")]
191
impl<'a> DecodeValue<'a> for SystemTime {
192
    fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self> {
193
        Ok(GeneralizedTime::decode_value(reader, header)?.into())
194
    }
195
}
196

            
197
#[cfg(feature = "std")]
198
impl EncodeValue for SystemTime {
199
    fn value_len(&self) -> Result<Length> {
200
        GeneralizedTime::try_from(self)?.value_len()
201
    }
202

            
203
    fn encode_value(&self, writer: &mut impl Writer) -> Result<()> {
204
        GeneralizedTime::try_from(self)?.encode_value(writer)
205
    }
206
}
207

            
208
#[cfg(feature = "std")]
209
impl From<GeneralizedTime> for SystemTime {
210
    fn from(time: GeneralizedTime) -> SystemTime {
211
        time.to_system_time()
212
    }
213
}
214

            
215
#[cfg(feature = "std")]
216
impl From<&GeneralizedTime> for SystemTime {
217
    fn from(time: &GeneralizedTime) -> SystemTime {
218
        time.to_system_time()
219
    }
220
}
221

            
222
#[cfg(feature = "std")]
223
impl TryFrom<SystemTime> for GeneralizedTime {
224
    type Error = Error;
225

            
226
    fn try_from(time: SystemTime) -> Result<GeneralizedTime> {
227
        GeneralizedTime::from_system_time(time)
228
    }
229
}
230

            
231
#[cfg(feature = "std")]
232
impl TryFrom<&SystemTime> for GeneralizedTime {
233
    type Error = Error;
234

            
235
    fn try_from(time: &SystemTime) -> Result<GeneralizedTime> {
236
        GeneralizedTime::from_system_time(*time)
237
    }
238
}
239

            
240
#[cfg(feature = "std")]
241
impl<'a> TryFrom<AnyRef<'a>> for SystemTime {
242
    type Error = Error;
243

            
244
    fn try_from(any: AnyRef<'a>) -> Result<SystemTime> {
245
        GeneralizedTime::try_from(any).map(|s| s.to_system_time())
246
    }
247
}
248

            
249
#[cfg(feature = "std")]
250
impl FixedTag for SystemTime {
251
    const TAG: Tag = Tag::GeneralizedTime;
252
}
253

            
254
#[cfg(feature = "std")]
255
impl OrdIsValueOrd for SystemTime {}
256

            
257
#[cfg(feature = "time")]
258
impl<'a> DecodeValue<'a> for PrimitiveDateTime {
259
    fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self> {
260
        GeneralizedTime::decode_value(reader, header)?.try_into()
261
    }
262
}
263

            
264
#[cfg(feature = "time")]
265
impl EncodeValue for PrimitiveDateTime {
266
    fn value_len(&self) -> Result<Length> {
267
        GeneralizedTime::try_from(self)?.value_len()
268
    }
269

            
270
    fn encode_value(&self, writer: &mut impl Writer) -> Result<()> {
271
        GeneralizedTime::try_from(self)?.encode_value(writer)
272
    }
273
}
274

            
275
#[cfg(feature = "time")]
276
impl FixedTag for PrimitiveDateTime {
277
    const TAG: Tag = Tag::GeneralizedTime;
278
}
279

            
280
#[cfg(feature = "time")]
281
impl OrdIsValueOrd for PrimitiveDateTime {}
282

            
283
#[cfg(feature = "time")]
284
impl TryFrom<PrimitiveDateTime> for GeneralizedTime {
285
    type Error = Error;
286

            
287
    fn try_from(time: PrimitiveDateTime) -> Result<GeneralizedTime> {
288
        Ok(GeneralizedTime::from_date_time(DateTime::try_from(time)?))
289
    }
290
}
291

            
292
#[cfg(feature = "time")]
293
impl TryFrom<&PrimitiveDateTime> for GeneralizedTime {
294
    type Error = Error;
295

            
296
    fn try_from(time: &PrimitiveDateTime) -> Result<GeneralizedTime> {
297
        Self::try_from(*time)
298
    }
299
}
300

            
301
#[cfg(feature = "time")]
302
impl TryFrom<GeneralizedTime> for PrimitiveDateTime {
303
    type Error = Error;
304

            
305
    fn try_from(time: GeneralizedTime) -> Result<PrimitiveDateTime> {
306
        time.to_date_time().try_into()
307
    }
308
}
309

            
310
#[cfg(test)]
311
mod tests {
312
    use super::GeneralizedTime;
313
    use crate::{Decode, Encode, SliceWriter};
314
    use hex_literal::hex;
315

            
316
    #[test]
317
    fn round_trip() {
318
        let example_bytes = hex!("18 0f 31 39 39 31 30 35 30 36 32 33 34 35 34 30 5a");
319
        let utc_time = GeneralizedTime::from_der(&example_bytes).unwrap();
320
        assert_eq!(utc_time.to_unix_duration().as_secs(), 673573540);
321

            
322
        let mut buf = [0u8; 128];
323
        let mut encoder = SliceWriter::new(&mut buf);
324
        utc_time.encode(&mut encoder).unwrap();
325
        assert_eq!(example_bytes, encoder.finish().unwrap());
326
    }
327
}