Skip to content

Commit 631107d

Browse files
authored
der: const GeneralizedTime::from_date_time (#1718)
* feat: const DateTime::new * der: const DateTime::new * der: const GeneralizedTime::to_date_time * test: add max valid/invalid generalized time * fix cargo clippy &
1 parent c1d90bf commit 631107d

File tree

2 files changed

+123
-17
lines changed

2 files changed

+123
-17
lines changed

der/src/asn1/generalized_time.rs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ impl GeneralizedTime {
4040
}
4141

4242
/// Convert this [`GeneralizedTime`] into a [`DateTime`].
43-
pub fn to_date_time(&self) -> DateTime {
43+
pub const fn to_date_time(&self) -> DateTime {
4444
self.0
4545
}
4646

@@ -347,4 +347,52 @@ mod tests {
347347
utc_time.encode(&mut encoder).unwrap();
348348
assert_eq!(example_bytes, encoder.finish().unwrap());
349349
}
350+
351+
#[test]
352+
fn max_valid_generalized_time() {
353+
let example_bytes = "\x18\x0f99991231235959Z".as_bytes();
354+
let utc_time = GeneralizedTime::from_der(example_bytes).unwrap();
355+
assert_eq!(utc_time.to_unix_duration().as_secs(), 253402300799);
356+
357+
let mut buf = [0u8; 128];
358+
let mut encoder = SliceWriter::new(&mut buf);
359+
utc_time.encode(&mut encoder).unwrap();
360+
assert_eq!(example_bytes, encoder.finish().unwrap());
361+
}
362+
363+
#[test]
364+
fn invalid_year_generalized_time() {
365+
let example_bytes = "\x18\x0f999@1231235959Z".as_bytes();
366+
assert!(GeneralizedTime::from_der(example_bytes).is_err());
367+
}
368+
369+
#[test]
370+
fn invalid_month_generalized_time() {
371+
let example_bytes = "\x18\x0f99991331235959Z".as_bytes();
372+
assert!(GeneralizedTime::from_der(example_bytes).is_err());
373+
}
374+
375+
#[test]
376+
fn invalid_day_generalized_time() {
377+
let example_bytes = "\x18\x0f99991232235959Z".as_bytes();
378+
assert!(GeneralizedTime::from_der(example_bytes).is_err());
379+
}
380+
381+
#[test]
382+
fn invalid_hour_generalized_time() {
383+
let example_bytes = "\x18\x0f99991231245959Z".as_bytes();
384+
assert!(GeneralizedTime::from_der(example_bytes).is_err());
385+
}
386+
387+
#[test]
388+
fn invalid_minute_generalized_time() {
389+
let example_bytes = "\x18\x0f99991231236059Z".as_bytes();
390+
assert!(GeneralizedTime::from_der(example_bytes).is_err());
391+
}
392+
393+
#[test]
394+
fn invalid_second_generalized_time() {
395+
let example_bytes = "\x18\x0f99991231235960Z".as_bytes();
396+
assert!(GeneralizedTime::from_der(example_bytes).is_err());
397+
}
350398
}

der/src/datetime.rs

Lines changed: 74 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use core::{fmt, str::FromStr, time::Duration};
1111
#[cfg(feature = "std")]
1212
use std::time::{SystemTime, UNIX_EPOCH};
1313

14+
use const_range::const_contains_u8;
1415
#[cfg(feature = "time")]
1516
use time::PrimitiveDateTime;
1617

@@ -69,18 +70,42 @@ impl DateTime {
6970
};
7071

7172
/// Create a new [`DateTime`] from the given UTC time components.
73+
pub const fn new(
74+
year: u16,
75+
month: u8,
76+
day: u8,
77+
hour: u8,
78+
minutes: u8,
79+
seconds: u8,
80+
) -> Result<Self> {
81+
match Self::from_ymd_hms(year, month, day, hour, minutes, seconds) {
82+
Some(date) => Ok(date),
83+
None => Err(Error::from_kind(ErrorKind::DateTime)),
84+
}
85+
}
86+
87+
/// Create a new [`DateTime`] from the given UTC time components.
88+
///
89+
/// Returns `None` if the value is outside the supported date range.
7290
// TODO(tarcieri): checked arithmetic
7391
#[allow(clippy::arithmetic_side_effects)]
74-
pub fn new(year: u16, month: u8, day: u8, hour: u8, minutes: u8, seconds: u8) -> Result<Self> {
92+
pub(crate) const fn from_ymd_hms(
93+
year: u16,
94+
month: u8,
95+
day: u8,
96+
hour: u8,
97+
minutes: u8,
98+
seconds: u8,
99+
) -> Option<Self> {
75100
// Basic validation of the components.
76101
if year < MIN_YEAR
77-
|| !(1..=12).contains(&month)
78-
|| !(1..=31).contains(&day)
79-
|| !(0..=23).contains(&hour)
80-
|| !(0..=59).contains(&minutes)
81-
|| !(0..=59).contains(&seconds)
102+
|| !const_contains_u8(1..=12, month)
103+
|| !const_contains_u8(1..=31, day)
104+
|| !const_contains_u8(0..=23, hour)
105+
|| !const_contains_u8(0..=59, minutes)
106+
|| !const_contains_u8(0..=59, seconds)
82107
{
83-
return Err(ErrorKind::DateTime.into());
108+
return None;
84109
}
85110

86111
let leap_years =
@@ -102,28 +127,28 @@ impl DateTime {
102127
10 => (273, 31),
103128
11 => (304, 30),
104129
12 => (334, 31),
105-
_ => return Err(ErrorKind::DateTime.into()),
130+
_ => return None,
106131
};
107132

108133
if day > mdays || day == 0 {
109-
return Err(ErrorKind::DateTime.into());
134+
return None;
110135
}
111136

112-
ydays += u16::from(day) - 1;
137+
ydays += day as u16 - 1;
113138

114139
if is_leap_year && month > 2 {
115140
ydays += 1;
116141
}
117142

118-
let days = u64::from(year - 1970) * 365 + u64::from(leap_years) + u64::from(ydays);
119-
let time = u64::from(seconds) + (u64::from(minutes) * 60) + (u64::from(hour) * 3600);
143+
let days = ((year - 1970) as u64) * 365 + leap_years as u64 + ydays as u64;
144+
let time = seconds as u64 + (minutes as u64 * 60) + (hour as u64 * 3600);
120145
let unix_duration = Duration::from_secs(time + days * 86400);
121146

122-
if unix_duration > MAX_UNIX_DURATION {
123-
return Err(ErrorKind::DateTime.into());
147+
if unix_duration.as_secs() > MAX_UNIX_DURATION.as_secs() {
148+
return None;
124149
}
125150

126-
Ok(Self {
151+
Some(Self {
127152
year,
128153
month,
129154
day,
@@ -136,7 +161,7 @@ impl DateTime {
136161

137162
/// Compute a [`DateTime`] from the given [`Duration`] since the `UNIX_EPOCH`.
138163
///
139-
/// Returns `None` if the value is outside the supported date range.
164+
/// Returns `Err` if the value is outside the supported date range.
140165
// TODO(tarcieri): checked arithmetic
141166
#[allow(clippy::arithmetic_side_effects)]
142167
pub fn from_unix_duration(unix_duration: Duration) -> Result<Self> {
@@ -430,6 +455,16 @@ fn decode_year(year: &[u8; 4]) -> Result<u16> {
430455
Ok(u16::from(hi) * 100 + u16::from(lo))
431456
}
432457

458+
mod const_range {
459+
use core::ops::RangeInclusive;
460+
461+
/// const [`RangeInclusive::contains`]
462+
#[inline]
463+
pub const fn const_contains_u8(range: RangeInclusive<u8>, item: u8) -> bool {
464+
item >= *range.start() && item <= *range.end()
465+
}
466+
}
467+
433468
#[cfg(test)]
434469
#[allow(clippy::unwrap_used)]
435470
mod tests {
@@ -447,6 +482,29 @@ mod tests {
447482
assert!(!is_date_valid(2100, 2, 29, 0, 0, 0));
448483
}
449484

485+
#[test]
486+
fn invalid_dates() {
487+
assert!(!is_date_valid(2, 3, 25, 0, 0, 0));
488+
489+
assert!(is_date_valid(1970, 1, 26, 0, 0, 0));
490+
assert!(!is_date_valid(1969, 1, 26, 0, 0, 0));
491+
assert!(!is_date_valid(1968, 1, 26, 0, 0, 0));
492+
assert!(!is_date_valid(1600, 1, 26, 0, 0, 0));
493+
494+
assert!(is_date_valid(2039, 2, 27, 0, 0, 0));
495+
assert!(!is_date_valid(2039, 2, 27, 255, 0, 0));
496+
assert!(!is_date_valid(2039, 2, 27, 0, 255, 0));
497+
assert!(!is_date_valid(2039, 2, 27, 0, 0, 255));
498+
499+
assert!(is_date_valid(2055, 12, 31, 0, 0, 0));
500+
assert!(is_date_valid(2055, 12, 31, 23, 0, 0));
501+
assert!(!is_date_valid(2055, 12, 31, 24, 0, 0));
502+
assert!(is_date_valid(2055, 12, 31, 0, 59, 0));
503+
assert!(!is_date_valid(2055, 12, 31, 0, 60, 0));
504+
assert!(is_date_valid(2055, 12, 31, 0, 0, 59));
505+
assert!(!is_date_valid(2055, 12, 31, 0, 0, 60));
506+
}
507+
450508
#[test]
451509
fn from_str() {
452510
let datetime = "2001-01-02T12:13:14Z".parse::<DateTime>().unwrap();

0 commit comments

Comments
 (0)