From 50cf12efa6980e460fa82b3c415931c6db740213 Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Fri, 29 Nov 2019 14:31:17 +0100 Subject: openpgp: Add two new types, Timestamp and Duration. - Timestamp and Duration represent dates and durations with the range and resolution that OpenPGP can represent. By using these types to store dates and durations, we enforce correct canonicalization. --- openpgp/src/types/mod.rs | 2 + openpgp/src/types/timestamp.rs | 168 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 openpgp/src/types/timestamp.rs (limited to 'openpgp/src/types') diff --git a/openpgp/src/types/mod.rs b/openpgp/src/types/mod.rs index 8dfffc17..27d142b9 100644 --- a/openpgp/src/types/mod.rs +++ b/openpgp/src/types/mod.rs @@ -18,6 +18,8 @@ mod key_flags; pub use self::key_flags::KeyFlags; mod server_preferences; pub use self::server_preferences::KeyServerPreferences; +mod timestamp; +pub use timestamp::{Timestamp, Duration}; /// The OpenPGP public key algorithms as defined in [Section 9.1 of /// RFC 4880], and [Section 5 of RFC 6637]. diff --git a/openpgp/src/types/timestamp.rs b/openpgp/src/types/timestamp.rs new file mode 100644 index 00000000..318a6ba9 --- /dev/null +++ b/openpgp/src/types/timestamp.rs @@ -0,0 +1,168 @@ +use std::convert::{TryFrom, TryInto}; +use std::fmt; +use std::time::{SystemTime, Duration as SystemDuration, UNIX_EPOCH}; +use quickcheck::{Arbitrary, Gen}; + +use crate::{ + Error, + Result, +}; + +/// A timestamp representable by OpenPGP. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Timestamp(u32); + +impl From for u32 { + fn from(t: Timestamp) -> Self { + t.0 + } +} + +impl From for Timestamp { + fn from(t: u32) -> Self { + Timestamp(t) + } +} + +impl TryFrom for Timestamp { + type Error = failure::Error; + + fn try_from(t: SystemTime) -> Result { + match t.duration_since(std::time::UNIX_EPOCH) { + Ok(d) if d.as_secs() <= std::u32::MAX as u64 => + Ok(Timestamp(d.as_secs() as u32)), + _ => Err(Error::InvalidArgument( + format!("Time exceeds u32 epoch: {:?}", t)) + .into()), + } + } +} + +impl From for SystemTime { + fn from(t: Timestamp) -> Self { + UNIX_EPOCH + SystemDuration::new(t.0 as u64, 0) + } +} + +impl fmt::Debug for Timestamp { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", SystemTime::from(*self)) + } +} + +impl Timestamp { + /// Returns the current time. + pub fn now() -> Timestamp { + SystemTime::now().try_into() + .expect("representable for the next hundred years") + } +} + +impl Arbitrary for Timestamp { + fn arbitrary(g: &mut G) -> Self { + Timestamp(u32::arbitrary(g)) + } +} + +/// A duration representable by OpenPGP. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Duration(u32); + +impl From for u32 { + fn from(d: Duration) -> Self { + d.0 + } +} + +impl From for Duration { + fn from(d: u32) -> Self { + Duration(d) + } +} + +impl TryFrom for Duration { + type Error = failure::Error; + + fn try_from(d: SystemDuration) -> Result { + if d.as_secs() <= std::u32::MAX as u64 { + Ok(Duration(d.as_secs() as u32)) + } else { + Err(Error::InvalidArgument( + format!("Duration exceeds u32: {:?}", d)) + .into()) + } + } +} + +impl From for SystemDuration { + fn from(d: Duration) -> Self { + SystemDuration::new(d.0 as u64, 0) + } +} + +impl fmt::Debug for Duration { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", SystemDuration::from(*self)) + } +} + +impl Duration { + /// Returns a `Duration` with the given number of seconds. + pub fn seconds(n: u32) -> Duration { + n.into() + } + + /// Returns a `Duration` with the given number of minutes, if + /// representable. + pub fn minutes(n: u32) -> Result { + 60u32.checked_mul(n).ok_or(()) + .map(Self::seconds) + .map_err(|_| Error::InvalidArgument( + format!("Not representable: {} minutes in seconds exceeds u32", + n)).into()) + } + + /// Returns a `Duration` with the given number of hours, if + /// representable. + pub fn hours(n: u32) -> Result { + 60u32.checked_mul(n) + .ok_or(Error::InvalidArgument("".into()).into()) + .and_then(Self::minutes) + .map_err(|_| Error::InvalidArgument( + format!("Not representable: {} hours in seconds exceeds u32", + n)).into()) + } + + /// Returns a `Duration` with the given number of days, if + /// representable. + pub fn days(n: u32) -> Result { + 24u32.checked_mul(n) + .ok_or(Error::InvalidArgument("".into()).into()) + .and_then(Self::hours) + .map_err(|_| Error::InvalidArgument( + format!("Not representable: {} days in seconds exceeds u32", + n)).into()) + } + + /// Returns a `Duration` with the given number of weeks, if + /// representable. + pub fn weeks(n: u32) -> Result { + 7u32.checked_mul(n) + .ok_or(Error::InvalidArgument("".into()).into()) + .and_then(Self::days) + .map_err(|_| Error::InvalidArgument( + format!("Not representable: {} weeks in seconds exceeds u32", + n)).into()) + } + + /// Returns the duration as seconds. + pub fn as_secs(self) -> u64 { + self.0 as u64 + } +} + +impl Arbitrary for Duration { + fn arbitrary(g: &mut G) -> Self { + Duration(u32::arbitrary(g)) + } +} -- cgit v1.2.3