1#![cfg_attr(feature = "arbitrary", allow(clippy::arithmetic_side_effects))]
3
4use crate::{
5 DecodeValue, EncodeValue, Error, ErrorKind, FixedTag, Header, Length, Reader, Result, Tag,
6 Writer,
7 datetime::{self, DateTime},
8 ord::OrdIsValueOrd,
9};
10use core::time::Duration;
11
12#[cfg(feature = "std")]
13use {crate::asn1::AnyRef, std::time::SystemTime};
14
15#[cfg(feature = "time")]
16use time::PrimitiveDateTime;
17
18#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
30#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
31pub struct GeneralizedTime(DateTime);
32
33impl GeneralizedTime {
34 const LENGTH: usize = 15;
36
37 #[must_use]
39 pub const fn from_date_time(datetime: DateTime) -> Self {
40 Self(datetime)
41 }
42
43 #[must_use]
45 pub const fn to_date_time(&self) -> DateTime {
46 self.0
47 }
48
49 pub fn from_unix_duration(unix_duration: Duration) -> Result<Self> {
55 DateTime::from_unix_duration(unix_duration)
56 .map(Into::into)
57 .map_err(|_| Self::TAG.value_error().into())
58 }
59
60 #[must_use]
62 pub fn to_unix_duration(&self) -> Duration {
63 self.0.unix_duration()
64 }
65
66 #[cfg(feature = "std")]
71 pub fn from_system_time(time: SystemTime) -> Result<Self> {
72 DateTime::try_from(time)
73 .map(Into::into)
74 .map_err(|_| Self::TAG.value_error().into())
75 }
76
77 #[cfg(feature = "std")]
79 #[must_use]
80 pub fn to_system_time(&self) -> SystemTime {
81 self.0.to_system_time()
82 }
83}
84
85impl_any_conversions!(GeneralizedTime);
86
87fn decode_from_values(
90 year: (u8, u8, u8, u8),
91 month: (u8, u8),
92 day: (u8, u8),
93 hour: (u8, u8),
94 min: (u8, u8),
95 sec: (u8, u8),
96) -> Result<GeneralizedTime> {
97 let year = u16::from(datetime::decode_decimal(
98 GeneralizedTime::TAG,
99 year.0,
100 year.1,
101 )?)
102 .checked_mul(100)
103 .and_then(|y| {
104 y.checked_add(
105 datetime::decode_decimal(GeneralizedTime::TAG, year.2, year.3)
106 .ok()?
107 .into(),
108 )
109 })
110 .ok_or(ErrorKind::DateTime)?;
111 let month = datetime::decode_decimal(GeneralizedTime::TAG, month.0, month.1)?;
112 let day = datetime::decode_decimal(GeneralizedTime::TAG, day.0, day.1)?;
113 let hour = datetime::decode_decimal(GeneralizedTime::TAG, hour.0, hour.1)?;
114 let minute = datetime::decode_decimal(GeneralizedTime::TAG, min.0, min.1)?;
115 let second = datetime::decode_decimal(GeneralizedTime::TAG, sec.0, sec.1)?;
116
117 let dt = DateTime::new(year, month, day, hour, minute, second)
118 .map_err(|_| GeneralizedTime::TAG.value_error())?;
119
120 GeneralizedTime::from_unix_duration(dt.unix_duration())
121}
122
123impl<'a> DecodeValue<'a> for GeneralizedTime {
124 type Error = Error;
125
126 fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self> {
127 if Self::LENGTH != usize::try_from(header.length())? {
128 return Err(reader.error(Self::TAG.value_error()));
129 }
130
131 let mut bytes = [0u8; Self::LENGTH];
132 reader.read_into(&mut bytes)?;
133
134 match bytes {
135 [
137 y1,
138 y2,
139 y3,
140 y4,
141 mon1,
142 mon2,
143 day1,
144 day2,
145 hour1,
146 hour2,
147 min1,
148 min2,
149 sec1,
150 sec2,
151 b'Z',
152 ] => decode_from_values(
153 (y1, y2, y3, y4),
154 (mon1, mon2),
155 (day1, day2),
156 (hour1, hour2),
157 (min1, min2),
158 (sec1, sec2),
159 )
160 .map_err(|err| reader.error(err.kind())),
161 _ => Err(reader.error(Self::TAG.value_error())),
162 }
163 }
164}
165
166impl EncodeValue for GeneralizedTime {
167 fn value_len(&self) -> Result<Length> {
168 Self::LENGTH.try_into()
169 }
170
171 fn encode_value(&self, writer: &mut impl Writer) -> Result<()> {
172 let year_hi = u8::try_from(self.0.year() / 100)?;
173 let year_lo = u8::try_from(self.0.year() % 100)?;
174
175 datetime::encode_decimal(writer, Self::TAG, year_hi)?;
176 datetime::encode_decimal(writer, Self::TAG, year_lo)?;
177 datetime::encode_decimal(writer, Self::TAG, self.0.month())?;
178 datetime::encode_decimal(writer, Self::TAG, self.0.day())?;
179 datetime::encode_decimal(writer, Self::TAG, self.0.hour())?;
180 datetime::encode_decimal(writer, Self::TAG, self.0.minutes())?;
181 datetime::encode_decimal(writer, Self::TAG, self.0.seconds())?;
182 writer.write_byte(b'Z')
183 }
184}
185
186impl FixedTag for GeneralizedTime {
187 const TAG: Tag = Tag::GeneralizedTime;
188}
189
190impl OrdIsValueOrd for GeneralizedTime {}
191
192impl From<&GeneralizedTime> for GeneralizedTime {
193 fn from(value: &GeneralizedTime) -> GeneralizedTime {
194 *value
195 }
196}
197
198impl From<GeneralizedTime> for DateTime {
199 fn from(utc_time: GeneralizedTime) -> DateTime {
200 utc_time.0
201 }
202}
203
204impl From<&GeneralizedTime> for DateTime {
205 fn from(utc_time: &GeneralizedTime) -> DateTime {
206 utc_time.0
207 }
208}
209
210impl From<DateTime> for GeneralizedTime {
211 fn from(datetime: DateTime) -> Self {
212 Self::from_date_time(datetime)
213 }
214}
215
216impl From<&DateTime> for GeneralizedTime {
217 fn from(datetime: &DateTime) -> Self {
218 Self::from_date_time(*datetime)
219 }
220}
221
222impl<'a> DecodeValue<'a> for DateTime {
223 type Error = Error;
224
225 fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self> {
226 Ok(GeneralizedTime::decode_value(reader, header)?.into())
227 }
228}
229
230impl EncodeValue for DateTime {
231 fn value_len(&self) -> Result<Length> {
232 GeneralizedTime::from(self).value_len()
233 }
234
235 fn encode_value(&self, writer: &mut impl Writer) -> Result<()> {
236 GeneralizedTime::from(self).encode_value(writer)
237 }
238}
239
240impl FixedTag for DateTime {
241 const TAG: Tag = Tag::GeneralizedTime;
242}
243
244impl OrdIsValueOrd for DateTime {}
245
246#[cfg(feature = "std")]
247impl<'a> DecodeValue<'a> for SystemTime {
248 type Error = Error;
249
250 fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self> {
251 Ok(GeneralizedTime::decode_value(reader, header)?.into())
252 }
253}
254
255#[cfg(feature = "std")]
256impl EncodeValue for SystemTime {
257 fn value_len(&self) -> Result<Length> {
258 GeneralizedTime::try_from(self)?.value_len()
259 }
260
261 fn encode_value(&self, writer: &mut impl Writer) -> Result<()> {
262 GeneralizedTime::try_from(self)?.encode_value(writer)
263 }
264}
265
266#[cfg(feature = "std")]
267impl From<GeneralizedTime> for SystemTime {
268 fn from(time: GeneralizedTime) -> SystemTime {
269 time.to_system_time()
270 }
271}
272
273#[cfg(feature = "std")]
274impl From<&GeneralizedTime> for SystemTime {
275 fn from(time: &GeneralizedTime) -> SystemTime {
276 time.to_system_time()
277 }
278}
279
280#[cfg(feature = "std")]
281impl TryFrom<SystemTime> for GeneralizedTime {
282 type Error = Error;
283
284 fn try_from(time: SystemTime) -> Result<GeneralizedTime> {
285 GeneralizedTime::from_system_time(time)
286 }
287}
288
289#[cfg(feature = "std")]
290impl TryFrom<&SystemTime> for GeneralizedTime {
291 type Error = Error;
292
293 fn try_from(time: &SystemTime) -> Result<GeneralizedTime> {
294 GeneralizedTime::from_system_time(*time)
295 }
296}
297
298#[cfg(feature = "std")]
299impl<'a> TryFrom<AnyRef<'a>> for SystemTime {
300 type Error = Error;
301
302 fn try_from(any: AnyRef<'a>) -> Result<SystemTime> {
303 GeneralizedTime::try_from(any).map(|s| s.to_system_time())
304 }
305}
306
307#[cfg(feature = "std")]
308impl FixedTag for SystemTime {
309 const TAG: Tag = Tag::GeneralizedTime;
310}
311
312#[cfg(feature = "std")]
313impl OrdIsValueOrd for SystemTime {}
314
315#[cfg(feature = "time")]
316impl<'a> DecodeValue<'a> for PrimitiveDateTime {
317 type Error = Error;
318
319 fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self> {
320 GeneralizedTime::decode_value(reader, header)?.try_into()
321 }
322}
323
324#[cfg(feature = "time")]
325impl EncodeValue for PrimitiveDateTime {
326 fn value_len(&self) -> Result<Length> {
327 GeneralizedTime::try_from(self)?.value_len()
328 }
329
330 fn encode_value(&self, writer: &mut impl Writer) -> Result<()> {
331 GeneralizedTime::try_from(self)?.encode_value(writer)
332 }
333}
334
335#[cfg(feature = "time")]
336impl FixedTag for PrimitiveDateTime {
337 const TAG: Tag = Tag::GeneralizedTime;
338}
339
340#[cfg(feature = "time")]
341impl OrdIsValueOrd for PrimitiveDateTime {}
342
343#[cfg(feature = "time")]
344impl TryFrom<PrimitiveDateTime> for GeneralizedTime {
345 type Error = Error;
346
347 fn try_from(time: PrimitiveDateTime) -> Result<GeneralizedTime> {
348 Ok(GeneralizedTime::from_date_time(DateTime::try_from(time)?))
349 }
350}
351
352#[cfg(feature = "time")]
353impl TryFrom<&PrimitiveDateTime> for GeneralizedTime {
354 type Error = Error;
355
356 fn try_from(time: &PrimitiveDateTime) -> Result<GeneralizedTime> {
357 Self::try_from(*time)
358 }
359}
360
361#[cfg(feature = "time")]
362impl TryFrom<GeneralizedTime> for PrimitiveDateTime {
363 type Error = Error;
364
365 fn try_from(time: GeneralizedTime) -> Result<PrimitiveDateTime> {
366 time.to_date_time().try_into()
367 }
368}
369
370#[cfg(test)]
371#[allow(clippy::unwrap_used)]
372mod tests {
373 use super::GeneralizedTime;
374 use crate::{Decode, Encode, SliceWriter};
375 use hex_literal::hex;
376
377 #[test]
378 fn round_trip() {
379 let example_bytes = hex!("18 0f 31 39 39 31 30 35 30 36 32 33 34 35 34 30 5a");
380 let utc_time = GeneralizedTime::from_der(&example_bytes).unwrap();
381 assert_eq!(utc_time.to_unix_duration().as_secs(), 673573540);
382
383 let mut buf = [0u8; 128];
384 let mut writer = SliceWriter::new(&mut buf);
385 utc_time.encode(&mut writer).unwrap();
386 assert_eq!(example_bytes, writer.finish().unwrap());
387 }
388
389 #[test]
390 fn max_valid_generalized_time() {
391 let example_bytes = "\x18\x0f99991231235959Z".as_bytes();
392 let utc_time = GeneralizedTime::from_der(example_bytes).unwrap();
393 assert_eq!(utc_time.to_unix_duration().as_secs(), 253402300799);
394
395 let mut buf = [0u8; 128];
396 let mut writer = SliceWriter::new(&mut buf);
397 utc_time.encode(&mut writer).unwrap();
398 assert_eq!(example_bytes, writer.finish().unwrap());
399 }
400
401 #[test]
402 fn invalid_year_generalized_time() {
403 let example_bytes = "\x18\x0f999@1231235959Z".as_bytes();
404 assert!(GeneralizedTime::from_der(example_bytes).is_err());
405 }
406
407 #[test]
408 fn invalid_month_generalized_time() {
409 let example_bytes = "\x18\x0f99991331235959Z".as_bytes();
410 assert!(GeneralizedTime::from_der(example_bytes).is_err());
411 }
412
413 #[test]
414 fn invalid_day_generalized_time() {
415 let example_bytes = "\x18\x0f99991232235959Z".as_bytes();
416 assert!(GeneralizedTime::from_der(example_bytes).is_err());
417 }
418
419 #[test]
420 fn invalid_hour_generalized_time() {
421 let example_bytes = "\x18\x0f99991231245959Z".as_bytes();
422 assert!(GeneralizedTime::from_der(example_bytes).is_err());
423 }
424
425 #[test]
426 fn invalid_minute_generalized_time() {
427 let example_bytes = "\x18\x0f99991231236059Z".as_bytes();
428 assert!(GeneralizedTime::from_der(example_bytes).is_err());
429 }
430
431 #[test]
432 fn invalid_second_generalized_time() {
433 let example_bytes = "\x18\x0f99991231235960Z".as_bytes();
434 assert!(GeneralizedTime::from_der(example_bytes).is_err());
435 }
436}