Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions rust/signed_doc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ futures = "0.3.31"
ed25519-bip32 = "0.4.1" # used by the `mk_signed_doc` cli tool
tracing = "0.1.40"
thiserror = "2.0.11"
chrono = "0.4.42"

[dev-dependencies]
base64-url = "3.0.0"
Expand Down
16 changes: 7 additions & 9 deletions rust/signed_doc/src/validator/rules/chain/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,17 @@ use crate::{
};

mod helper {
use std::time::{Duration, SystemTime, UNIX_EPOCH};

use catalyst_types::uuid::UuidV7;
use chrono::{Duration, Utc};
use uuid::{Timestamp, Uuid};

pub(super) fn get_now_plus_uuidv7(secs: u64) -> UuidV7 {
let future_time = SystemTime::now()
.checked_add(Duration::from_secs(secs))
.unwrap();
let duration_since_epoch = future_time.duration_since(UNIX_EPOCH).unwrap();
pub(super) fn get_now_plus_uuidv7(secs: i64) -> UuidV7 {
let future_time = Utc::now()
.checked_add_signed(Duration::seconds(secs))
.expect("time overflow in future_time calculation");

let unix_secs = duration_since_epoch.as_secs();
let nanos = duration_since_epoch.subsec_nanos();
let unix_secs = u64::try_from(future_time.timestamp()).unwrap_or(0);
let nanos = future_time.timestamp_subsec_nanos();

let ts = Timestamp::from_unix(uuid::NoContext, unix_secs, nanos);
let uuid = Uuid::new_v7(ts);
Expand Down
33 changes: 17 additions & 16 deletions rust/signed_doc/src/validator/rules/id/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
#[cfg(test)]
mod tests;

use std::time::{Duration, SystemTime};

use anyhow::Context;
use chrono::{DateTime, Utc};

use crate::{providers::CatalystSignedDocumentProvider, CatalystSignedDocument};

Expand All @@ -16,11 +15,10 @@ pub(crate) struct IdRule;
impl IdRule {
/// Validates document `id` field on the timestamps:
/// 1. If `provider.future_threshold()` not `None`, document `id` cannot be too far in
/// the future (`future_threshold` arg) from `SystemTime::now()` based on the
/// provide threshold
/// 2. If `provider.future_threshold()` not `None`, document `id` cannot be too far
/// behind (`past_threshold` arg) from `SystemTime::now()` based on the provide
/// the future (`future_threshold` arg) from `Utc::now()` based on the provided
/// threshold
/// 2. If `provider.past_threshold()` not `None`, document `id` cannot be too far
/// behind (`past_threshold` arg) from `Utc::now()` based on the provided threshold
#[allow(clippy::unused_async)]
pub(crate) async fn check<Provider>(
&self,
Expand All @@ -46,37 +44,40 @@ impl IdRule {
.ok_or(anyhow::anyhow!("Document `id` field must be a UUIDv7"))?
.to_unix();

let Some(id_time) =
SystemTime::UNIX_EPOCH.checked_add(Duration::new(id_time_secs, id_time_nanos))
let Some(id_time) = i64::try_from(id_time_secs)
.ok()
.and_then(|id_time_secs| DateTime::from_timestamp(id_time_secs, id_time_nanos))
else {
doc.report().invalid_value(
"id",
&id.to_string(),
"Must a valid duration since `UNIX_EPOCH`",
"Cannot instantiate a valid `SystemTime` value from the provided `id` field timestamp.",
"Must a valid UTC date time since `UNIX_EPOCH`",
"Cannot instantiate a valid `DateTime<Utc>` value from the provided `id` field timestamp.",
);
return Ok(false);
};

let now = SystemTime::now();
let now = Utc::now();
let time_delta = id_time.signed_duration_since(now);

if let Ok(id_age) = id_time.duration_since(now) {
if let Ok(id_age) = time_delta.to_std() {
// `now` is earlier than `id_time`
if let Some(future_threshold) = provider.future_threshold() {
if id_age > future_threshold {
doc.report().invalid_value(
"id",
&id.to_string(),
"id < now + future_threshold",
&format!("Document Version timestamp {id} cannot be too far in future (threshold: {future_threshold:?}) from now: {now:?}"),
&format!("Document ID timestamp {id} cannot be too far in future (threshold: {future_threshold:?}) from now: {now}"),
);
is_valid = false;
}
}
} else {
// `id_time` is earlier than `now`
let id_age = now
.duration_since(id_time)
let id_age = time_delta
.abs()
.to_std()
.context("BUG! `id_time` must be earlier than `now` at this place")?;

if let Some(past_threshold) = provider.past_threshold() {
Expand All @@ -85,7 +86,7 @@ impl IdRule {
"id",
&id.to_string(),
"id > now - past_threshold",
&format!("Document Version timestamp {id} cannot be too far behind (threshold: {past_threshold:?}) from now: {now:?}",),
&format!("Document ID timestamp {id} cannot be too far behind (threshold: {past_threshold:?}) from now: {now:?}",),
);
is_valid = false;
}
Expand Down
59 changes: 29 additions & 30 deletions rust/signed_doc/src/validator/rules/id/tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use std::time::SystemTime;

use chrono::Utc;
use test_case::test_case;
use uuid::{Timestamp, Uuid};

Expand All @@ -22,46 +21,46 @@ use crate::{
#[test_case(
#[allow(clippy::arithmetic_side_effects)]
|provider| {
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
let to_far_in_past = Uuid::new_v7(Timestamp::from_unix_time(
now - provider.past_threshold().unwrap().as_secs() - 1,
0,
0,
0,
))
.try_into()
.unwrap();
let now = Utc::now().timestamp();
let past_threshold_secs = i64::try_from(provider.past_threshold().unwrap().as_secs()).unwrap_or(0);

let too_far_in_past = Uuid::new_v7(Timestamp::from_unix_time(
u64::try_from(now - past_threshold_secs - 1).unwrap_or(0),
0,
0,
0,
))
.try_into()
.unwrap();

Builder::new()
.with_metadata_field(SupportedField::Id(to_far_in_past))
.with_metadata_field(SupportedField::Id(too_far_in_past))
.build()
}
=> false;
"`id` to far in past"
"`id` too far in past"
)]
#[test_case(
#[allow(clippy::arithmetic_side_effects)]
|provider| {
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
let to_far_in_future = Uuid::new_v7(Timestamp::from_unix_time(
now + provider.future_threshold().unwrap().as_secs() + 1,
0,
0,
0,
))
.try_into()
.unwrap();
let now = Utc::now().timestamp();
let future_threshold_secs = i64::try_from(provider.future_threshold().unwrap().as_secs()).unwrap_or(0);

let too_far_in_future = Uuid::new_v7(Timestamp::from_unix_time(
u64::try_from(now + future_threshold_secs + 1).unwrap_or(0),
0,
0,
0,
))
.try_into()
.unwrap();

Builder::new()
.with_metadata_field(SupportedField::Id(to_far_in_future))
.with_metadata_field(SupportedField::Id(too_far_in_future))
.build()
}
=> false;
"`id` to far in future"
"`id` too far in future"
)]
#[test_case(
|_| {
Expand Down
Loading
Loading