use crate::{Scru64Id, NODE_CTR_SIZE};
mod node_spec;
pub use node_spec::{NodeSpec, NodeSpecError, NodeSpecParseError};
pub mod counter_mode;
use counter_mode::{CounterMode, DefaultCounterMode, RenewContext};
#[cfg(feature = "global_gen")]
#[cfg_attr(docsrs, doc(cfg(feature = "global_gen")))]
mod global_gen;
#[cfg(feature = "global_gen")]
pub use global_gen::GlobalGenerator;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Scru64Generator<C = DefaultCounterMode> {
prev: Scru64Id,
counter_size: u8,
counter_mode: C,
}
impl Scru64Generator {
pub const fn new(node_spec: NodeSpec) -> Self {
if node_spec.node_id_size() < 20 {
Self::with_counter_mode(node_spec, DefaultCounterMode::new(0))
} else {
Self::with_counter_mode(node_spec, DefaultCounterMode::new(1))
}
}
}
impl<C> Scru64Generator<C> {
pub const fn node_id(&self) -> u32 {
self.prev.node_ctr() >> self.counter_size
}
pub const fn node_id_size(&self) -> u8 {
NODE_CTR_SIZE - self.counter_size
}
pub fn node_spec(&self) -> NodeSpec {
match NodeSpec::with_node_prev(self.prev, self.node_id_size()) {
Ok(t) => t,
Err(_) => unreachable!(),
}
}
}
impl<C: CounterMode> Scru64Generator<C> {
pub const fn with_counter_mode(node_spec: NodeSpec, counter_mode: C) -> Self {
Self {
prev: node_spec.node_prev_raw(),
counter_size: NODE_CTR_SIZE - node_spec.node_id_size(),
counter_mode,
}
}
fn renew_node_ctr(&mut self, timestamp: u64) -> u32 {
let node_id = self.node_id();
let context = RenewContext { timestamp, node_id };
let counter = self.counter_mode.renew(self.counter_size, &context);
assert!(
counter < (1 << self.counter_size),
"illegal `CounterMode` implementation"
);
(node_id << self.counter_size) | counter
}
pub fn generate_or_reset_core(&mut self, unix_ts_ms: u64, rollback_allowance: u64) -> Scru64Id {
if let Some(value) = self.generate_or_abort_core(unix_ts_ms, rollback_allowance) {
value
} else {
let timestamp = unix_ts_ms >> 8;
self.prev = Scru64Id::from_parts(timestamp, self.renew_node_ctr(timestamp)).unwrap();
self.prev
}
}
pub fn generate_or_abort_core(
&mut self,
unix_ts_ms: u64,
rollback_allowance: u64,
) -> Option<Scru64Id> {
let timestamp = unix_ts_ms >> 8;
let allowance = rollback_allowance >> 8;
assert!(timestamp > 0, "`timestamp` out of range");
assert!(
allowance < (1 << 40),
"`rollback_allowance` out of reasonable range"
);
let prev_timestamp = self.prev.timestamp();
if timestamp > prev_timestamp {
self.prev = Scru64Id::from_parts(timestamp, self.renew_node_ctr(timestamp)).unwrap();
} else if timestamp + allowance >= prev_timestamp {
let prev_node_ctr = self.prev.node_ctr();
let counter_mask = (1u32 << self.counter_size) - 1;
if (prev_node_ctr & counter_mask) < counter_mask {
self.prev = Scru64Id::from_parts(prev_timestamp, prev_node_ctr + 1).unwrap();
} else {
self.prev = Scru64Id::from_parts(
prev_timestamp + 1,
self.renew_node_ctr(prev_timestamp + 1),
)
.expect("`timestamp` and `counter` reached max; no more ID available");
}
} else {
return None;
}
Some(self.prev)
}
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
mod with_std {
use super::{CounterMode, Scru64Generator, Scru64Id};
fn unix_ts_ms() -> u64 {
use std::time;
time::SystemTime::now()
.duration_since(time::UNIX_EPOCH)
.expect("clock may have gone backwards")
.as_millis() as u64
}
impl<C: CounterMode> Scru64Generator<C> {
pub fn generate(&mut self) -> Option<Scru64Id> {
self.generate_or_abort_core(unix_ts_ms(), 10_000)
}
pub fn generate_or_reset(&mut self) -> Scru64Id {
self.generate_or_reset_core(unix_ts_ms(), 10_000)
}
}
}
#[cfg(test)]
mod tests {
use super::{NodeSpec, Scru64Generator, Scru64Id};
use crate::test_cases::EXAMPLE_NODE_SPECS;
fn assert_consecutive(first: Scru64Id, second: Scru64Id) {
assert!(first < second);
if first.timestamp() == second.timestamp() {
assert_eq!(first.node_ctr() + 1, second.node_ctr());
} else {
assert_eq!(first.timestamp() + 1, second.timestamp());
}
}
#[test]
fn generate_or_reset() {
const N_LOOPS: usize = 64;
const ALLOWANCE: u64 = 10_000;
for e in EXAMPLE_NODE_SPECS {
let counter_size = 24 - e.node_id_size;
let node_spec = NodeSpec::with_node_id(e.node_id, e.node_id_size).unwrap();
let mut g = Scru64Generator::new(node_spec);
let mut ts = 1_577_836_800_000u64; let mut prev = g.generate_or_reset_core(ts, ALLOWANCE);
for _ in 0..N_LOOPS {
ts += 16;
let curr = g.generate_or_reset_core(ts, ALLOWANCE);
assert_consecutive(prev, curr);
assert!((curr.timestamp() - (ts >> 8)) < (ALLOWANCE >> 8));
assert!((curr.node_ctr() >> counter_size) == e.node_id);
prev = curr;
}
ts += ALLOWANCE * 16;
prev = g.generate_or_reset_core(ts, ALLOWANCE);
for _ in 0..N_LOOPS {
ts -= 16;
let curr = g.generate_or_reset_core(ts, ALLOWANCE);
assert_consecutive(prev, curr);
assert!((curr.timestamp() - (ts >> 8)) < (ALLOWANCE >> 8));
assert!((curr.node_ctr() >> counter_size) == e.node_id);
prev = curr;
}
ts += ALLOWANCE * 16;
prev = g.generate_or_reset_core(ts, ALLOWANCE);
for _ in 0..N_LOOPS {
ts -= ALLOWANCE + 0x100;
let curr = g.generate_or_reset_core(ts, ALLOWANCE);
assert!(prev > curr);
assert!((curr.timestamp() - (ts >> 8)) < (ALLOWANCE >> 8));
assert!((curr.node_ctr() >> counter_size) == e.node_id);
prev = curr;
}
}
}
#[test]
fn generate_or_abort() {
const N_LOOPS: usize = 64;
const ALLOWANCE: u64 = 10_000;
for e in EXAMPLE_NODE_SPECS {
let counter_size = 24 - e.node_id_size;
let node_spec = NodeSpec::with_node_id(e.node_id, e.node_id_size).unwrap();
let mut g = Scru64Generator::new(node_spec);
let mut ts = 1_577_836_800_000u64; let mut prev = g.generate_or_abort_core(ts, ALLOWANCE).unwrap();
for _ in 0..N_LOOPS {
ts += 16;
let curr = g.generate_or_abort_core(ts, ALLOWANCE).unwrap();
assert_consecutive(prev, curr);
assert!((curr.timestamp() - (ts >> 8)) < (ALLOWANCE >> 8));
assert!((curr.node_ctr() >> counter_size) == e.node_id);
prev = curr;
}
ts += ALLOWANCE * 16;
prev = g.generate_or_abort_core(ts, ALLOWANCE).unwrap();
for _ in 0..N_LOOPS {
ts -= 16;
let curr = g.generate_or_abort_core(ts, ALLOWANCE).unwrap();
assert_consecutive(prev, curr);
assert!((curr.timestamp() - (ts >> 8)) < (ALLOWANCE >> 8));
assert!((curr.node_ctr() >> counter_size) == e.node_id);
prev = curr;
}
ts += ALLOWANCE * 16;
g.generate_or_abort_core(ts, ALLOWANCE).unwrap();
ts -= ALLOWANCE + 0x100;
for _ in 0..N_LOOPS {
ts -= 16;
assert!(g.generate_or_abort_core(ts, ALLOWANCE).is_none());
}
}
}
#[cfg(feature = "std")]
#[test]
fn clock_integration() {
fn now() -> u64 {
use std::time;
time::SystemTime::now()
.duration_since(time::UNIX_EPOCH)
.expect("clock may have gone backwards")
.as_millis() as u64
>> 8
}
for e in EXAMPLE_NODE_SPECS {
let node_spec = NodeSpec::with_node_id(e.node_id, e.node_id_size).unwrap();
let mut g = Scru64Generator::new(node_spec);
let mut ts_now = now();
let mut x = g.generate().unwrap();
assert!((x.timestamp() - ts_now) <= 1);
ts_now = now();
x = g.generate_or_reset();
assert!((x.timestamp() - ts_now) <= 1);
}
}
}