node: Improve `Timestamp` conversion safety
Remove `Timestamp::from(u64)` instance, since not all `u64`s are valid Timestamps, and add `try_from` instead.
This commit is contained in:
parent
cd2884a609
commit
70d2e1a0db
|
|
@ -71,7 +71,7 @@ impl Store for Database {
|
||||||
|
|
||||||
if let Some(Ok(row)) = stmt.into_iter().next() {
|
if let Some(Ok(row)) = stmt.into_iter().next() {
|
||||||
return match row.try_read::<Option<i64>, _>(0)? {
|
return match row.try_read::<Option<i64>, _>(0)? {
|
||||||
Some(i) => Ok(Some(Timestamp::from(u64::try_from(i)?))),
|
Some(i) => Ok(Some(Timestamp::try_from(i)?)),
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -669,7 +669,7 @@ mod tests {
|
||||||
fn test_node_announcement_validate() {
|
fn test_node_announcement_validate() {
|
||||||
let ann = NodeAnnouncement {
|
let ann = NodeAnnouncement {
|
||||||
features: node::Features::SEED,
|
features: node::Features::SEED,
|
||||||
timestamp: Timestamp::from(42491841),
|
timestamp: Timestamp::try_from(42491841u64).unwrap(),
|
||||||
alias: Alias::new("alice"),
|
alias: Alias::new("alice"),
|
||||||
addresses: BoundedVec::new(),
|
addresses: BoundedVec::new(),
|
||||||
nonce: 0,
|
nonce: 0,
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,8 @@ pub enum Error {
|
||||||
InvalidProtocolVersion([u8; 4]),
|
InvalidProtocolVersion([u8; 4]),
|
||||||
#[error("invalid onion address: {0}")]
|
#[error("invalid onion address: {0}")]
|
||||||
InvalidOnionAddr(#[from] tor::OnionAddrDecodeError),
|
InvalidOnionAddr(#[from] tor::OnionAddrDecodeError),
|
||||||
|
#[error("invalid timestamp: {0}")]
|
||||||
|
InvalidTimestamp(u64),
|
||||||
#[error("unknown address type `{0}`")]
|
#[error("unknown address type `{0}`")]
|
||||||
UnknownAddressType(u8),
|
UnknownAddressType(u8),
|
||||||
#[error("unknown message type `{0}`")]
|
#[error("unknown message type `{0}`")]
|
||||||
|
|
@ -537,8 +539,9 @@ impl Encode for Timestamp {
|
||||||
impl Decode for Timestamp {
|
impl Decode for Timestamp {
|
||||||
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
||||||
let millis = u64::decode(reader)?;
|
let millis = u64::decode(reader)?;
|
||||||
|
let ts = Timestamp::try_from(millis).map_err(Error::InvalidTimestamp)?;
|
||||||
|
|
||||||
Ok(Timestamp::from(millis))
|
Ok(ts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -347,10 +347,14 @@ mod test {
|
||||||
vec![(id, InsertResult::SeedAdded)]
|
vec![(id, InsertResult::SeedAdded)]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
db.insert([&id], node, Timestamp::from(1)).unwrap(),
|
db.insert([&id], node, Timestamp::try_from(1u64).unwrap())
|
||||||
|
.unwrap(),
|
||||||
vec![(id, InsertResult::TimeUpdated)]
|
vec![(id, InsertResult::TimeUpdated)]
|
||||||
);
|
);
|
||||||
assert_eq!(db.entry(&id, &node).unwrap(), Some(Timestamp::from(1)));
|
assert_eq!(
|
||||||
|
db.entry(&id, &node).unwrap(),
|
||||||
|
Some(Timestamp::try_from(1u64).unwrap())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -372,7 +376,8 @@ mod test {
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
db.insert([&id1, &id2], node, Timestamp::from(1)).unwrap(),
|
db.insert([&id1, &id2], node, Timestamp::try_from(1u64).unwrap())
|
||||||
|
.unwrap(),
|
||||||
vec![
|
vec![
|
||||||
(id1, InsertResult::TimeUpdated),
|
(id1, InsertResult::TimeUpdated),
|
||||||
(id2, InsertResult::TimeUpdated)
|
(id2, InsertResult::TimeUpdated)
|
||||||
|
|
@ -415,7 +420,8 @@ mod test {
|
||||||
|
|
||||||
for node in &nodes {
|
for node in &nodes {
|
||||||
let time = rng.u64(..now.as_millis());
|
let time = rng.u64(..now.as_millis());
|
||||||
db.insert(&ids, *node, Timestamp::from(time)).unwrap();
|
db.insert(&ids, *node, Timestamp::try_from(time).unwrap())
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let ids = arbitrary::vec::<RepoId>(10);
|
let ids = arbitrary::vec::<RepoId>(10);
|
||||||
|
|
@ -423,7 +429,8 @@ mod test {
|
||||||
|
|
||||||
for node in &nodes {
|
for node in &nodes {
|
||||||
let time = rng.u64(now.as_millis()..i64::MAX as u64);
|
let time = rng.u64(now.as_millis()..i64::MAX as u64);
|
||||||
db.insert(&ids, *node, Timestamp::from(time)).unwrap();
|
db.insert(&ids, *node, Timestamp::try_from(time).unwrap())
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let pruned = db.prune(now.into(), None).unwrap();
|
let pruned = db.prune(now.into(), None).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use std::{
|
use std::{
|
||||||
fmt,
|
fmt,
|
||||||
|
num::TryFromIntError,
|
||||||
ops::{Add, Deref, Sub},
|
ops::{Add, Deref, Sub},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -15,7 +16,7 @@ impl Add<u64> for Timestamp {
|
||||||
type Output = Timestamp;
|
type Output = Timestamp;
|
||||||
|
|
||||||
fn add(self, millis: u64) -> Self::Output {
|
fn add(self, millis: u64) -> Self::Output {
|
||||||
Self(self.0 + millis)
|
Self(self.0.saturating_add(millis))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -23,7 +24,7 @@ impl Sub<u64> for Timestamp {
|
||||||
type Output = Timestamp;
|
type Output = Timestamp;
|
||||||
|
|
||||||
fn sub(self, millis: u64) -> Self::Output {
|
fn sub(self, millis: u64) -> Self::Output {
|
||||||
Self(self.0 - millis)
|
Self(self.0.saturating_sub(millis))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,9 +70,23 @@ impl From<Timestamp> for LocalTime {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<u64> for Timestamp {
|
impl TryFrom<u64> for Timestamp {
|
||||||
fn from(u: u64) -> Self {
|
type Error = u64;
|
||||||
Self(u)
|
|
||||||
|
fn try_from(u: u64) -> Result<Self, u64> {
|
||||||
|
if u <= *Self::MAX {
|
||||||
|
Ok(Self(u))
|
||||||
|
} else {
|
||||||
|
Err(u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<i64> for Timestamp {
|
||||||
|
type Error = TryFromIntError;
|
||||||
|
|
||||||
|
fn try_from(i: i64) -> Result<Self, Self::Error> {
|
||||||
|
i.try_into().map(Self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,7 +99,7 @@ impl TryFrom<&sql::Value> for Timestamp {
|
||||||
Ok(u) => Ok(Timestamp(u)),
|
Ok(u) => Ok(Timestamp(u)),
|
||||||
Err(e) => Err(sql::Error {
|
Err(e) => Err(sql::Error {
|
||||||
code: None,
|
code: None,
|
||||||
message: Some(format!("sql: invalid integer for timestamp: {e}")),
|
message: Some(format!("sql: invalid integer `{i}` for timestamp: {e}")),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
_ => Err(sql::Error {
|
_ => Err(sql::Error {
|
||||||
|
|
@ -101,8 +116,18 @@ impl sql::BindableWithIndex for &Timestamp {
|
||||||
Ok(integer) => integer.bind(stmt, i),
|
Ok(integer) => integer.bind(stmt, i),
|
||||||
Err(e) => Err(sql::Error {
|
Err(e) => Err(sql::Error {
|
||||||
code: None,
|
code: None,
|
||||||
message: Some(format!("sql: invalid timestamp: {e}")),
|
message: Some(format!("sql: invalid timestamp `{self}`: {e}")),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_timestamp_max() {
|
||||||
|
assert_eq!(i64::try_from(*Timestamp::MAX), Ok(i64::MAX));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -315,6 +315,6 @@ impl Arbitrary for Alias {
|
||||||
|
|
||||||
impl Arbitrary for Timestamp {
|
impl Arbitrary for Timestamp {
|
||||||
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
||||||
Self::from(u64::arbitrary(g))
|
Self::try_from(u64::arbitrary(g).min(*Self::MAX)).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue