use core::fmt;
use core::ops::Deref;
#[cfg(feature = "std")]
use std::time::{SystemTime, UNIX_EPOCH};
use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec;
use rand::rngs::StdRng;
use rand::{RngCore, SeedableRng};
const ALPHABET: &[u8; 58] = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
const BASE: u64 = 58;
const COUNTER_CHAR_COUNT: usize = 6;
const RANDOM_CHAR_COUNT: usize = 8;
const ID_LENGTH: usize = 22;
const RANDOM_BATCH_SIZE: usize = 16384;
const FIRST_BYTE: u8 = ALPHABET[0];
const TIMESTAMP_LOOKUP: [u8; 58] = {
let mut table = [0u8; 58];
let mut i = 0;
while i < 58 {
table[i] = ALPHABET[i];
i += 1;
}
table
};
const RANDOM_BYTE_LOOKUP: [u8; 256] = {
let mut table = [0u8; 256];
let mut byte = 0;
while byte < 256 {
let value = byte & 0x3f;
if value < 58 {
table[byte] = ALPHABET[value];
}
byte += 1;
}
table
};
const SUCCESSOR: [u8; 123] = {
let mut table = [0u8; 123]; let mut i = 0;
while i < 57 {
table[ALPHABET[i] as usize] = ALPHABET[i + 1];
i += 1;
}
table
};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SparkId([u8; ID_LENGTH]);
impl SparkId {
#[cfg(feature = "std")]
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
crate::LOCAL_GEN.with(|generator| generator.borrow_mut().next_id())
}
fn as_str(&self) -> &str {
core::str::from_utf8(&self.0).expect("SparkId contains invalid UTF-8")
}
}
impl Deref for SparkId {
type Target = str;
fn deref(&self) -> &str {
self.as_str()
}
}
impl AsRef<str> for SparkId {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for SparkId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl fmt::Debug for SparkId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SparkId({})", self.as_str())
}
}
impl From<SparkId> for String {
fn from(id: SparkId) -> String {
id.as_str().to_owned()
}
}
pub struct IdGenerator {
timestamp_cache_ms: u64,
id_buffer: [u8; ID_LENGTH],
counter_tail: u8,
random_buffer: Box<[u8]>,
random_count: usize,
random_position: usize,
raw_buffer: Box<[u8]>,
rng: StdRng,
#[cfg(any(test, feature = "test-internals"))]
time_function: Option<fn() -> u64>,
}
impl IdGenerator {
pub fn new() -> Self {
IdGenerator {
timestamp_cache_ms: 0,
id_buffer: [FIRST_BYTE; ID_LENGTH],
counter_tail: FIRST_BYTE,
random_buffer: vec![0u8; RANDOM_BATCH_SIZE].into_boxed_slice(),
random_count: 0,
random_position: 0,
raw_buffer: vec![0u8; RANDOM_BATCH_SIZE].into_boxed_slice(),
rng: StdRng::from_os_rng(),
#[cfg(any(test, feature = "test-internals"))]
time_function: None,
}
}
#[cfg(feature = "std")]
pub fn next_id(&mut self) -> SparkId {
self.advance(self.current_time_ms());
SparkId(self.id_buffer)
}
pub fn next_id_at(&mut self, timestamp_ms: u64) -> SparkId {
self.advance(timestamp_ms);
SparkId(self.id_buffer)
}
fn advance(&mut self, timestamp: u64) {
if timestamp > self.timestamp_cache_ms {
self.timestamp_cache_ms = timestamp;
self.encode_timestamp(timestamp);
self.seed_counter();
} else {
let next = SUCCESSOR[self.counter_tail as usize];
if next != 0 {
self.counter_tail = next;
} else {
self.increment_carry();
}
}
if self.random_position + RANDOM_CHAR_COUNT > self.random_count {
self.refill_random();
}
let position = self.random_position;
self.random_position = position + RANDOM_CHAR_COUNT;
self.id_buffer[13] = self.counter_tail;
self.id_buffer[14..22].copy_from_slice(&self.random_buffer[position..position + RANDOM_CHAR_COUNT]);
}
#[cfg(feature = "std")]
fn current_time_ms(&self) -> u64 {
#[cfg(any(test, feature = "test-internals"))]
if let Some(f) = self.time_function {
return f();
}
let duration = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system clock before Unix epoch");
duration.as_secs() * 1000 + duration.subsec_millis() as u64
}
fn encode_timestamp(&mut self, mut timestamp: u64) {
let mut remainder: u64;
remainder = timestamp % BASE; timestamp /= BASE;
let c7 = TIMESTAMP_LOOKUP[remainder as usize];
remainder = timestamp % BASE; timestamp /= BASE;
let c6 = TIMESTAMP_LOOKUP[remainder as usize];
remainder = timestamp % BASE; timestamp /= BASE;
let c5 = TIMESTAMP_LOOKUP[remainder as usize];
remainder = timestamp % BASE; timestamp /= BASE;
let c4 = TIMESTAMP_LOOKUP[remainder as usize];
remainder = timestamp % BASE; timestamp /= BASE;
let c3 = TIMESTAMP_LOOKUP[remainder as usize];
remainder = timestamp % BASE; timestamp /= BASE;
let c2 = TIMESTAMP_LOOKUP[remainder as usize];
remainder = timestamp % BASE; timestamp /= BASE;
let c1 = TIMESTAMP_LOOKUP[remainder as usize];
let c0 = TIMESTAMP_LOOKUP[timestamp as usize];
self.id_buffer[0] = c0;
self.id_buffer[1] = c1;
self.id_buffer[2] = c2;
self.id_buffer[3] = c3;
self.id_buffer[4] = c4;
self.id_buffer[5] = c5;
self.id_buffer[6] = c6;
self.id_buffer[7] = c7;
}
fn refill_random(&mut self) {
self.rng.fill_bytes(&mut self.raw_buffer);
let mut count = 0;
for &byte in &self.raw_buffer {
let mapped = RANDOM_BYTE_LOOKUP[byte as usize];
if mapped != 0 {
self.random_buffer[count] = mapped;
count += 1;
}
}
self.random_count = count;
self.random_position = 0;
}
fn seed_counter(&mut self) {
if self.random_position + COUNTER_CHAR_COUNT > self.random_count {
self.refill_random();
}
let position = self.random_position;
self.random_position = position + COUNTER_CHAR_COUNT;
self.id_buffer[8..13]
.copy_from_slice(&self.random_buffer[position..position + 5]);
self.counter_tail = self.random_buffer[position + 5];
}
fn increment_carry(&mut self) {
for i in (8..=12).rev() {
let next = SUCCESSOR[self.id_buffer[i] as usize];
if next != 0 {
self.id_buffer[i] = next;
for j in (i + 1)..=12 {
self.id_buffer[j] = FIRST_BYTE;
}
self.counter_tail = FIRST_BYTE;
return;
}
}
self.timestamp_cache_ms += 1;
self.encode_timestamp(self.timestamp_cache_ms);
self.seed_counter();
}
}
#[cfg(any(test, feature = "test-internals"))]
impl IdGenerator {
pub fn set_time_function(&mut self, f: fn() -> u64) {
self.time_function = Some(f);
}
pub fn clear_time_function(&mut self) {
self.time_function = None;
}
pub fn prefix_plus_counter_head(&self) -> &[u8; 13] {
self.id_buffer[..13].try_into().unwrap()
}
pub fn counter_tail(&self) -> u8 {
self.counter_tail
}
pub fn encode_timestamp_test(&mut self, timestamp: u64) {
self.encode_timestamp(timestamp);
}
pub fn seed_counter_test(&mut self) {
self.seed_counter();
}
pub fn set_counter_head(&mut self, bytes: &[u8; 5]) {
self.id_buffer[8..13].copy_from_slice(bytes);
}
pub fn set_counter_tail(&mut self, byte: u8) {
self.counter_tail = byte;
}
pub fn set_timestamp_cache_ms(&mut self, timestamp: u64) {
self.timestamp_cache_ms = timestamp;
}
pub fn timestamp_cache_ms(&self) -> u64 {
self.timestamp_cache_ms
}
pub fn increment_carry_test(&mut self) {
self.increment_carry();
}
pub fn refill_random_test(&mut self) {
self.refill_random();
}
pub fn random_buffer_valid(&self) -> &[u8] {
&self.random_buffer[..self.random_count]
}
pub fn random_count(&self) -> usize {
self.random_count
}
}
impl Default for IdGenerator {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "std")]
impl Iterator for IdGenerator {
type Item = SparkId;
fn next(&mut self) -> Option<SparkId> {
Some(self.next_id())
}
}