Files
log/src/tid.rs
2026-02-04 22:18:09 +09:00

84 lines
2.4 KiB
Rust

use std::sync::atomic::{AtomicU32, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
/// Base32-sort character set used by ATProto TIDs
const BASE32_SORT: &[u8; 32] = b"234567abcdefghijklmnopqrstuvwxyz";
/// Atomic counter for clock ID to avoid collisions within the same microsecond
static CLOCK_ID: AtomicU32 = AtomicU32::new(0);
/// Generate a TID (Timestamp Identifier) per the ATProto specification.
///
/// Format: 13 characters of base32-sort encoding
/// - Bits 63..10: microsecond timestamp (54 bits)
/// - Bits 9..0: clock ID (10 bits, wrapping counter)
///
/// The high bit (bit 63) is always 0 to keep the value positive.
pub fn generate_tid() -> String {
let micros = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system clock before UNIX epoch")
.as_micros() as u64;
let clk = CLOCK_ID.fetch_add(1, Ordering::Relaxed) & 0x3FF; // 10-bit wrap
// Combine: timestamp in upper 54 bits, clock ID in lower 10 bits
let tid_value: u64 = (micros << 10) | (clk as u64);
encode_base32_sort(tid_value)
}
/// Encode a u64 into a 13-character base32-sort string (big-endian, zero-padded).
fn encode_base32_sort(mut value: u64) -> String {
let mut buf = [b'2'; 13]; // '2' is 0 in base32-sort
for i in (0..13).rev() {
buf[i] = BASE32_SORT[(value & 0x1F) as usize];
value >>= 5;
}
// Safety: all chars are ASCII
String::from_utf8(buf.to_vec()).expect("base32-sort is always valid UTF-8")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tid_length() {
let tid = generate_tid();
assert_eq!(tid.len(), 13);
}
#[test]
fn tid_charset() {
let tid = generate_tid();
let valid: &str = "234567abcdefghijklmnopqrstuvwxyz";
for c in tid.chars() {
assert!(valid.contains(c), "invalid char in TID: {}", c);
}
}
#[test]
fn tid_monotonic() {
let a = generate_tid();
let b = generate_tid();
// TIDs generated in sequence should sort correctly
assert!(a < b || a == b, "TIDs should be monotonically increasing: {} >= {}", a, b);
}
#[test]
fn encode_zero() {
let encoded = encode_base32_sort(0);
assert_eq!(encoded, "2222222222222");
}
#[test]
fn encode_known_value() {
// Verify encoding produces consistent results
let encoded = encode_base32_sort(1);
assert_eq!(encoded, "2222222222223");
}
}