use serde::{Deserialize, Deserializer, Serializer};
use std::sync::atomic::{AtomicU64, Ordering};
#[cfg(not(target_arch = "wasm32"))]
use std::time::{SystemTime, UNIX_EPOCH};
use crate::{Error, Result};
static LAST_TIMESTAMP_NANOS: AtomicU64 = AtomicU64::new(0);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NonceStrategy {
Timestamp,
Counter,
TimestampWithCounter,
}
pub struct NonceManager {
strategy: NonceStrategy,
counter: AtomicU64,
last_timestamp: AtomicU64,
}
impl NonceManager {
pub fn new(strategy: NonceStrategy) -> Self {
Self {
strategy,
counter: AtomicU64::new(0),
last_timestamp: AtomicU64::new(0),
}
}
pub fn timestamp() -> Self {
Self::new(NonceStrategy::Timestamp)
}
pub fn counter() -> Self {
Self::new(NonceStrategy::Counter)
}
pub fn high_frequency() -> Self {
Self::new(NonceStrategy::TimestampWithCounter)
}
pub fn next(&self) -> u64 {
match self.strategy {
NonceStrategy::Timestamp => current_timestamp_nanos(),
NonceStrategy::Counter => self.counter.fetch_add(1, Ordering::SeqCst),
NonceStrategy::TimestampWithCounter => self.next_hf(),
}
}
fn next_hf(&self) -> u64 {
let now = current_timestamp_nanos();
let mut previous = self.last_timestamp.load(Ordering::Acquire);
loop {
let next = now.max(previous.saturating_add(1));
match self.last_timestamp.compare_exchange_weak(
previous,
next,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => return next,
Err(actual) => previous = actual,
}
}
}
pub fn reset(&self) {
self.counter.store(0, Ordering::SeqCst);
self.last_timestamp.store(0, Ordering::SeqCst);
}
}
impl Default for NonceManager {
fn default() -> Self {
Self::timestamp()
}
}
#[inline]
pub fn current_timestamp_millis() -> u64 {
#[cfg(target_arch = "wasm32")]
{
return js_sys::Date::now() as u64;
}
#[cfg(not(target_arch = "wasm32"))]
{
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("time went backwards")
.as_millis() as u64
}
}
#[inline]
pub fn current_timestamp_micros() -> u64 {
#[cfg(target_arch = "wasm32")]
{
return current_timestamp_millis()
.checked_mul(1_000)
.expect("Unix timestamp in microseconds exceeds u64");
}
#[cfg(not(target_arch = "wasm32"))]
{
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("time went backwards")
.as_micros() as u64
}
}
fn monotonic_timestamp_nanos(candidate: u64, last: &AtomicU64) -> u64 {
let mut previous = last.load(Ordering::Acquire);
loop {
let next = candidate.max(previous.saturating_add(1));
match last.compare_exchange_weak(previous, next, Ordering::AcqRel, Ordering::Acquire) {
Ok(_) => return next,
Err(actual) => previous = actual,
}
}
}
#[inline]
pub fn current_timestamp_nanos() -> u64 {
#[cfg(target_arch = "wasm32")]
let candidate = current_timestamp_millis()
.checked_mul(1_000_000)
.expect("Unix timestamp in nanoseconds exceeds u64");
#[cfg(not(target_arch = "wasm32"))]
let candidate = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("time went backwards")
.as_nanos()
.try_into()
.expect("Unix timestamp in nanoseconds exceeds u64");
monotonic_timestamp_nanos(candidate, &LAST_TIMESTAMP_NANOS)
}
pub fn parse_decimal(value: &str) -> Result<u64> {
if value.is_empty() || !value.bytes().all(|byte| byte.is_ascii_digit()) {
return Err(Error::InvalidNonce(value.to_owned()));
}
value
.parse::<u64>()
.map_err(|_| Error::InvalidNonce(value.to_owned()))
}
pub mod serde_decimal {
use super::*;
pub fn serialize<S>(value: &u64, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&value.to_string())
}
pub fn deserialize<'de, D>(deserializer: D) -> std::result::Result<u64, D::Error>
where
D: Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
parse_decimal(&value).map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_timestamp_nonce() {
let manager = NonceManager::timestamp();
let n1 = manager.next();
let n2 = manager.next();
let now = current_timestamp_nanos();
assert!(n1 <= now && n1 > now - 1_000_000_000);
assert!(n2 >= n1);
}
#[test]
fn test_counter_nonce() {
let manager = NonceManager::counter();
assert_eq!(manager.next(), 0);
assert_eq!(manager.next(), 1);
assert_eq!(manager.next(), 2);
}
#[test]
fn test_high_frequency_nonce() {
let manager = NonceManager::high_frequency();
let nonces: Vec<_> = (0..100).map(|_| manager.next()).collect();
for i in 1..nonces.len() {
assert!(
nonces[i] > nonces[i - 1],
"Nonce {} ({}) should be greater than {} ({})",
i,
nonces[i],
i - 1,
nonces[i - 1]
);
}
}
#[test]
fn test_nanosecond_clock_handles_same_tick_monotonically() {
let last = AtomicU64::new(0);
assert_eq!(monotonic_timestamp_nanos(123, &last), 123);
assert_eq!(monotonic_timestamp_nanos(123, &last), 124);
assert_eq!(monotonic_timestamp_nanos(122, &last), 125);
}
#[test]
fn test_parse_decimal_above_js_safe_integer() {
assert_eq!(
parse_decimal("9007199254740993").unwrap(),
9_007_199_254_740_993
);
assert_eq!(parse_decimal(&u64::MAX.to_string()).unwrap(), u64::MAX);
assert!(parse_decimal("18446744073709551616").is_err());
assert!(parse_decimal("1.0").is_err());
assert!(parse_decimal("-1").is_err());
assert!(parse_decimal("").is_err());
}
}