use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
pub fn unix_timestamp() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
}
pub fn unix_timestamp_millis() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64
}
pub fn unix_timestamp_nanos() -> u128 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos()
}
pub fn constant_time<T, F: FnOnce() -> T>(min_duration: Duration, operation: F) -> T {
let start = Instant::now();
let result = operation();
let elapsed = start.elapsed();
if elapsed < min_duration {
std::thread::sleep(min_duration - elapsed);
}
result
}
pub fn timed_compare<F: FnOnce() -> bool>(min_duration: Duration, compare: F) -> bool {
constant_time(min_duration, compare)
}
pub fn is_timestamp_valid(timestamp: u64, max_drift: Duration) -> bool {
let now = unix_timestamp();
let drift_secs = max_drift.as_secs();
if timestamp > now + drift_secs {
return false;
}
if timestamp + drift_secs < now {
return false;
}
true
}
#[derive(Debug, Clone, Copy)]
pub struct TimestampRange {
pub not_before: u64,
pub not_after: u64,
}
impl TimestampRange {
pub fn new(not_before: u64, not_after: u64) -> Self {
Self {
not_before,
not_after,
}
}
pub fn from_now(duration: Duration) -> Self {
let now = unix_timestamp();
Self {
not_before: now,
not_after: now + duration.as_secs(),
}
}
pub fn centered_on_now(tolerance: Duration) -> Self {
let now = unix_timestamp();
let tolerance_secs = tolerance.as_secs();
Self {
not_before: now.saturating_sub(tolerance_secs),
not_after: now + tolerance_secs,
}
}
pub fn contains(&self, timestamp: u64) -> bool {
timestamp >= self.not_before && timestamp <= self.not_after
}
pub fn is_expired(&self) -> bool {
unix_timestamp() > self.not_after
}
pub fn is_not_yet_valid(&self) -> bool {
unix_timestamp() < self.not_before
}
pub fn remaining(&self) -> Option<Duration> {
let now = unix_timestamp();
if now > self.not_after {
None
} else {
Some(Duration::from_secs(self.not_after - now))
}
}
}
use std::sync::atomic::{AtomicU64, Ordering};
pub struct MonotonicClock {
last_value: AtomicU64,
}
impl MonotonicClock {
pub const fn new() -> Self {
Self {
last_value: AtomicU64::new(0),
}
}
pub fn next(&self) -> u64 {
loop {
let current = unix_timestamp_millis();
let last = self.last_value.load(Ordering::SeqCst);
let next = if current > last { current } else { last + 1 };
if self
.last_value
.compare_exchange(last, next, Ordering::SeqCst, Ordering::SeqCst)
.is_ok()
{
return next;
}
}
}
pub fn last(&self) -> u64 {
self.last_value.load(Ordering::SeqCst)
}
}
impl Default for MonotonicClock {
fn default() -> Self {
Self::new()
}
}
static GLOBAL_CLOCK: MonotonicClock = MonotonicClock::new();
pub fn monotonic_next() -> u64 {
GLOBAL_CLOCK.next()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_unix_timestamp() {
let ts = unix_timestamp();
assert!(ts > 1700000000); }
#[test]
fn test_constant_time() {
let min_duration = Duration::from_millis(50);
let start = Instant::now();
constant_time(min_duration, || {
let _ = 1 + 1;
});
let elapsed = start.elapsed();
assert!(elapsed >= min_duration);
}
#[test]
fn test_timestamp_validity() {
let now = unix_timestamp();
let drift = Duration::from_secs(60);
assert!(is_timestamp_valid(now, drift));
assert!(is_timestamp_valid(now - 30, drift));
assert!(is_timestamp_valid(now + 30, drift));
assert!(!is_timestamp_valid(now - 120, drift));
assert!(!is_timestamp_valid(now + 120, drift));
}
#[test]
fn test_timestamp_range() {
let range = TimestampRange::centered_on_now(Duration::from_secs(60));
let now = unix_timestamp();
assert!(range.contains(now));
assert!(range.contains(now + 30));
assert!(range.contains(now - 30));
assert!(!range.contains(now + 120));
}
#[test]
fn test_monotonic_clock() {
let clock = MonotonicClock::new();
let v1 = clock.next();
let v2 = clock.next();
let v3 = clock.next();
assert!(v2 > v1);
assert!(v3 > v2);
}
#[test]
fn test_global_monotonic() {
let v1 = monotonic_next();
let v2 = monotonic_next();
assert!(v2 > v1);
}
}