use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
pub trait NonceProvider: Send + Sync {
fn next_nonce(&self) -> u64;
}
pub struct IncreasingNonce {
last_nonce: AtomicU64,
}
impl IncreasingNonce {
pub fn new() -> Self {
Self {
last_nonce: AtomicU64::new(0),
}
}
fn current_time_micros() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_micros() as u64
}
}
impl Default for IncreasingNonce {
fn default() -> Self {
Self::new()
}
}
impl NonceProvider for IncreasingNonce {
fn next_nonce(&self) -> u64 {
let time_nonce = Self::current_time_micros();
loop {
let last = self.last_nonce.load(Ordering::SeqCst);
let next = time_nonce.max(last + 1);
if self
.last_nonce
.compare_exchange(last, next, Ordering::SeqCst, Ordering::SeqCst)
.is_ok()
{
return next;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
use std::thread;
#[test]
fn test_nonce_strictly_increasing() {
let provider = IncreasingNonce::new();
let mut last = 0u64;
for _ in 0..1000 {
let nonce = provider.next_nonce();
assert!(nonce > last, "Nonce must be strictly increasing");
last = nonce;
}
}
#[test]
fn test_nonce_unique_across_threads() {
let provider = std::sync::Arc::new(IncreasingNonce::new());
let mut handles = vec![];
for _ in 0..4 {
let p = provider.clone();
handles.push(thread::spawn(move || {
let mut nonces = Vec::new();
for _ in 0..1000 {
nonces.push(p.next_nonce());
}
nonces
}));
}
let mut all_nonces = HashSet::new();
for handle in handles {
let nonces = handle.join().unwrap();
for nonce in nonces {
assert!(
all_nonces.insert(nonce),
"Nonce must be unique across threads"
);
}
}
}
}