asn1_rs/asn1_types/
utctime.rs

1use crate::*;
2use core::convert::TryFrom;
3use core::fmt;
4#[cfg(feature = "datetime")]
5use time::OffsetDateTime;
6
7#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
8pub struct UtcTime(pub ASN1DateTime);
9
10impl UtcTime {
11    pub const fn new(datetime: ASN1DateTime) -> Self {
12        UtcTime(datetime)
13    }
14
15    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
16        // X.680 section 43 defines a UniversalTime as a VisibleString restricted to:
17        //
18        // a) the six digits YYMMDD where YY is the two low-order digits of the Christian year, MM is the month
19        // (counting January as 01), and DD is the day of the month (01 to 31); and
20        // b) either:
21        //   1) the four digits hhmm where hh is hour (00 to 23) and mm is minutes (00 to 59); or
22        //   2) the six digits hhmmss where hh and mm are as in 1) above, and ss is seconds (00 to 59); and
23        // c) either:
24        //   1) the character Z ; or
25        //   2) one of the characters + or - , followed by hhmm, where hh is hour and mm is minutes.
26        //
27        // XXX // RFC 5280 requires mandatory seconds and Z-normalized time zone
28        let (year, month, day, hour, minute, rem) = match bytes {
29            [year1, year2, mon1, mon2, day1, day2, hour1, hour2, min1, min2, rem @ ..] => {
30                let year = decode_decimal(Self::TAG, *year1, *year2)?;
31                let month = decode_decimal(Self::TAG, *mon1, *mon2)?;
32                let day = decode_decimal(Self::TAG, *day1, *day2)?;
33                let hour = decode_decimal(Self::TAG, *hour1, *hour2)?;
34                let minute = decode_decimal(Self::TAG, *min1, *min2)?;
35                (year, month, day, hour, minute, rem)
36            }
37            _ => return Err(Self::TAG.invalid_value("malformed time string (not yymmddhhmm)")),
38        };
39        if rem.is_empty() {
40            return Err(Self::TAG.invalid_value("malformed time string"));
41        }
42        // check for seconds
43        let (second, rem) = match rem {
44            [sec1, sec2, rem @ ..] => {
45                let second = decode_decimal(Self::TAG, *sec1, *sec2)?;
46                (second, rem)
47            }
48            _ => (0, rem),
49        };
50        if month > 12 || day > 31 || hour > 23 || minute > 59 || second > 59 {
51            return Err(Self::TAG.invalid_value("time components with invalid values"));
52        }
53        if rem.is_empty() {
54            return Err(Self::TAG.invalid_value("malformed time string"));
55        }
56        let tz = match rem {
57            [b'Z'] => ASN1TimeZone::Z,
58            [b'+', h1, h2, m1, m2] => {
59                let hh = decode_decimal(Self::TAG, *h1, *h2)?;
60                let mm = decode_decimal(Self::TAG, *m1, *m2)?;
61                ASN1TimeZone::Offset(hh as i8, mm as i8)
62            }
63            [b'-', h1, h2, m1, m2] => {
64                let hh = decode_decimal(Self::TAG, *h1, *h2)?;
65                let mm = decode_decimal(Self::TAG, *m1, *m2)?;
66                ASN1TimeZone::Offset(-(hh as i8), mm as i8)
67            }
68            _ => return Err(Self::TAG.invalid_value("malformed time string: no time zone")),
69        };
70        Ok(UtcTime(ASN1DateTime::new(
71            year as u32,
72            month,
73            day,
74            hour,
75            minute,
76            second,
77            None,
78            tz,
79        )))
80        // match *bytes {
81        //     [year1, year2, mon1, mon2, day1, day2, hour1, hour2, min1, min2, sec1, sec2, b'Z'] => {
82        //         let year = decode_decimal(Self::TAG, year1, year2)?;
83        //         let month = decode_decimal(Self::TAG, mon1, mon2)?;
84        //         let day = decode_decimal(Self::TAG, day1, day2)?;
85        //         let hour = decode_decimal(Self::TAG, hour1, hour2)?;
86        //         let minute = decode_decimal(Self::TAG, min1, min2)?;
87        //         let second = decode_decimal(Self::TAG, sec1, sec2)?;
88
89        //         // RFC 5280 rules for interpreting the year
90        //         let year = if year >= 50 { year + 1900 } else { year + 2000 };
91
92        //         Ok(UtcTime::new(year, month, day, hour, minute, second))
93        //     }
94        //     _ => Err(Error::InvalidValue),
95        // }
96    }
97
98    /// Return a ISO 8601 combined date and time with time zone.
99    #[cfg(feature = "datetime")]
100    #[cfg_attr(docsrs, doc(cfg(feature = "datetime")))]
101    #[inline]
102    pub fn utc_datetime(&self) -> Result<OffsetDateTime> {
103        self.0.to_datetime()
104    }
105
106    /// Return an adjusted ISO 8601 combined date and time with time zone.
107    /// According to Universal time definition in X.680 we add 2000 years
108    /// from 0 to 49 year and 1900 otherwise.
109    #[cfg(feature = "datetime")]
110    #[cfg_attr(docsrs, doc(cfg(feature = "datetime")))]
111    #[inline]
112    pub fn utc_adjusted_datetime(&self) -> Result<OffsetDateTime> {
113        self.0.to_datetime().and_then(|dt| {
114            let year = dt.year();
115            // We follow the Universal time definition in X.680 for interpreting
116            // the adjusted year
117            let year = if year >= 50 { year + 1900 } else { year + 2000 };
118            time::Date::from_calendar_date(year, dt.month(), dt.day())
119                .map(|d| dt.replace_date(d))
120                .map_err(|_e| Self::TAG.invalid_value("Invalid adjusted date"))
121        })
122    }
123
124    /// Returns the number of non-leap seconds since the midnight on January 1, 1970.
125    #[cfg(feature = "datetime")]
126    #[cfg_attr(docsrs, doc(cfg(feature = "datetime")))]
127    pub fn timestamp(&self) -> Result<i64> {
128        let dt = self.0.to_datetime()?;
129        Ok(dt.unix_timestamp())
130    }
131}
132
133impl<'a> TryFrom<Any<'a>> for UtcTime {
134    type Error = Error;
135
136    fn try_from(any: Any<'a>) -> Result<UtcTime> {
137        TryFrom::try_from(&any)
138    }
139}
140
141impl<'a, 'b> TryFrom<&'b Any<'a>> for UtcTime {
142    type Error = Error;
143
144    fn try_from(any: &'b Any<'a>) -> Result<UtcTime> {
145        any.tag().assert_eq(Self::TAG)?;
146        #[allow(clippy::trivially_copy_pass_by_ref)]
147        fn is_visible(b: &u8) -> bool {
148            0x20 <= *b && *b <= 0x7f
149        }
150        if !any.data.iter().all(is_visible) {
151            return Err(Error::StringInvalidCharset);
152        }
153
154        UtcTime::from_bytes(any.data)
155    }
156}
157
158impl fmt::Display for UtcTime {
159    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160        let dt = &self.0;
161        match dt.tz {
162            ASN1TimeZone::Z | ASN1TimeZone::Undefined => write!(
163                f,
164                "{:04}-{:02}-{:02} {:02}:{:02}:{:02}Z",
165                dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second
166            ),
167            ASN1TimeZone::Offset(hh, mm) => {
168                let (s, hh) = if hh > 0 { ('+', hh) } else { ('-', -hh) };
169                write!(
170                    f,
171                    "{:04}-{:02}-{:02} {:02}:{:02}:{:02}{}{:02}{:02}",
172                    dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, s, hh, mm
173                )
174            }
175        }
176    }
177}
178
179impl CheckDerConstraints for UtcTime {
180    fn check_constraints(_any: &Any) -> Result<()> {
181        Ok(())
182    }
183}
184
185impl DerAutoDerive for UtcTime {}
186
187impl Tagged for UtcTime {
188    const TAG: Tag = Tag::UtcTime;
189}
190
191#[cfg(feature = "std")]
192impl ToDer for UtcTime {
193    fn to_der_len(&self) -> Result<usize> {
194        // data:
195        // - 6 bytes for YYMMDD
196        // - 6 for hhmmss in DER (X.690 section 11.8.2)
197        // - 1 for the character Z in DER (X.690 section 11.8.1)
198        // data length: 13
199        //
200        // thus, length will always be on 1 byte (short length) and
201        // class+structure+tag also on 1
202        //
203        // total: 15 = 1 (class+constructed+tag) + 1 (length) + 13
204        Ok(15)
205    }
206
207    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
208        // see above for length value
209        writer.write(&[Self::TAG.0 as u8, 13]).map_err(Into::into)
210    }
211
212    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
213        write!(
214            writer,
215            "{:02}{:02}{:02}{:02}{:02}{:02}Z",
216            self.0.year, self.0.month, self.0.day, self.0.hour, self.0.minute, self.0.second,
217        )?;
218        // write_fmt returns (), see above for length value
219        Ok(13)
220    }
221}