use sha2::{Digest, Sha256};
fn encode(ms: u64, rand_tail: &[u8]) -> String {
let mut h = Sha256::new();
h.update(ms.to_le_bytes());
h.update(rand_tail);
let hex = hex::encode(h.finalize());
let time_hex = format!("{ms:013x}"); format!("{time_hex}{}", &hex[..13])
}
pub fn msg_id() -> String {
let ms = now_ms();
let nonce: Vec<u8> = std::iter::repeat_with(rand_byte).take(8).collect();
format!("msg_{}", encode(ms, &nonce[..8]))
}
pub fn prt_id() -> String {
let ms = now_ms();
let nonce: Vec<u8> = std::iter::repeat_with(rand_byte).take(8).collect();
format!("prt_{}", encode(ms, &nonce[..8]))
}
pub fn ses_id() -> String {
let ms = now_ms();
let nonce: Vec<u8> = std::iter::repeat_with(rand_byte).take(8).collect();
format!("ses_{}", encode(ms, &nonce[..8]))
}
pub fn que_id() -> String {
let ms = now_ms();
let nonce: Vec<u8> = std::iter::repeat_with(rand_byte).take(8).collect();
format!("que_{}", encode(ms, &nonce[..8]))
}
pub fn per_id() -> String {
let ms = now_ms();
let nonce: Vec<u8> = std::iter::repeat_with(rand_byte).take(8).collect();
format!("per_{}", encode(ms, &nonce[..8]))
}
pub fn ascending_tail() -> String {
use std::sync::atomic::{AtomicU64, Ordering};
static LAST_MS: AtomicU64 = AtomicU64::new(0);
static COUNTER: AtomicU64 = AtomicU64::new(0);
let ms = now_ms();
let prev = LAST_MS.swap(ms, Ordering::Relaxed);
if prev != ms {
COUNTER.store(0, Ordering::Relaxed);
}
let counter = COUNTER.fetch_add(1, Ordering::Relaxed) + 1;
let current = ms.wrapping_mul(0x1000).wrapping_add(counter);
let mut time = String::with_capacity(12);
for i in 0..6 {
let byte = (current >> (40 - 8 * i)) & 0xff;
time.push_str(&format!("{byte:02x}"));
}
const CHARS: &[u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
let mut tail = String::with_capacity(14);
for _ in 0..14 {
tail.push(CHARS[rand_byte() as usize % 62] as char);
}
format!("{time}{tail}")
}
pub fn evt_id() -> String {
let ms = now_ms();
let nonce: Vec<u8> = std::iter::repeat_with(rand_byte).take(8).collect();
format!("evt_{}", encode(ms, &nonce[..8]))
}
fn now_ms() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0)
}
fn rand_byte() -> u8 {
use std::collections::hash_map::RandomState;
use std::hash::{BuildHasher, Hasher};
let mut h = RandomState::new().build_hasher();
h.write_u64(now_ms());
h.finish() as u8
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ids_have_upstream_shape() {
for id in [msg_id(), prt_id(), ses_id(), evt_id()] {
let (prefix, body) = id.split_once('_').unwrap();
assert!(matches!(prefix, "msg" | "prt" | "ses" | "evt"));
assert_eq!(body.len(), 26, "upstream ids are prefix+26: {id}");
assert!(body.chars().all(|c| c.is_ascii_hexdigit()), "{id}");
}
}
#[test]
fn ids_unique_enough() {
let a = msg_id();
let b = msg_id();
assert_ne!(a, b, "collision within a millisecond: {a}");
}
}