Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

der: expose DateTime through public API #75

Merged
merged 1 commit into from
Oct 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 4 additions & 6 deletions der/src/asn1/generalized_time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ impl DecodeValue<'_> for GeneralizedTime {

DateTime::new(year, month, day, hour, minute, second)
.and_then(|dt| dt.unix_duration())
.ok_or_else(|| Self::TAG.value_error())
.map_err(|_| Self::TAG.value_error())
.and_then(Self::new)
}
_ => Err(Self::TAG.value_error()),
Expand All @@ -95,9 +95,7 @@ impl Encodable for GeneralizedTime {
fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> {
Header::new(Self::TAG, Self::LENGTH)?.encode(encoder)?;

let datetime =
DateTime::from_unix_duration(self.0).ok_or_else(|| Self::TAG.value_error())?;

let datetime = DateTime::from_unix_duration(self.0).map_err(|_| Self::TAG.value_error())?;
let year_hi = datetime.year() / 100;
let year_lo = datetime.year() % 100;

Expand All @@ -106,8 +104,8 @@ impl Encodable for GeneralizedTime {
datetime::encode_decimal(encoder, Self::TAG, datetime.month())?;
datetime::encode_decimal(encoder, Self::TAG, datetime.day())?;
datetime::encode_decimal(encoder, Self::TAG, datetime.hour())?;
datetime::encode_decimal(encoder, Self::TAG, datetime.minute())?;
datetime::encode_decimal(encoder, Self::TAG, datetime.second())?;
datetime::encode_decimal(encoder, Self::TAG, datetime.minutes())?;
datetime::encode_decimal(encoder, Self::TAG, datetime.seconds())?;
encoder.byte(b'Z')
}
}
Expand Down
10 changes: 4 additions & 6 deletions der/src/asn1/utc_time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ impl DecodeValue<'_> for UtcTime {

DateTime::new(year, month, day, hour, minute, second)
.and_then(|dt| dt.unix_duration())
.ok_or_else(|| Self::TAG.value_error())
.map_err(|_| Self::TAG.value_error())
.and_then(Self::new)
}
_ => Err(Self::TAG.value_error()),
Expand All @@ -104,9 +104,7 @@ impl Encodable for UtcTime {
fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> {
Header::new(Self::TAG, Self::LENGTH)?.encode(encoder)?;

let datetime =
DateTime::from_unix_duration(self.0).ok_or_else(|| Self::TAG.value_error())?;

let datetime = DateTime::from_unix_duration(self.0).map_err(|_| Self::TAG.value_error())?;
debug_assert!((1950..2050).contains(&datetime.year()));

let year = match datetime.year() {
Expand All @@ -119,8 +117,8 @@ impl Encodable for UtcTime {
datetime::encode_decimal(encoder, Self::TAG, datetime.month())?;
datetime::encode_decimal(encoder, Self::TAG, datetime.day())?;
datetime::encode_decimal(encoder, Self::TAG, datetime.hour())?;
datetime::encode_decimal(encoder, Self::TAG, datetime.minute())?;
datetime::encode_decimal(encoder, Self::TAG, datetime.second())?;
datetime::encode_decimal(encoder, Self::TAG, datetime.minutes())?;
datetime::encode_decimal(encoder, Self::TAG, datetime.seconds())?;
encoder.byte(b'Z')
}
}
Expand Down
136 changes: 68 additions & 68 deletions der/src/datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Copyright (c) 2016 The humantime Developers
// Released under the MIT OR Apache 2.0 licenses

use crate::{Encoder, Result, Tag};
use crate::{Encoder, ErrorKind, Result, Tag};
use core::time::Duration;

/// Minimum year allowed in [`DateTime`] values.
Expand All @@ -15,35 +15,14 @@ const MIN_YEAR: u16 = 1970;
/// [`DateTime`] (non-inclusive).
const MAX_UNIX_DURATION: Duration = Duration::from_secs(253_402_300_800);

/// Decode 2-digit decimal value
pub(crate) fn decode_decimal(tag: Tag, hi: u8, lo: u8) -> Result<u16> {
if (b'0'..=b'9').contains(&hi) && (b'0'..=b'9').contains(&lo) {
Ok((hi - b'0') as u16 * 10 + (lo - b'0') as u16)
} else {
Err(tag.value_error())
}
}

/// Encode 2-digit decimal value
pub(crate) fn encode_decimal(encoder: &mut Encoder<'_>, tag: Tag, value: u16) -> Result<()> {
let hi_val = value / 10;

if hi_val >= 10 {
return Err(tag.value_error());
}

encoder.byte(hi_val as u8 + b'0')?;
encoder.byte((value % 10) as u8 + b'0')
}

/// Inner date/time type shared by multiple ASN.1 types
/// Date-and-time type shared by multiple ASN.1 types
/// (e.g. `GeneralizedTime`, `UTCTime`).
///
/// Following conventions from RFC 5280, this type is always Z-normalized
/// (i.e. represents a UTC time). However, it isn't named "UTC time" in order
/// to prevent confusion with ASN.1 `UTCTime`.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) struct DateTime {
pub struct DateTime {
/// Full year (e.g. 2000).
///
/// Must be >=1970 to permit positive conversions to Unix time.
Expand All @@ -58,54 +37,54 @@ pub(crate) struct DateTime {
/// Hour (0-23)
hour: u16,

/// Minute (0-59)
minute: u16,
/// Minutes (0-59)
minutes: u16,

/// Second (0-59)
second: u16,
/// Seconds (0-59)
seconds: u16,
}

impl DateTime {
/// Create a new [`DateTime`] from the given UTC time components.
///
/// Note that this does not fully validate the components of the date.
/// To ensure the date is valid, it must be converted to a Unix timestamp
/// by calling [`DateTime::unix_timestamp`].
pub(crate) fn new(
pub fn new(
year: u16,
month: u16,
day: u16,
hour: u16,
minute: u16,
second: u16,
) -> Option<Self> {
) -> Result<Self> {
// Basic validation of the components.
if year >= MIN_YEAR
&& (1..=12).contains(&month)
&& (1..=31).contains(&day)
&& (0..=23).contains(&hour)
&& (0..=59).contains(&minute)
&& (0..=59).contains(&second)
if year < MIN_YEAR
|| !(1..=12).contains(&month)
|| !(1..=31).contains(&day)
|| !(0..=23).contains(&hour)
|| !(0..=59).contains(&minute)
|| !(0..=59).contains(&second)
{
Some(Self {
year,
month,
day,
hour,
minute,
second,
})
} else {
None
return Err(ErrorKind::DateTime.into());
}

let result = Self {
year,
month,
day,
hour,
minutes: minute,
seconds: second,
};

// Validate time maps to a Unix duration
result.unix_duration()?;
Ok(result)
}

/// Compute a [`DateTime`] from the given [`Duration`] since the `UNIX_EPOCH`.
///
/// Returns `None` if the value is outside the supported date range.
pub fn from_unix_duration(unix_duration: Duration) -> Option<Self> {
pub fn from_unix_duration(unix_duration: Duration) -> Result<Self> {
if unix_duration > MAX_UNIX_DURATION {
return None;
return Err(ErrorKind::DateTime.into());
}

let secs_since_epoch = unix_duration.as_secs();
Expand Down Expand Up @@ -179,38 +158,38 @@ impl DateTime {
)
}

/// Get the year
/// Get the year.
pub fn year(&self) -> u16 {
self.year
}

/// Get the month
/// Get the month.
pub fn month(&self) -> u16 {
self.month
}

/// Get the day
/// Get the day.
pub fn day(&self) -> u16 {
self.day
}

/// Get the hour
/// Get the hour.
pub fn hour(&self) -> u16 {
self.hour
}

/// Get the minute
pub fn minute(&self) -> u16 {
self.minute
/// Get the minutes.
pub fn minutes(&self) -> u16 {
self.minutes
}

/// Get the second
pub fn second(&self) -> u16 {
self.second
/// Get the seconds.
pub fn seconds(&self) -> u16 {
self.seconds
}

/// Compute [`Duration`] since `UNIX_EPOCH` from the given calendar date.
pub(crate) fn unix_duration(&self) -> Option<Duration> {
pub fn unix_duration(&self) -> Result<Duration> {
let leap_years = ((self.year - 1) - 1968) / 4 - ((self.year - 1) - 1900) / 100
+ ((self.year - 1) - 1600) / 400;

Expand All @@ -230,11 +209,11 @@ impl DateTime {
10 => (273, 31),
11 => (304, 30),
12 => (334, 31),
_ => return None,
_ => return Err(ErrorKind::DateTime.into()),
};

if self.day > mdays || self.day == 0 {
return None;
return Err(ErrorKind::DateTime.into());
}

ydays += self.day - 1;
Expand All @@ -244,8 +223,8 @@ impl DateTime {
}

let days = (self.year - 1970) as u64 * 365 + leap_years as u64 + ydays as u64;
let time = self.second as u64 + (self.minute as u64 * 60) + (self.hour as u64 * 3600);
Some(Duration::from_secs(time + days * 86400))
let time = self.seconds as u64 + (self.minutes as u64 * 60) + (self.hour as u64 * 3600);
Ok(Duration::from_secs(time + days * 86400))
}

/// Is the year a leap year?
Expand All @@ -254,6 +233,27 @@ impl DateTime {
}
}

/// Decode 2-digit decimal value
pub(crate) fn decode_decimal(tag: Tag, hi: u8, lo: u8) -> Result<u16> {
if (b'0'..=b'9').contains(&hi) && (b'0'..=b'9').contains(&lo) {
Ok((hi - b'0') as u16 * 10 + (lo - b'0') as u16)
} else {
Err(tag.value_error())
}
}

/// Encode 2-digit decimal value
pub(crate) fn encode_decimal(encoder: &mut Encoder<'_>, tag: Tag, value: u16) -> Result<()> {
let hi_val = value / 10;

if hi_val >= 10 {
return Err(tag.value_error());
}

encoder.byte(hi_val as u8 + b'0')?;
encoder.byte((value % 10) as u8 + b'0')
}

#[cfg(test)]
mod tests {
use super::DateTime;
Expand All @@ -262,7 +262,7 @@ mod tests {
fn is_date_valid(year: u16, month: u16, day: u16, hour: u16, minute: u16, second: u16) -> bool {
DateTime::new(year, month, day, hour, minute, second)
.and_then(|dt| dt.unix_duration())
.is_some()
.is_ok()
}

#[test]
Expand Down
4 changes: 4 additions & 0 deletions der/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ impl std::error::Error for ErrorKind {}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum ErrorKind {
/// Date-and-time related errors.
DateTime,

/// Indicates a field which is duplicated when only one is expected.
DuplicateField {
/// Tag of the duplicated field.
Expand Down Expand Up @@ -217,6 +220,7 @@ impl ErrorKind {
impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ErrorKind::DateTime => write!(f, "date/time error"),
ErrorKind::DuplicateField { tag } => write!(f, "duplicate field for {}", tag),
ErrorKind::Failed => write!(f, "operation failed"),
ErrorKind::Length { tag } => write!(f, "incorrect length for {}", tag),
Expand Down
1 change: 1 addition & 0 deletions der/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ mod value;

pub use crate::{
asn1::{Any, Choice},
datetime::DateTime,
decodable::Decodable,
decoder::Decoder,
encodable::Encodable,
Expand Down