use core::fmt;
use core::ops::Deref;
use core::str::FromStr;
#[cfg(feature = "std")]
use std::time::{SystemTime, UNIX_EPOCH};
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 = ALPHABET.len() as u64;
const BASE_USIZE: usize = ALPHABET.len();
const TIMESTAMP_CHAR_COUNT: usize = 8;
const COUNTER_CHAR_COUNT: usize = 6;
const RANDOM_CHAR_COUNT: usize = 7;
const ID_LENGTH: usize = TIMESTAMP_CHAR_COUNT + COUNTER_CHAR_COUNT + RANDOM_CHAR_COUNT;
const RANDOM_BATCH_SIZE: usize = 16384;
const FIRST_INDEX: u8 = 0;
const MAX_INDEX: u8 = 57;
const INVALID_INDEX: u8 = 0xFF;
const PACKED_BYTE_COUNT: usize = 16;
const FIELD_MSB_MASK: u128 = 0x82082082082082082082082082082080;
const MAX_TIMESTAMP: u64 = BASE.pow(TIMESTAMP_CHAR_COUNT as u32) - 1;
const COUNTER_HEAD_CHAR_COUNT: usize = COUNTER_CHAR_COUNT - 1;
const ALPHABET_LOOKUP: [u8; 64] = {
let mut table = [0u8; 64];
let mut i = 0;
while i < 58 {
table[i] = ALPHABET[i];
i += 1;
}
table
};
const DECODE: [u8; 256] = {
let mut table = [INVALID_INDEX; 256];
let mut i: usize = 0;
while i < BASE_USIZE {
table[ALPHABET[i] as usize] = i as u8;
i += 1;
}
table
};
fn pack_timestamp(indices: &[u8; TIMESTAMP_CHAR_COUNT]) -> u128 {
let mut value: u128 = 0;
let mut i = 0;
while i < TIMESTAMP_CHAR_COUNT {
value = (value << 6) | indices[i] as u128;
i += 1;
}
value << 80
}
fn pack_counter_head(indices: &[u8; COUNTER_HEAD_CHAR_COUNT]) -> u128 {
let mut value: u128 = 0;
let mut i = 0;
while i < COUNTER_HEAD_CHAR_COUNT {
value = (value << 6) | indices[i] as u128;
i += 1;
}
value << 50
}
#[inline(always)]
fn pack_suffix(counter_tail: u8, random: &[u8]) -> u128 {
let mut value: u64 = counter_tail as u64;
value = (value << 6) | random[0] as u64;
value = (value << 6) | random[1] as u64;
value = (value << 6) | random[2] as u64;
value = (value << 6) | random[3] as u64;
value = (value << 6) | random[4] as u64;
value = (value << 6) | random[5] as u64;
value = (value << 8) | (random[6] as u64) << 2;
value as u128
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseSparkIdError {
kind: ParseErrorKind,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(clippy::enum_variant_names)]
enum ParseErrorKind {
InvalidLength(usize),
InvalidChar { byte: u8, position: usize },
InvalidBinaryIndex { value: u8, byte_position: usize },
InvalidPadding,
}
impl fmt::Display for ParseSparkIdError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.kind {
ParseErrorKind::InvalidLength(length) => {
write!(f, "invalid SparkId length: expected 21, got {length}")
}
ParseErrorKind::InvalidChar { byte, position } => {
write!(
f,
"invalid character '{}' at position {position} in SparkId",
*byte as char
)
}
ParseErrorKind::InvalidBinaryIndex {
value,
byte_position,
} => {
write!(
f,
"invalid 6-bit index {value} at byte {byte_position} in binary SparkId"
)
}
ParseErrorKind::InvalidPadding => {
write!(f, "non-zero padding bits in binary SparkId")
}
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SparkId(u128);
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SparkIdStr([u8; ID_LENGTH]);
#[cfg(feature = "std")]
use std::cell::RefCell;
#[cfg(feature = "std")]
std::thread_local! {
static LOCAL_GEN: RefCell<IdGenerator> = RefCell::new(IdGenerator::new());
}
impl SparkId {
#[cfg(feature = "std")]
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
LOCAL_GEN.with(|generator| generator.borrow_mut().next_id())
}
#[inline]
pub fn as_u128(&self) -> u128 {
self.0
}
#[inline]
pub fn from_u128(value: u128) -> Result<Self, ParseSparkIdError> {
if value & 0x03 != 0 {
return Err(ParseSparkIdError {
kind: ParseErrorKind::InvalidPadding,
});
}
let invalid =
value & (value << 1) & (value << 2) & ((value << 3) | (value << 4)) & FIELD_MSB_MASK;
if invalid != 0 {
let first_invalid_bit = 128 - invalid.leading_zeros(); let field_index = (128 - first_invalid_bit as usize) / 6;
let shift = 122 - (field_index * 6);
let field_value = ((value >> shift) & 0x3F) as u8;
return Err(ParseSparkIdError {
kind: ParseErrorKind::InvalidBinaryIndex {
value: field_value,
byte_position: (field_index * 6) / 8,
},
});
}
Ok(SparkId(value))
}
#[inline]
pub fn to_bytes(&self) -> [u8; PACKED_BYTE_COUNT] {
self.0.to_be_bytes()
}
#[inline]
pub fn from_bytes(bytes: [u8; PACKED_BYTE_COUNT]) -> Result<Self, ParseSparkIdError> {
Self::from_u128(u128::from_be_bytes(bytes))
}
#[inline]
pub fn as_str(&self) -> SparkIdStr {
let mut out = [0u8; ID_LENGTH];
self.encode_utf8(&mut out);
SparkIdStr(out)
}
#[inline]
pub fn encode_utf8(&self, buffer: &mut [u8; ID_LENGTH]) {
let value = self.0;
let mut shift = 122i32;
let mut i = 0;
while i < ID_LENGTH {
buffer[i] = ALPHABET_LOOKUP[((value >> shift as u32) & 0x3F) as usize];
shift -= 6;
i += 1;
}
}
#[inline]
pub fn timestamp_ms(&self) -> u64 {
let v = self.0;
let index_0 = ((v >> 122) & 0x3F) as u64;
let index_1 = ((v >> 116) & 0x3F) as u64;
let index_2 = ((v >> 110) & 0x3F) as u64;
let index_3 = ((v >> 104) & 0x3F) as u64;
let index_4 = ((v >> 98) & 0x3F) as u64;
let index_5 = ((v >> 92) & 0x3F) as u64;
let index_6 = ((v >> 86) & 0x3F) as u64;
let index_7 = ((v >> 80) & 0x3F) as u64;
let mut value: u64 = index_0;
value = value * BASE + index_1;
value = value * BASE + index_2;
value = value * BASE + index_3;
value = value * BASE + index_4;
value = value * BASE + index_5;
value = value * BASE + index_6;
value = value * BASE + index_7;
value
}
#[cfg(feature = "std")]
#[inline]
pub fn timestamp(&self) -> SystemTime {
UNIX_EPOCH + std::time::Duration::from_millis(self.timestamp_ms())
}
}
impl SparkIdStr {
fn as_str_inner(&self) -> &str {
unsafe { core::str::from_utf8_unchecked(&self.0) }
}
}
impl Deref for SparkIdStr {
type Target = str;
fn deref(&self) -> &str {
self.as_str_inner()
}
}
impl AsRef<str> for SparkIdStr {
fn as_ref(&self) -> &str {
self.as_str_inner()
}
}
impl fmt::Display for SparkIdStr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str_inner())
}
}
impl fmt::Debug for SparkIdStr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SparkIdStr({})", self.as_str_inner())
}
}
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 {
let s = id.as_str();
String::from(&*s)
}
}
impl FromStr for SparkId {
type Err = ParseSparkIdError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes = s.as_bytes();
if bytes.len() != ID_LENGTH {
return Err(ParseSparkIdError {
kind: ParseErrorKind::InvalidLength(bytes.len()),
});
}
let mut value: u128 = 0;
let mut position = 0;
while position < 20 {
let index = DECODE[bytes[position] as usize];
if index == INVALID_INDEX {
return Err(ParseSparkIdError {
kind: ParseErrorKind::InvalidChar {
byte: bytes[position],
position,
},
});
}
value = (value << 6) | index as u128;
position += 1;
}
let index = DECODE[bytes[20] as usize];
if index == INVALID_INDEX {
return Err(ParseSparkIdError {
kind: ParseErrorKind::InvalidChar {
byte: bytes[20],
position: 20,
},
});
}
value = (value << 8) | (index as u128) << 2;
Ok(SparkId(value))
}
}
impl<'a> TryFrom<&'a str> for SparkId {
type Error = ParseSparkIdError;
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
s.parse()
}
}
impl FromStr for SparkIdStr {
type Err = ParseSparkIdError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes = s.as_bytes();
if bytes.len() != ID_LENGTH {
return Err(ParseSparkIdError {
kind: ParseErrorKind::InvalidLength(bytes.len()),
});
}
for (position, &byte) in bytes.iter().enumerate() {
if DECODE[byte as usize] == INVALID_INDEX {
return Err(ParseSparkIdError {
kind: ParseErrorKind::InvalidChar { byte, position },
});
}
}
Ok(SparkIdStr(bytes.try_into().unwrap()))
}
}
impl<'a> TryFrom<&'a str> for SparkIdStr {
type Error = ParseSparkIdError;
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
s.parse()
}
}
#[cfg(feature = "serde")]
mod serde_support {
use super::{SparkId, SparkIdStr, ID_LENGTH, PACKED_BYTE_COUNT};
use core::fmt;
use serde::de::{self, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
impl Serialize for SparkId {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
if serializer.is_human_readable() {
serializer.serialize_str(&self.as_str())
} else {
self.to_bytes().serialize(serializer)
}
}
}
impl<'de> Deserialize<'de> for SparkId {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
if deserializer.is_human_readable() {
struct SparkIdStringVisitor;
impl<'de> Visitor<'de> for SparkIdStringVisitor {
type Value = SparkId;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "a {ID_LENGTH}-char Base58 SparkId string")
}
fn visit_str<E: de::Error>(self, value: &str) -> Result<SparkId, E> {
value.parse().map_err(de::Error::custom)
}
}
deserializer.deserialize_str(SparkIdStringVisitor)
} else {
let bytes = <[u8; PACKED_BYTE_COUNT]>::deserialize(deserializer)?;
SparkId::from_bytes(bytes).map_err(de::Error::custom)
}
}
}
impl Serialize for SparkIdStr {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self)
}
}
impl<'de> Deserialize<'de> for SparkIdStr {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct SparkIdStrVisitor;
impl<'de> Visitor<'de> for SparkIdStrVisitor {
type Value = SparkIdStr;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "a {ID_LENGTH}-char Base58 SparkId string")
}
fn visit_str<E: de::Error>(self, value: &str) -> Result<SparkIdStr, E> {
value.parse().map_err(de::Error::custom)
}
}
deserializer.deserialize_str(SparkIdStrVisitor)
}
}
}
pub struct IdGenerator {
timestamp_cache_ms: u64,
counter_head: [u8; COUNTER_HEAD_CHAR_COUNT],
counter_tail: u8,
cached_timestamp_packed: u128,
cached_prefix: u128,
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,
counter_head: [FIRST_INDEX; COUNTER_HEAD_CHAR_COUNT],
counter_tail: FIRST_INDEX,
cached_timestamp_packed: 0,
cached_prefix: 0,
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 {
let random_position = self.prepare_next(self.current_time_ms());
let random = unsafe {
self.random_buffer
.get_unchecked(random_position..random_position + RANDOM_CHAR_COUNT)
};
SparkId(self.cached_prefix | pack_suffix(self.counter_tail, random))
}
pub fn next_id_at(&mut self, timestamp_ms: u64) -> SparkId {
let random_position = self.prepare_next(timestamp_ms);
let random = unsafe {
self.random_buffer
.get_unchecked(random_position..random_position + RANDOM_CHAR_COUNT)
};
SparkId(self.cached_prefix | pack_suffix(self.counter_tail, random))
}
#[inline(always)]
fn prepare_next(&mut self, timestamp: u64) -> usize {
if timestamp > self.timestamp_cache_ms {
let delta = timestamp - self.timestamp_cache_ms;
self.timestamp_cache_ms = timestamp;
if delta <= BASE {
self.increment_encoded_timestamp(delta);
} else {
self.encode_timestamp(timestamp);
}
self.seed_counter();
} else {
if self.counter_tail < MAX_INDEX {
self.counter_tail += 1;
} 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;
position
}
#[cfg(feature = "std")]
fn current_time_ms(&self) -> u64 {
#[cfg(any(test, feature = "test-internals"))]
if let Some(f) = self.time_function {
return f();
}
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system clock before Unix epoch")
.as_millis() as u64
}
#[cold]
fn encode_timestamp(&mut self, mut timestamp: u64) {
assert!(
timestamp <= MAX_TIMESTAMP,
"Timestamp out of range: {timestamp} (valid range: 0 to {MAX_TIMESTAMP})"
);
let mut indices = [0u8; TIMESTAMP_CHAR_COUNT];
let mut i = TIMESTAMP_CHAR_COUNT;
while i > 0 {
i -= 1;
indices[i] = (timestamp % BASE) as u8;
timestamp /= BASE;
}
self.cached_timestamp_packed = pack_timestamp(&indices);
}
#[inline(always)]
fn increment_encoded_timestamp(&mut self, delta: u64) {
const FIELD_7_SHIFT: u32 = 80;
const FIELD_MASK: u128 = 0x3F;
let current = ((self.cached_timestamp_packed >> FIELD_7_SHIFT) & FIELD_MASK) as u64;
let headroom = MAX_INDEX as u64 - current;
if delta <= headroom {
self.cached_timestamp_packed += (delta as u128) << FIELD_7_SHIFT;
return;
}
let remainder = delta - headroom - 1;
debug_assert!(remainder < BASE, "delta must be <= 58");
self.cached_timestamp_packed = (self.cached_timestamp_packed & !(FIELD_MASK << FIELD_7_SHIFT))
| ((remainder as u128) << FIELD_7_SHIFT);
let mut shift = FIELD_7_SHIFT + 6;
loop {
let field = ((self.cached_timestamp_packed >> shift) & FIELD_MASK) as u64;
if field < MAX_INDEX as u64 {
self.cached_timestamp_packed += 1u128 << shift;
return;
}
self.cached_timestamp_packed &= !(FIELD_MASK << shift);
if shift >= 122 {
break;
}
shift += 6;
}
panic!(
"Timestamp out of range: {} (valid range: 0 to {MAX_TIMESTAMP})",
self.timestamp_cache_ms
);
}
#[cold]
fn refill_random(&mut self) {
self.rng.fill_bytes(&mut self.raw_buffer);
let mut count = 0;
for &byte in &*self.raw_buffer {
let value = byte & 0x3F;
self.random_buffer[count] = value;
count += (value < BASE_USIZE as u8) as usize;
}
self.random_count = count;
self.random_position = 0;
}
#[inline]
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.counter_head
.copy_from_slice(&self.random_buffer[position..position + COUNTER_HEAD_CHAR_COUNT]);
self.counter_tail = self.random_buffer[position + COUNTER_HEAD_CHAR_COUNT];
self.cached_prefix = self.cached_timestamp_packed | pack_counter_head(&self.counter_head);
}
#[cold]
fn increment_carry(&mut self) {
for i in (0..COUNTER_HEAD_CHAR_COUNT).rev() {
if self.counter_head[i] < MAX_INDEX {
self.counter_head[i] += 1;
self.counter_tail = FIRST_INDEX;
self.cached_prefix = self.cached_timestamp_packed | pack_counter_head(&self.counter_head);
return;
}
self.counter_head[i] = FIRST_INDEX;
}
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 counter_head_indices(&self) -> &[u8; COUNTER_HEAD_CHAR_COUNT] {
&self.counter_head
}
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, indices: &[u8; 5]) {
self.counter_head.copy_from_slice(indices);
}
pub fn set_counter_tail(&mut self, index: u8) {
self.counter_tail = index;
}
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())
}
}