Skip to content

Commit

Permalink
Merge pull request #811 from uuid-rs/fix/v7-overflow
Browse files Browse the repository at this point in the history
Guarantee v7 timestamp will never overflow
  • Loading branch information
KodrAus authored Feb 26, 2025
2 parents 56ba68f + c2d313f commit f05b6df
Showing 1 changed file with 37 additions and 20 deletions.
57 changes: 37 additions & 20 deletions src/timestamp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,17 @@ pub mod context {
let ts = Timestamp::from_unix(&context, seconds, subsec_nanos);
assert_eq!(1, ts.counter);
}

#[test]
fn context_overflow() {
let seconds = u64::MAX;
let subsec_nanos = u32::MAX;

let context = Context::new(u16::MAX);

// Ensure we don't panic
Timestamp::from_unix(&context, seconds, subsec_nanos);
}
}
}

Expand Down Expand Up @@ -812,14 +823,14 @@ pub mod context {

#[derive(Debug)]
struct Adjust {
by_ns: u32,
by_ns: u128,
}

impl Adjust {
#[inline]
fn by_millis(millis: u32) -> Self {
Adjust {
by_ns: millis.saturating_mul(1_000_000),
by_ns: (millis as u128).saturating_mul(1_000_000),
}
}

Expand All @@ -830,23 +841,12 @@ pub mod context {
return (seconds, subsec_nanos);
}

let mut shifted_subsec_nanos =
subsec_nanos.checked_add(self.by_ns).unwrap_or(subsec_nanos);
let ts = (seconds as u128)
.saturating_mul(1_000_000_000)
.saturating_add(subsec_nanos as u128)
.saturating_add(self.by_ns as u128);

if shifted_subsec_nanos < 1_000_000_000 {
// The shift hasn't overflowed into the next second
(seconds, shifted_subsec_nanos)
} else {
// The shift has overflowed into the next second
shifted_subsec_nanos -= 1_000_000_000;

if seconds < u64::MAX {
(seconds + 1, shifted_subsec_nanos)
} else {
// The next second would overflow a `u64`
(seconds, subsec_nanos)
}
}
((ts / 1_000_000_000) as u64, (ts % 1_000_000_000) as u32)
}
}

Expand Down Expand Up @@ -878,7 +878,9 @@ pub mod context {
#[inline]
fn from_ts(seconds: u64, subsec_nanos: u32) -> Self {
// Reseed when the millisecond advances
let last_seed = (seconds * 1_000) + (subsec_nanos as u64 / 1_000_000);
let last_seed = seconds
.saturating_mul(1_000)
.saturating_add((subsec_nanos / 1_000_000) as u64);

ReseedingTimestamp {
last_seed,
Expand Down Expand Up @@ -918,7 +920,7 @@ pub mod context {

// The factor reduces the size of the sub-millisecond precision to
// fit into the specified number of bits
let factor = (999_999u64 / 2u64.pow(bits as u32)) + 1;
let factor = (999_999 / u64::pow(2, bits as u32)) + 1;

Precision {
bits,
Expand Down Expand Up @@ -1113,6 +1115,21 @@ pub mod context {

assert!(Uuid::new_v7(ts3) > Uuid::new_v7(ts2));
}

#[test]
fn context_overflow() {
let seconds = u64::MAX;
let subsec_nanos = u32::MAX;

// Ensure we don't panic
for context in [
ContextV7::new(),
ContextV7::new().with_additional_precision(),
ContextV7::new().with_adjust_by_millis(u32::MAX),
] {
Timestamp::from_unix(&context, seconds, subsec_nanos);
}
}
}
}

Expand Down

0 comments on commit f05b6df

Please sign in to comment.