1use crate::*;
2use alloc::format;
3#[cfg(not(feature = "std"))]
4use alloc::string::String;
5use core::convert::TryFrom;
6use core::fmt;
7#[cfg(feature = "datetime")]
8use time::OffsetDateTime;
9
10#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
11pub struct GeneralizedTime(pub ASN1DateTime);
12
13impl GeneralizedTime {
14 pub const fn new(datetime: ASN1DateTime) -> Self {
15 GeneralizedTime(datetime)
16 }
17
18 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
19 let (year, month, day, hour, minute, rem) = match bytes {
30 [year1, year2, year3, year4, mon1, mon2, day1, day2, hour1, hour2, min1, min2, rem @ ..] =>
31 {
32 let year_hi = decode_decimal(Self::TAG, *year1, *year2)?;
33 let year_lo = decode_decimal(Self::TAG, *year3, *year4)?;
34 let year = (year_hi as u32) * 100 + (year_lo as u32);
35 let month = decode_decimal(Self::TAG, *mon1, *mon2)?;
36 let day = decode_decimal(Self::TAG, *day1, *day2)?;
37 let hour = decode_decimal(Self::TAG, *hour1, *hour2)?;
38 let minute = decode_decimal(Self::TAG, *min1, *min2)?;
39 (year, month, day, hour, minute, rem)
40 }
41 _ => return Err(Self::TAG.invalid_value("malformed time string (not yymmddhhmm)")),
42 };
43 if rem.is_empty() {
44 return Err(Self::TAG.invalid_value("malformed time string"));
45 }
46 let (second, rem) = match rem {
48 [sec1, sec2, rem @ ..] => {
49 let second = decode_decimal(Self::TAG, *sec1, *sec2)?;
50 (second, rem)
51 }
52 _ => (0, rem),
53 };
54 if month > 12 || day > 31 || hour > 23 || minute > 59 || second > 59 {
55 return Err(Self::TAG.invalid_value("time components with invalid values"));
62 }
63 if rem.is_empty() {
64 return Ok(GeneralizedTime(ASN1DateTime::new(
66 year,
67 month,
68 day,
69 hour,
70 minute,
71 second,
72 None,
73 ASN1TimeZone::Undefined,
74 )));
75 }
76 let (millisecond, rem) = match rem {
78 [b'.' | b',', rem @ ..] => {
79 let mut fsecond = 0;
80 let mut rem = rem;
81 let mut digits = 0;
82 for idx in 0..=4 {
83 if rem.is_empty() {
84 if idx == 0 {
85 return Err(Self::TAG.invalid_value(
87 "malformed time string (dot or comma but no digits)",
88 ));
89 }
90 digits = idx;
91 break;
92 }
93 if idx == 4 {
94 return Err(
95 Self::TAG.invalid_value("malformed time string (invalid milliseconds)")
96 );
97 }
98 match rem[0] {
99 b'0'..=b'9' => {
100 fsecond = fsecond * 10 + (rem[0] - b'0') as u16;
102 }
103 b'Z' | b'+' | b'-' => {
104 digits = idx;
105 break;
106 }
107 _ => {
108 return Err(Self::TAG.invalid_value(
109 "malformed time string (invalid milliseconds/timezone)",
110 ))
111 }
112 }
113 rem = &rem[1..];
114 }
115 let fsecond = match digits {
118 1 => fsecond * 100,
119 2 => fsecond * 10,
120 _ => fsecond,
121 };
122 (Some(fsecond), rem)
123 }
124 _ => (None, rem),
125 };
126 if rem.is_empty() {
128 return Ok(GeneralizedTime(ASN1DateTime::new(
130 year,
131 month,
132 day,
133 hour,
134 minute,
135 second,
136 millisecond,
137 ASN1TimeZone::Undefined,
138 )));
139 }
140 let tz = match rem {
141 [b'Z'] => ASN1TimeZone::Z,
142 [b'+', h1, h2, m1, m2] => {
143 let hh = decode_decimal(Self::TAG, *h1, *h2)?;
144 let mm = decode_decimal(Self::TAG, *m1, *m2)?;
145 ASN1TimeZone::Offset(hh as i8, mm as i8)
146 }
147 [b'-', h1, h2, m1, m2] => {
148 let hh = decode_decimal(Self::TAG, *h1, *h2)?;
149 let mm = decode_decimal(Self::TAG, *m1, *m2)?;
150 ASN1TimeZone::Offset(-(hh as i8), mm as i8)
151 }
152 _ => return Err(Self::TAG.invalid_value("malformed time string: no time zone")),
153 };
154 Ok(GeneralizedTime(ASN1DateTime::new(
155 year,
156 month,
157 day,
158 hour,
159 minute,
160 second,
161 millisecond,
162 tz,
163 )))
164 }
165
166 #[cfg(feature = "datetime")]
168 #[cfg_attr(docsrs, doc(cfg(feature = "datetime")))]
169 pub fn utc_datetime(&self) -> Result<OffsetDateTime> {
170 self.0.to_datetime()
171 }
172}
173
174impl<'a> TryFrom<Any<'a>> for GeneralizedTime {
175 type Error = Error;
176
177 fn try_from(any: Any<'a>) -> Result<GeneralizedTime> {
178 TryFrom::try_from(&any)
179 }
180}
181
182impl<'a, 'b> TryFrom<&'b Any<'a>> for GeneralizedTime {
183 type Error = Error;
184
185 fn try_from(any: &'b Any<'a>) -> Result<GeneralizedTime> {
186 any.tag().assert_eq(Self::TAG)?;
187 #[allow(clippy::trivially_copy_pass_by_ref)]
188 fn is_visible(b: &u8) -> bool {
189 0x20 <= *b && *b <= 0x7f
190 }
191 if !any.data.iter().all(is_visible) {
192 return Err(Error::StringInvalidCharset);
193 }
194
195 GeneralizedTime::from_bytes(any.data)
196 }
197}
198
199impl fmt::Display for GeneralizedTime {
200 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201 let dt = &self.0;
202 let fsec = match self.0.millisecond {
203 Some(v) => format!(".{}", v),
204 None => String::new(),
205 };
206 match dt.tz {
207 ASN1TimeZone::Undefined => write!(
208 f,
209 "{:04}-{:02}-{:02} {:02}:{:02}:{:02}{}",
210 dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, fsec
211 ),
212 ASN1TimeZone::Z => write!(
213 f,
214 "{:04}-{:02}-{:02} {:02}:{:02}:{:02}{}Z",
215 dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, fsec
216 ),
217 ASN1TimeZone::Offset(hh, mm) => {
218 let (s, hh) = if hh > 0 { ('+', hh) } else { ('-', -hh) };
219 write!(
220 f,
221 "{:04}-{:02}-{:02} {:02}:{:02}:{:02}{}{}{:02}{:02}",
222 dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, fsec, s, hh, mm
223 )
224 }
225 }
226 }
227}
228
229impl CheckDerConstraints for GeneralizedTime {
230 fn check_constraints(any: &Any) -> Result<()> {
231 if any.data.last() != Some(&b'Z') {
233 return Err(Error::DerConstraintFailed(DerConstraint::MissingTimeZone));
234 }
235 if any.data.iter().any(|&b| b == b',') {
239 return Err(Error::DerConstraintFailed(DerConstraint::MissingSeconds));
240 }
241 Ok(())
242 }
243}
244
245impl DerAutoDerive for GeneralizedTime {}
246
247impl Tagged for GeneralizedTime {
248 const TAG: Tag = Tag::GeneralizedTime;
249}
250
251#[cfg(feature = "std")]
252impl ToDer for GeneralizedTime {
253 fn to_der_len(&self) -> Result<usize> {
254 let num_digits = match self.0.millisecond {
266 None => 0,
267 Some(v) => 1 + v.to_string().len(),
268 };
269 Ok(2 + 15 + num_digits)
270 }
271
272 fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
273 let num_digits = match self.0.millisecond {
275 None => 0,
276 Some(v) => 1 + v.to_string().len() as u8,
277 };
278 writer
279 .write(&[Self::TAG.0 as u8, 15 + num_digits])
280 .map_err(Into::into)
281 }
282
283 fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
284 let fractional = match self.0.millisecond {
285 None => "".to_string(),
286 Some(v) => format!(".{}", v),
287 };
288 let num_digits = fractional.len();
289 write!(
290 writer,
291 "{:04}{:02}{:02}{:02}{:02}{:02}{}Z",
292 self.0.year,
293 self.0.month,
294 self.0.day,
295 self.0.hour,
296 self.0.minute,
297 self.0.second,
298 fractional,
299 )?;
300 Ok(15 + num_digits)
302 }
303}