use std::fmt;
use std::str::FromStr;
use std::sync::Mutex;
use std::time::{SystemTime, UNIX_EPOCH};
use crate::errors::TidError;
const BASE32_SORTABLE: &[u8; 32] = b"234567abcdefghijklmnopqrstuvwxyz";
const BASE32_DECODE: [u8; 256] = {
let mut table = [0xFF; 256];
table[b'2' as usize] = 0;
table[b'3' as usize] = 1;
table[b'4' as usize] = 2;
table[b'5' as usize] = 3;
table[b'6' as usize] = 4;
table[b'7' as usize] = 5;
table[b'a' as usize] = 6;
table[b'b' as usize] = 7;
table[b'c' as usize] = 8;
table[b'd' as usize] = 9;
table[b'e' as usize] = 10;
table[b'f' as usize] = 11;
table[b'g' as usize] = 12;
table[b'h' as usize] = 13;
table[b'i' as usize] = 14;
table[b'j' as usize] = 15;
table[b'k' as usize] = 16;
table[b'l' as usize] = 17;
table[b'm' as usize] = 18;
table[b'n' as usize] = 19;
table[b'o' as usize] = 20;
table[b'p' as usize] = 21;
table[b'q' as usize] = 22;
table[b'r' as usize] = 23;
table[b's' as usize] = 24;
table[b't' as usize] = 25;
table[b'u' as usize] = 26;
table[b'v' as usize] = 27;
table[b'w' as usize] = 28;
table[b'x' as usize] = 29;
table[b'y' as usize] = 30;
table[b'z' as usize] = 31;
table
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Tid(u64);
static LAST_TID: Mutex<Option<(u64, u16)>> = Mutex::new(None);
impl Tid {
pub const LENGTH: usize = 13;
const MAX_TIMESTAMP: u64 = (1u64 << 53) - 1;
const CLOCK_ID_MASK: u64 = 0x3FF;
pub fn new() -> Self {
Self::new_with_time(Self::current_timestamp_micros())
}
pub fn new_with_time(timestamp_micros: u64) -> Self {
assert!(
timestamp_micros <= Self::MAX_TIMESTAMP,
"Timestamp exceeds 53-bit maximum"
);
let mut last = LAST_TID.lock().unwrap();
let clock_id = if let Some((last_timestamp, last_clock)) = *last {
if timestamp_micros > last_timestamp {
Self::random_clock_id()
} else if timestamp_micros == last_timestamp {
if last_clock == Self::CLOCK_ID_MASK as u16 {
Self::random_clock_id()
} else {
last_clock + 1
}
} else {
let adjusted_timestamp = last_timestamp + 1;
let adjusted_clock = Self::random_clock_id();
*last = Some((adjusted_timestamp, adjusted_clock));
return Self::from_parts(adjusted_timestamp, adjusted_clock);
}
} else {
Self::random_clock_id()
};
*last = Some((timestamp_micros, clock_id));
Self::from_parts(timestamp_micros, clock_id)
}
pub fn from_parts(timestamp_micros: u64, clock_id: u16) -> Self {
assert!(
timestamp_micros <= Self::MAX_TIMESTAMP,
"Timestamp exceeds 53-bit maximum"
);
assert!(
clock_id <= Self::CLOCK_ID_MASK as u16,
"Clock ID exceeds 10-bit maximum"
);
let value = (timestamp_micros << 10) | (clock_id as u64);
Tid(value)
}
pub fn timestamp_micros(&self) -> u64 {
self.0 >> 10
}
pub fn clock_id(&self) -> u16 {
(self.0 & Self::CLOCK_ID_MASK) as u16
}
pub fn as_u64(&self) -> u64 {
self.0
}
pub fn encode(&self) -> String {
let mut chars = [0u8; Self::LENGTH];
let mut value = self.0;
for i in (0..Self::LENGTH).rev() {
chars[i] = BASE32_SORTABLE[(value & 0x1F) as usize];
value >>= 5;
}
String::from_utf8(chars.to_vec()).expect("base32-sortable encoding is always valid UTF-8")
}
pub fn decode(s: &str) -> Result<Self, TidError> {
if s.len() != Self::LENGTH {
return Err(TidError::InvalidLength {
expected: Self::LENGTH,
actual: s.len(),
});
}
let bytes = s.as_bytes();
let mut value: u64 = 0;
for (i, &byte) in bytes.iter().enumerate() {
let decoded = BASE32_DECODE[byte as usize];
if decoded == 0xFF {
return Err(TidError::InvalidCharacter {
character: byte as char,
position: i,
});
}
value = (value << 5) | (decoded as u64);
}
if value & (1u64 << 63) != 0 {
return Err(TidError::InvalidFormat {
reason: "Top bit must be 0".to_string(),
});
}
Ok(Tid(value))
}
fn current_timestamp_micros() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("System time before UNIX epoch")
.as_micros() as u64
}
fn random_clock_id() -> u16 {
use rand::Rng;
let mut rng = rand::rng();
(rng.next_u32() as u16) & (Self::CLOCK_ID_MASK as u16)
}
}
impl Default for Tid {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for Tid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.encode())
}
}
impl FromStr for Tid {
type Err = TidError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::decode(s)
}
}
impl serde::Serialize for Tid {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.encode())
}
}
impl<'de> serde::Deserialize<'de> for Tid {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Self::decode(&s).map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tid_encode_decode() {
let tid = Tid::new();
let encoded = tid.encode();
assert_eq!(encoded.len(), Tid::LENGTH);
let decoded = Tid::decode(&encoded).unwrap();
assert_eq!(tid, decoded);
}
#[test]
fn test_tid_from_parts() {
let timestamp = 1234567890123456u64;
let clock_id = 42u16;
let tid = Tid::from_parts(timestamp, clock_id);
assert_eq!(tid.timestamp_micros(), timestamp);
assert_eq!(tid.clock_id(), clock_id);
}
#[test]
fn test_tid_monotonic() {
let tid1 = Tid::new();
std::thread::sleep(std::time::Duration::from_micros(10));
let tid2 = Tid::new();
assert!(tid1 < tid2);
}
#[test]
fn test_tid_same_timestamp() {
let timestamp = 1234567890123456u64;
let tid1 = Tid::new_with_time(timestamp);
let tid2 = Tid::new_with_time(timestamp);
assert!(tid1 < tid2 || tid1.clock_id() + 1 == tid2.clock_id());
}
#[test]
fn test_tid_string_roundtrip() {
let tid = Tid::new();
let s = tid.to_string();
let parsed: Tid = s.parse().unwrap();
assert_eq!(tid, parsed);
}
#[test]
fn test_tid_serde() {
let tid = Tid::new();
let json = serde_json::to_string(&tid).unwrap();
let parsed: Tid = serde_json::from_str(&json).unwrap();
assert_eq!(tid, parsed);
}
#[test]
fn test_tid_valid_examples() {
let examples = ["3jzfcijpj2z2a", "7777777777777", "2222222222222"];
for example in &examples {
let tid = Tid::decode(example).unwrap();
assert_eq!(&tid.encode(), example);
}
}
#[test]
fn test_tid_invalid_length() {
let result = Tid::decode("123");
assert!(matches!(result, Err(TidError::InvalidLength { .. })));
}
#[test]
fn test_tid_invalid_character() {
let result = Tid::decode("123456789012!");
assert!(matches!(result, Err(TidError::InvalidCharacter { .. })));
}
#[test]
fn test_tid_first_char_range() {
let tid = Tid::new();
let encoded = tid.encode();
let first_char = encoded.chars().next().unwrap();
assert!("234567abcdefghij".contains(first_char));
}
#[test]
fn test_tid_sortability() {
let tid1 = Tid::from_parts(1000000, 0);
let tid2 = Tid::from_parts(2000000, 0);
let tid3 = Tid::from_parts(3000000, 0);
let s1 = tid1.to_string();
let s2 = tid2.to_string();
let s3 = tid3.to_string();
assert!(s1 < s2);
assert!(s2 < s3);
assert!(s1 < s3);
}
#[test]
fn test_tid_clock_backward() {
let timestamp1 = 2000000u64;
let tid1 = Tid::new_with_time(timestamp1);
let timestamp2 = 1000000u64; let tid2 = Tid::new_with_time(timestamp2);
assert!(tid2 > tid1);
}
}