use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
use thiserror::Error;
use super::{Id, IdGenerator, IdType};
use std::future::Future;
use std::pin::Pin;
#[derive(Debug, Error)]
pub enum UlidError {
#[error("Clock moved backwards. Refusing to generate ID.")]
ClockMovedBackwards,
#[error("Failed to generate entropy: {0}")]
EntropyError(String),
}
#[derive(Debug, Clone, Copy)]
pub struct UlidConfig {
pub monotonic: bool,
}
impl Default for UlidConfig {
fn default() -> Self {
Self { monotonic: true }
}
}
#[derive(Debug)]
pub struct Ulid {
config: UlidConfig,
last_timestamp: AtomicU64,
last_random: AtomicU64,
}
impl Ulid {
pub fn new(config: UlidConfig) -> Self {
Self {
config,
last_timestamp: AtomicU64::new(0),
last_random: AtomicU64::new(0),
}
}
pub fn with_default() -> Self {
Self::new(UlidConfig::default())
}
pub fn generate_string(&self) -> Result<String, UlidError> {
let bytes = self.generate_bytes()?;
Ok(encode_ulid(&bytes))
}
pub fn generate_bytes(&self) -> Result<[u8; 16], UlidError> {
let timestamp = self.current_timestamp()?;
let (timestamp, random) = if self.config.monotonic {
let last_ts = self.last_timestamp.load(Ordering::SeqCst);
if timestamp < last_ts {
return Err(UlidError::ClockMovedBackwards);
}
let mut random_bytes = [0u8; 10];
getrandom::fill(&mut random_bytes)
.map_err(|e| UlidError::EntropyError(e.to_string()))?;
if timestamp == last_ts {
let last_rand = self.last_random.load(Ordering::SeqCst);
let new_rand = last_rand.wrapping_add(1);
self.last_random.store(new_rand, Ordering::SeqCst);
let rand_bytes = new_rand.to_be_bytes();
random_bytes[0..8].copy_from_slice(&rand_bytes);
} else {
self.last_timestamp.store(timestamp, Ordering::SeqCst);
let mut rand_val_bytes = [0u8; 8];
rand_val_bytes.copy_from_slice(&random_bytes[0..8]);
let rand_val = u64::from_be_bytes(rand_val_bytes);
self.last_random.store(rand_val, Ordering::SeqCst);
}
(timestamp, random_bytes)
} else {
let mut random_bytes = [0u8; 10];
getrandom::fill(&mut random_bytes)
.map_err(|e| UlidError::EntropyError(e.to_string()))?;
(timestamp, random_bytes)
};
let mut bytes = [0u8; 16];
let ts_bytes = timestamp.to_be_bytes();
bytes[0..6].copy_from_slice(&ts_bytes[2..8]);
bytes[6..16].copy_from_slice(&random);
Ok(bytes)
}
fn current_timestamp(&self) -> Result<u64, UlidError> {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.map_err(|_| UlidError::ClockMovedBackwards)
}
}
impl IdGenerator for Ulid {
type Error = UlidError;
fn generate(&self) -> Pin<Box<dyn Future<Output = Result<Id, Self::Error>> + Send + '_>> {
Box::pin(async move {
let s = self.generate_string()?;
Ok(Id::String(s))
})
}
fn id_type(&self) -> IdType {
IdType::String128
}
}
fn encode_ulid(bytes: &[u8; 16]) -> String {
const ALPHABET: &[u8; 32] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
let mut result = String::with_capacity(26);
let mut buffer: u128 = 0;
let mut bits_in_buffer = 0;
let mut byte_idx = 0;
for _ in 0..26 {
while bits_in_buffer < 5 && byte_idx < 16 {
buffer = (buffer << 8) | (bytes[byte_idx] as u128);
bits_in_buffer += 8;
byte_idx += 1;
}
if bits_in_buffer >= 5 {
bits_in_buffer -= 5;
let index = ((buffer >> bits_in_buffer) & 0x1F) as usize;
result.push(ALPHABET[index] as char);
} else {
let index = ((buffer << (5 - bits_in_buffer)) & 0x1F) as usize;
result.push(ALPHABET[index] as char);
}
}
result
}