diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2019-11-29 14:31:17 +0100 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2019-12-03 10:47:55 +0100 |
commit | 50cf12efa6980e460fa82b3c415931c6db740213 (patch) | |
tree | af333cf7915d7581a09f5fc52eb9c570556a35c3 /openpgp/src | |
parent | 9e6a800443a10dadafef6d17482b8c7d3b43c183 (diff) |
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.
Diffstat (limited to 'openpgp/src')
-rw-r--r-- | openpgp/src/types/mod.rs | 2 | ||||
-rw-r--r-- | openpgp/src/types/timestamp.rs | 168 |
2 files changed, 170 insertions, 0 deletions
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<Timestamp> for u32 { + fn from(t: Timestamp) -> Self { + t.0 + } +} + +impl From<u32> for Timestamp { + fn from(t: u32) -> Self { + Timestamp(t) + } +} + +impl TryFrom<SystemTime> for Timestamp { + type Error = failure::Error; + + fn try_from(t: SystemTime) -> Result<Self> { + 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<Timestamp> 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: Gen>(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<Duration> for u32 { + fn from(d: Duration) -> Self { + d.0 + } +} + +impl From<u32> for Duration { + fn from(d: u32) -> Self { + Duration(d) + } +} + +impl TryFrom<SystemDuration> for Duration { + type Error = failure::Error; + + fn try_from(d: SystemDuration) -> Result<Self> { + 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<Duration> 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<Duration> { + 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<Duration> { + 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<Duration> { + 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<Duration> { + 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: Gen>(g: &mut G) -> Self { + Duration(u32::arbitrary(g)) + } +} |