1use crate::*;
2use alloc::format;
3#[cfg(not(feature = "std"))]
4use alloc::string::String;
5use core::fmt;
6use nom::{AsBytes, Input as _};
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_tryfrom_any!(GeneralizedTime);
175
176impl<'i> BerParser<'i> for GeneralizedTime {
177 type Error = BerError<Input<'i>>;
178
179 fn from_ber_content(
180 header: &'_ Header<'i>,
181 input: Input<'i>,
182 ) -> IResult<Input<'i>, Self, Self::Error> {
183 if header.is_constructed() {
186 return Err(BerError::nom_err_input(&input, InnerError::Unsupported));
187 }
188
189 fn is_visible(b: u8) -> bool {
190 (0x20..=0x7f).contains(&b)
191 }
192 if !input.iter_elements().all(is_visible) {
193 return Err(BerError::nom_err_input(
194 &input,
195 InnerError::StringInvalidCharset,
196 ));
197 }
198
199 let (rem, data) = input.take_split(input.len());
200 let time = GeneralizedTime::from_bytes(data.as_bytes2())
201 .map_err(|e| BerError::nom_err_input(&data, e.into()))?;
202 Ok((rem, time))
203 }
204}
205
206impl<'i> DerParser<'i> for GeneralizedTime {
207 type Error = BerError<Input<'i>>;
208
209 fn from_der_content(
210 header: &'_ Header<'i>,
211 input: Input<'i>,
212 ) -> IResult<Input<'i>, Self, Self::Error> {
213 header.assert_primitive_input(&input).map_err(Err::Error)?;
215
216 fn is_visible(b: u8) -> bool {
217 (0x20..=0x7f).contains(&b)
218 }
219 if !input.iter_elements().all(is_visible) {
220 return Err(BerError::nom_err_input(
221 &input,
222 InnerError::StringInvalidCharset,
223 ));
224 }
225
226 let (rem, data) = input.take_split(input.len());
227
228 if data.as_bytes2().last() != Some(&b'Z') {
230 return Err(BerError::nom_err_input(
231 &data,
232 InnerError::DerConstraintFailed(DerConstraint::MissingTimeZone),
233 ));
234 }
235 if data.as_bytes2().contains(&b',') {
239 return Err(BerError::nom_err_input(
240 &data,
241 InnerError::DerConstraintFailed(DerConstraint::MissingSeconds),
242 ));
243 }
244
245 let time = GeneralizedTime::from_bytes(data.as_bytes2())
246 .map_err(|e| BerError::nom_err_input(&data, e.into()))?;
247 Ok((rem, time))
248 }
249}
250
251impl fmt::Display for GeneralizedTime {
252 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
253 let dt = &self.0;
254 let fsec = match self.0.millisecond {
255 Some(v) => format!(".{}", v),
256 None => String::new(),
257 };
258 match dt.tz {
259 ASN1TimeZone::Undefined => write!(
260 f,
261 "{:04}-{:02}-{:02} {:02}:{:02}:{:02}{}",
262 dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, fsec
263 ),
264 ASN1TimeZone::Z => write!(
265 f,
266 "{:04}-{:02}-{:02} {:02}:{:02}:{:02}{}Z",
267 dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, fsec
268 ),
269 ASN1TimeZone::Offset(hh, mm) => {
270 let (s, hh) = if hh > 0 { ('+', hh) } else { ('-', -hh) };
271 write!(
272 f,
273 "{:04}-{:02}-{:02} {:02}:{:02}:{:02}{}{}{:02}{:02}",
274 dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, fsec, s, hh, mm
275 )
276 }
277 }
278 }
279}
280
281impl CheckDerConstraints for GeneralizedTime {
282 fn check_constraints(any: &Any) -> Result<()> {
283 if any.data.as_bytes().last() != Some(&b'Z') {
285 return Err(Error::DerConstraintFailed(DerConstraint::MissingTimeZone));
286 }
287 if any.data.as_bytes2().contains(&b',') {
291 return Err(Error::DerConstraintFailed(DerConstraint::MissingSeconds));
292 }
293 Ok(())
294 }
295}
296
297impl DerAutoDerive for GeneralizedTime {}
298
299impl Tagged for GeneralizedTime {
300 const TAG: Tag = Tag::GeneralizedTime;
301}
302
303#[cfg(feature = "std")]
304const _: () = {
305 use std::io::Write;
306
307 impl ToBer for GeneralizedTime {
308 type Encoder = Primitive<{ Tag::GeneralizedTime.0 }>;
309
310 fn ber_content_len(&self) -> Length {
311 let num_digits = match self.0.millisecond {
318 None => 0,
319 Some(v) => 1 + v.to_string().len(),
320 };
321
322 Length::Definite(15 + num_digits)
323 }
324
325 fn ber_write_content<W: Write>(&self, target: &mut W) -> SerializeResult<usize> {
326 let fractional = match self.0.millisecond {
327 None => "".to_string(),
328 Some(v) => format!(".{}", v),
329 };
330 let num_digits = fractional.len();
331 write!(
332 target,
333 "{:04}{:02}{:02}{:02}{:02}{:02}{}Z",
334 self.0.year,
335 self.0.month,
336 self.0.day,
337 self.0.hour,
338 self.0.minute,
339 self.0.second,
340 fractional,
341 )?;
342 Ok(15 + num_digits)
344 }
345
346 fn ber_tag_info(&self) -> (Class, bool, Tag) {
347 (Self::CLASS, false, Self::TAG)
348 }
349 }
350
351 impl_toder_from_tober!(TY GeneralizedTime);
352};
353
354#[cfg(test)]
355mod tests {
356 use hex_literal::hex;
357
358 use crate::{ASN1TimeZone, DerConstraint, DerParser, GeneralizedTime, InnerError};
359
360 #[test]
361 fn parse_der_generalizedtime() {
362 let input = &hex!("18 0F 32 30 30 32 31 32 31 33 31 34 32 39 32 33 5A FF");
363 let (rem, result) = GeneralizedTime::parse_der(input.into()).expect("parsing failed");
364 assert_eq!(rem.as_bytes2(), &[0xff]);
365 #[cfg(feature = "datetime")]
366 {
367 use time::macros::datetime;
368 let datetime = datetime! {2002-12-13 14:29:23 UTC};
369 assert_eq!(result.utc_datetime(), Ok(datetime));
370 }
371 let _ = result;
372 let input = b"\x18\x1019851106210627.3";
374 let result = GeneralizedTime::parse_der(input.into()).expect_err("should not parse");
375 assert!(matches!(
376 result,
377 nom::Err::Error(e) if *e.inner() == InnerError::DerConstraintFailed(DerConstraint::MissingTimeZone)
378
379 ));
380 let input = b"\x18\x1119851106210627.3Z";
382 let (rem, result) = GeneralizedTime::parse_der(input.into()).expect("parsing failed");
383 assert!(rem.is_empty());
384 assert_eq!(result.0.millisecond, Some(300));
385 assert_eq!(result.0.tz, ASN1TimeZone::Z);
386 #[cfg(feature = "datetime")]
387 {
388 use time::macros::datetime;
389 let datetime = datetime! {1985-11-06 21:06:27.3 UTC};
390 assert_eq!(result.utc_datetime(), Ok(datetime));
391 }
392 #[cfg(feature = "std")]
393 let _ = result.to_string();
394 let input = b"\x18\x1519851106210627.3-0500";
397 let result = GeneralizedTime::parse_der(input.into()).expect_err("should not parse");
398 assert!(matches!(
399 result,
400 nom::Err::Error(e) if *e.inner() == InnerError::DerConstraintFailed(DerConstraint::MissingTimeZone)
401
402 ));
403 }
404
405 #[cfg(feature = "std")]
406 mod tests_std {
407 use hex_literal::hex;
408
409 use crate::{ASN1DateTime, ASN1TimeZone, GeneralizedTime, ToBer};
410
411 #[test]
412 fn tober_generalizedtime() {
413 let datetime = ASN1DateTime::new(2013, 12, 2, 14, 29, 23, None, ASN1TimeZone::Z);
415 let time = GeneralizedTime::new(datetime);
416 let mut v: Vec<u8> = Vec::new();
417 time.ber_encode(&mut v).expect("serialization failed");
418 let expected = &[&hex!("18 0f") as &[u8], b"20131202142923Z"].concat();
419 assert_eq!(&v, expected);
420
421 let datetime = ASN1DateTime::new(1999, 12, 31, 23, 59, 59, Some(123), ASN1TimeZone::Z);
423 let time = GeneralizedTime::new(datetime);
424 let mut v: Vec<u8> = Vec::new();
425 time.ber_encode(&mut v).expect("serialization failed");
426 let expected = &[&hex!("18 13") as &[u8], b"19991231235959.123Z"].concat();
427 assert_eq!(&v, expected);
428 }
429 }
430}