Skip to content
This repository was archived by the owner on Apr 13, 2021. It is now read-only.

Commit ad4cf59

Browse files
committed
Use std::time::Duration instead of u64 for expiry time
1 parent 06bfd37 commit ad4cf59

File tree

3 files changed

+54
-19
lines changed

3 files changed

+54
-19
lines changed

src/de.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -485,11 +485,11 @@ impl FromBase32 for ExpiryTime {
485485
type Err = ParseError;
486486

487487
fn from_base32(field_data: &[u5]) -> Result<ExpiryTime, ParseError> {
488-
let expiry = parse_int_be::<u64, u5>(field_data, 32);
489-
if let Some(expiry) = expiry {
490-
Ok(ExpiryTime{seconds: expiry})
491-
} else {
492-
Err(ParseError::IntegerOverflowError)
488+
match parse_int_be::<u64, u5>(field_data, 32)
489+
.and_then(|t| ExpiryTime::from_seconds(t).ok()) // ok, since the only error is out of bounds
490+
{
491+
Some(t) => Ok(t),
492+
None => Err(ParseError::IntegerOverflowError),
493493
}
494494
}
495495
}
@@ -814,7 +814,7 @@ mod test {
814814
use bech32::FromBase32;
815815

816816
let input = from_bech32("pu".as_bytes());
817-
let expected = Ok(ExpiryTime{seconds: 60});
817+
let expected = Ok(ExpiryTime::from_seconds(60).unwrap());
818818
assert_eq!(ExpiryTime::from_base32(&input), expected);
819819

820820
let input_too_large = from_bech32("sqqqqqqqqqqqq".as_bytes());

src/lib.rs

+47-12
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,15 @@ mod tb;
2222
pub use de::{ParseError, ParseOrSemanticError};
2323

2424

25-
// TODO: fix before 2038 (see rust PR #55527)
25+
// TODO: fix before 2037 (see rust PR #55527)
2626
/// Defines the maximum UNIX timestamp that can be represented as `SystemTime`. This is checked by
2727
/// one of the unit tests, please run them.
2828
const SYSTEM_TIME_MAX_UNIX_TIMESTAMP: u64 = std::i32::MAX as u64;
2929

30+
/// Allow the expiry time to be up to one year. Since this reduces the range of possible timestamps
31+
/// it should be rather low as long as we still have to support 32bit time representations
32+
const MAX_EXPIRY_TIME: u64 = 60 * 60 * 24 * 356;
33+
3034
/// This function is used as a static assert for the size of `SystemTime`. If the crate fails to
3135
/// compile due to it this indicates that your system uses unexpected bounds for `SystemTime`. You
3236
/// can remove this functions and run the test `test_system_time_bounds_assumptions`. In any case,
@@ -262,10 +266,12 @@ pub struct Description(String);
262266
pub struct PayeePubKey(pub PublicKey);
263267

264268
/// Positive duration that defines when (relatively to the timestamp) in the future the invoice expires
269+
///
270+
/// # Invariants
271+
/// The number of seconds this expiry time represents has to be in the range
272+
/// `0...SYSTEM_TIME_MAX_UNIX_TIMESTAMP / 2` to avoid overflows when adding it to a timestamp
265273
#[derive(Eq, PartialEq, Debug, Clone)]
266-
pub struct ExpiryTime {
267-
pub seconds: u64
268-
}
274+
pub struct ExpiryTime(Duration);
269275

270276
/// `min_final_cltv_expiry` to use for the last HTLC in the route
271277
#[derive(Eq, PartialEq, Debug, Clone)]
@@ -382,8 +388,8 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool> InvoiceBuilder<D, H, T> {
382388
}
383389

384390
/// Sets the expiry time in seconds.
385-
pub fn expiry_time_seconds(mut self, expiry_seconds: u64) -> Self {
386-
self.tagged_fields.push(TaggedField::ExpiryTime(ExpiryTime {seconds: expiry_seconds}));
391+
pub fn expiry_time_seconds(mut self, expiry_time: Duration) -> Self {
392+
self.tagged_fields.push(TaggedField::ExpiryTime(ExpiryTime::from_duration(expiry_time).unwrap()));
387393
self
388394
}
389395

@@ -490,7 +496,7 @@ impl<D: tb::Bool, H: tb::Bool> InvoiceBuilder<D, H, tb::False> {
490496

491497
/// Sets the timestamp to the current UNIX timestamp.
492498
pub fn current_timestamp(mut self) -> InvoiceBuilder<D, H, tb::True> {
493-
use std::time::{SystemTime, UNIX_EPOCH};
499+
use std::time::SystemTime;
494500
let now = PositiveTimestamp::from_system_time(SystemTime::now());
495501
self.timestamp = Some(now.expect("for the foreseeable future this shouldn't happen"));
496502
self.set_flags()
@@ -760,23 +766,23 @@ impl RawInvoice {
760766
impl PositiveTimestamp {
761767

762768
/// Create a new `PositiveTimestamp` from a unix timestamp in the Range
763-
/// `0...SYSTEM_TIME_MAX_UNIX_TIMESTAMP / 2`, otherwise return a
769+
/// `0...SYSTEM_TIME_MAX_UNIX_TIMESTAMP - MAX_EXPIRY_TIME`, otherwise return a
764770
/// `CreationError::TimestampOutOfBounds`.
765771
pub fn from_unix_timestamp(unix_seconds: u64) -> Result<Self, CreationError> {
766-
if unix_seconds > SYSTEM_TIME_MAX_UNIX_TIMESTAMP / 2 {
772+
if unix_seconds > SYSTEM_TIME_MAX_UNIX_TIMESTAMP - MAX_EXPIRY_TIME {
767773
Err(CreationError::TimestampOutOfBounds)
768774
} else {
769775
Ok(PositiveTimestamp(UNIX_EPOCH + Duration::from_secs(unix_seconds)))
770776
}
771777
}
772778

773779
/// Create a new `PositiveTimestamp` from a `SystemTime` with a corresponding unix timestamp in
774-
/// the Range `0...SYSTEM_TIME_MAX_UNIX_TIMESTAMP / 2`, otherwise return a
780+
/// the Range `0...SYSTEM_TIME_MAX_UNIX_TIMESTAMP - MAX_EXPIRY_TIME`, otherwise return a
775781
/// `CreationError::TimestampOutOfBounds`.
776782
pub fn from_system_time(time: SystemTime) -> Result<Self, CreationError> {
777783
if time
778784
.duration_since(UNIX_EPOCH)
779-
.map(|t| t.as_secs() <= SYSTEM_TIME_MAX_UNIX_TIMESTAMP / 2) // check for consistency reasons
785+
.map(|t| t.as_secs() <= SYSTEM_TIME_MAX_UNIX_TIMESTAMP - MAX_EXPIRY_TIME)
780786
.unwrap_or(true)
781787
{
782788
Ok(PositiveTimestamp(time))
@@ -1010,6 +1016,32 @@ impl Deref for PayeePubKey {
10101016
}
10111017
}
10121018

1019+
impl ExpiryTime {
1020+
pub fn from_seconds(seconds: u64) -> Result<ExpiryTime, CreationError> {
1021+
if seconds <= SYSTEM_TIME_MAX_UNIX_TIMESTAMP - MAX_EXPIRY_TIME {
1022+
Ok(ExpiryTime(Duration::from_secs(seconds)))
1023+
} else {
1024+
Err(CreationError::ExpiryTimeOutOfBounds)
1025+
}
1026+
}
1027+
1028+
pub fn from_duration(duration: Duration) -> Result<ExpiryTime, CreationError> {
1029+
if duration.as_secs() <= SYSTEM_TIME_MAX_UNIX_TIMESTAMP - MAX_EXPIRY_TIME {
1030+
Ok(ExpiryTime(duration))
1031+
} else {
1032+
Err(CreationError::ExpiryTimeOutOfBounds)
1033+
}
1034+
}
1035+
1036+
pub fn as_seconds(&self) -> u64 {
1037+
self.0.as_secs()
1038+
}
1039+
1040+
pub fn as_duration(&self) -> &Duration {
1041+
&self.0
1042+
}
1043+
}
1044+
10131045
impl Route {
10141046
pub fn new(hops: Vec<RouteHop>) -> Result<Route, CreationError> {
10151047
if hops.len() <= 12 {
@@ -1065,6 +1097,9 @@ pub enum CreationError {
10651097

10661098
/// The unix timestamp of the supplied date is <0 or can't be represented as `SystemTime`
10671099
TimestampOutOfBounds,
1100+
1101+
/// The supplied expiry time could cause an overflow if added to a `PositiveTimestamp`
1102+
ExpiryTimeOutOfBounds,
10681103
}
10691104

10701105
/// Errors that may occur when converting a `RawInvoice` to an `Invoice`. They relate to the
@@ -1369,7 +1404,7 @@ mod test {
13691404
1234567
13701405
);
13711406
assert_eq!(invoice.payee_pub_key(), Some(&PayeePubKey(public_key)));
1372-
assert_eq!(invoice.expiry_time(), Some(&ExpiryTime{seconds: 54321}));
1407+
assert_eq!(invoice.expiry_time(), Some(&ExpiryTime::from_seconds(54321).unwrap()));
13731408
assert_eq!(invoice.min_final_cltv_expiry(), Some(&MinFinalCltvExpiry(144)));
13741409
assert_eq!(invoice.fallbacks(), vec![&Fallback::PubKeyHash([0;20])]);
13751410
assert_eq!(invoice.routes(), vec![&Route(route_1), &Route(route_2)]);

src/ser.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ impl ToBase32<Vec<u5>> for PayeePubKey {
169169

170170
impl ToBase32<Vec<u5>> for ExpiryTime {
171171
fn to_base32(&self) -> Vec<u5> {
172-
encode_int_be_base32(self.seconds)
172+
encode_int_be_base32(self.as_seconds())
173173
}
174174
}
175175

0 commit comments

Comments
 (0)