Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
55 changes: 26 additions & 29 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,7 @@
#[cfg(test)]
mod tests;

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

use anyhow::Context;
use chrono::{Duration, TimeZone, Utc};

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

Expand All @@ -16,11 +14,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,46 +43,46 @@ 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))
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.",
);
return Ok(false);
};
let id_time = Utc
.timestamp_opt(i64::try_from(id_time_secs).unwrap_or(0), id_time_nanos)
.single()
.ok_or_else(|| anyhow::anyhow!("Invalid timestamp in document `id` field"))?;

let now = Utc::now();

let now = SystemTime::now();
let diff = id_time.signed_duration_since(now);

if let Ok(id_age) = id_time.duration_since(now) {
// `now` is earlier than `id_time`
if diff.num_nanoseconds().unwrap_or(0) > 0 {
// id_time is in the future
if let Some(future_threshold) = provider.future_threshold() {
if id_age > future_threshold {
let threshold = Duration::from_std(future_threshold)?;
if diff > 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 Version timestamp {id} cannot be too far in future (threshold: {threshold:?}) from now: {now:?}"
),
);
is_valid = false;
}
}
} else {
// `id_time` is earlier than `now`
let id_age = now
.duration_since(id_time)
.context("BUG! `id_time` must be earlier than `now` at this place")?;
// id_time is in the past
// make positive duration
let id_age = diff.abs();

if let Some(past_threshold) = provider.past_threshold() {
if id_age > past_threshold {
let threshold = Duration::from_std(past_threshold)?;
if id_age > threshold {
doc.report().invalid_value(
"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 Version timestamp {id} cannot be too far behind (threshold: {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