didbot_schema/tid.rs
1//! Timestamp identifiers: the sortable keys this server mints records under.
2//!
3//! `didbot-pds`'s record store mints TIDs, and this module reads one back. A
4//! minter with no matching parser is a format nothing can check, and the
5//! first thing a wrong bit in an rkey costs is the ordering the whole store is
6//! built on.
7//!
8//! A TID is a 64-bit integer rendered as thirteen characters of a base32
9//! alphabet ordered so that ASCII order matches numeric order. The layout is
10//! one clear top bit, 53 bits of microseconds since the Unix epoch, and a
11//! 10-bit clock identifier.
12//!
13//! [`Minter`] is the other half: the one place a TID is made. Record keys
14//! come from it and so do the revisions a commit is stamped with, and they
15//! come from the same one per store so that neither can repeat the other.
16
17use std::sync::Mutex;
18use std::time::{SystemTime, UNIX_EPOCH};
19
20/// The alphabet a TID is written in: base32 with the digits and letters
21/// reordered so that string order matches numeric order.
22pub const BASE32_SORTABLE: &[u8; 32] = b"234567abcdefghijklmnopqrstuvwxyz";
23
24/// How many characters a TID always is. Not a maximum: a TID is never padded
25/// and never abbreviated.
26pub const TID_LENGTH: usize = 13;
27
28/// Why a string is not a TID.
29#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
30pub enum TidError {
31 /// Not exactly [`TID_LENGTH`] characters.
32 #[error("tid is {0} characters, must be exactly {TID_LENGTH}")]
33 WrongLength(usize),
34 /// A character outside [`BASE32_SORTABLE`]. Case matters: `3JZFCIJPJ2Z2A`
35 /// is not the same string as `3jzfcijpj2z2a` and is not a TID.
36 #[error("invalid character {0:?} in tid: not sortable base32")]
37 InvalidCharacter(char),
38 /// The top bit of the 64-bit value is set.
39 ///
40 /// The leading character therefore has to fall in the first half of the
41 /// alphabet, which is what rules out `zzzzzzzzzzzzz`. The bit is reserved,
42 /// and a value that uses it is a different format wearing this one's
43 /// shape.
44 #[error("tid has its high bit set: leading character {0:?} is out of range")]
45 HighBitSet(char),
46}
47
48/// A parsed TID: the timestamp it carries and the clock that minted it.
49#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
50pub struct Tid {
51 micros: u64,
52 clock_id: u16,
53}
54
55impl Tid {
56 /// Microseconds since the Unix epoch, as the key encodes them.
57 ///
58 /// This is what the minter's clock said, not a verified time. A TID says
59 /// when its writer believed it was writing.
60 pub fn micros(self) -> u64 {
61 self.micros
62 }
63
64 /// The 10-bit clock identifier that distinguishes concurrent minters.
65 pub fn clock_id(self) -> u16 {
66 self.clock_id
67 }
68}
69
70/// Checks a string against the TID rules.
71///
72/// ```
73/// use didbot_schema::tid::validate;
74/// assert!(validate("3jzfcijpj2z2a").is_ok());
75/// assert!(validate("3JZFCIJPJ2Z2A").is_err()); // case-sensitive
76/// assert!(validate("zzzzzzzzzzzzz").is_err()); // high bit set
77/// ```
78pub fn validate(tid: &str) -> Result<(), TidError> {
79 parse(tid).map(|_| ())
80}
81
82/// Decodes a TID into the timestamp and clock identifier it carries.
83pub fn parse(tid: &str) -> Result<Tid, TidError> {
84 // Length is counted in characters rather than bytes so that a string of
85 // thirteen multi-byte characters is rejected for its characters and not
86 // for a byte count that would read as a puzzling off-by-26.
87 let length = tid.chars().count();
88 if length != TID_LENGTH {
89 return Err(TidError::WrongLength(length));
90 }
91 let mut value: u64 = 0;
92 for (index, ch) in tid.chars().enumerate() {
93 let digit = BASE32_SORTABLE
94 .iter()
95 .position(|candidate| u32::from(*candidate) == u32::from(ch))
96 .ok_or(TidError::InvalidCharacter(ch))?;
97 // The leading character contributes bits 63..=59, and bit 63 is
98 // reserved. Only the first sixteen letters of the alphabet leave it
99 // clear.
100 if index == 0 && digit >= 16 {
101 return Err(TidError::HighBitSet(ch));
102 }
103 value = (value << 5) | digit as u64;
104 }
105 Ok(Tid {
106 micros: (value >> 10) & ((1 << 53) - 1),
107 clock_id: (value & 0x3ff) as u16,
108 })
109}
110
111/// Encodes a 64-bit value as 13 characters of [`BASE32_SORTABLE`].
112///
113/// The alphabet's ordering is the whole point: it lets a store keep records in
114/// a plain ordered map and get newest-first listing from the key alone.
115fn encode_key(value: u64) -> String {
116 let mut out = [b'2'; 13];
117 let mut remaining = value;
118 for slot in out.iter_mut().rev() {
119 *slot = BASE32_SORTABLE[(remaining & 0x1f) as usize];
120 remaining >>= 5;
121 }
122 // Every byte came out of a 32-entry ASCII table.
123 String::from_utf8_lossy(&out).into_owned()
124}
125
126/// Reads back a key [`encode_key`] produced, if the string is one.
127///
128/// Used only on replay, to move a fresh minter past the keys a previous run
129/// of this process already handed out. Anything that is not thirteen
130/// characters of the sortable alphabet is not a key this server minted, and
131/// answers `None` rather than a number derived from part of it.
132fn decode_key(key: &str) -> Option<u64> {
133 if key.len() != 13 {
134 return None;
135 }
136 let mut value = 0u64;
137 for byte in key.bytes() {
138 let digit = BASE32_SORTABLE
139 .iter()
140 .position(|candidate| *candidate == byte)?;
141 value = (value << 5) | digit as u64;
142 }
143 Some(value)
144}
145
146/// Mints TIDs that sort in the order they were minted.
147///
148/// A TID as atproto specifies it: microseconds since the Unix epoch in the
149/// low 53 bits above a 10-bit clock identifier, with the top bit clear,
150/// rendered in [`BASE32_SORTABLE`]. Two properties matter. It is timestamped,
151/// so a TID says when it was minted without a second field beside it; and it
152/// is sortable as a string, so newest-first is a reverse iteration of an
153/// ordered map rather than a sort.
154///
155/// One minter serves a store's record keys and its commit revisions together.
156/// They are separate sequences to a reader and the same sequence here, which
157/// costs nothing — a revision is not required to be dense — and buys the one
158/// property both need: that nothing this store hands out ever repeats or
159/// sorts backwards, whatever the clock does.
160#[derive(Debug)]
161pub struct Minter {
162 clock_id: u64,
163 last: Mutex<u64>,
164}
165
166impl Minter {
167 /// A minter with a fixed clock identifier.
168 ///
169 /// The clock id is derived from the process start rather than randomly,
170 /// because this store is per-process and never merges with another: its
171 /// only job is to keep two processes writing the same logical repository
172 /// from colliding, which cannot happen to an in-memory store anyway.
173 #[must_use]
174 pub fn new() -> Self {
175 Self {
176 clock_id: micros_now() & 0x3ff,
177 last: Mutex::new(0),
178 }
179 }
180
181 /// The next TID, strictly greater than every one this minter has returned.
182 ///
183 /// The clock is only read, never trusted for ordering. Two writes inside
184 /// one microsecond, or a clock that steps backwards, would otherwise
185 /// produce a key that sorts before an earlier record and silently reorder
186 /// a listing.
187 pub fn next(&self) -> String {
188 let mut last = self
189 .last
190 .lock()
191 .unwrap_or_else(|poisoned| poisoned.into_inner());
192 let micros = micros_now().max(*last + 1);
193 *last = micros;
194 encode_key((micros << 10) | self.clock_id)
195 }
196
197 /// Notes a TID somebody else minted, so this minter never repeats it.
198 ///
199 /// A restart replays keys a previous process wrote, and the clock is the
200 /// only thing that would otherwise keep the new process past them. That
201 /// is enough while the clock moves forward and is exactly the assumption
202 /// [`Minter::next`] already refuses to make, so replay states it here
203 /// instead: a key that sorted after everything when it was written must
204 /// still sort after everything when the log is read back.
205 pub fn observe(&self, minted: &str) {
206 let Some(value) = decode_key(minted) else {
207 return;
208 };
209 let mut last = self
210 .last
211 .lock()
212 .unwrap_or_else(|poisoned| poisoned.into_inner());
213 *last = (*last).max(value >> 10);
214 }
215}
216
217impl Default for Minter {
218 fn default() -> Self {
219 Self::new()
220 }
221}
222
223/// Microseconds since the Unix epoch, clamped into a TID's 53 bits.
224///
225/// A clock before the epoch reads as the epoch. It is not worth an error
226/// path: the resulting key is still unique and still sorts, because
227/// [`Minter::next`] enforces both regardless of what the clock says.
228fn micros_now() -> u64 {
229 let micros = SystemTime::now()
230 .duration_since(UNIX_EPOCH)
231 .map(|since| since.as_micros())
232 .unwrap_or_default();
233 u64::try_from(micros).unwrap_or(u64::MAX) & ((1 << 53) - 1)
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239
240 #[test]
241 fn keys_mint_in_ascending_order_even_within_one_microsecond() {
242 let minter = Minter::new();
243 let keys: Vec<String> = (0..1000).map(|_| minter.next()).collect();
244 let mut sorted = keys.clone();
245 sorted.sort();
246 assert_eq!(keys, sorted, "record keys must sort in minting order");
247 sorted.dedup();
248 assert_eq!(sorted.len(), keys.len(), "record keys must be unique");
249 }
250
251 #[test]
252 fn keys_are_thirteen_characters_of_the_sortable_alphabet() {
253 let key = Minter::new().next();
254 assert_eq!(key.len(), 13, "a tid is 13 characters: {key}");
255 assert!(
256 key.bytes().all(|b| BASE32_SORTABLE.contains(&b)),
257 "{key} is not base32-sortable"
258 );
259 }
260
261 #[test]
262 fn a_key_decodes_back_to_what_it_encoded() {
263 for value in [0u64, 1, 31, 32, 1 << 40, (1 << 63) - 1] {
264 assert_eq!(decode_key(&encode_key(value)), Some(value));
265 }
266 }
267
268 #[test]
269 fn anything_that_is_not_a_minted_key_decodes_to_nothing() {
270 assert_eq!(decode_key("self"), None);
271 assert_eq!(decode_key(""), None);
272 // Thirteen characters, but `0`, `1`, `8` and `9` are not in the
273 // sortable alphabet — it starts at `2` so that the digits and the
274 // letters sort in one run.
275 assert_eq!(decode_key("1111111111111"), None);
276 assert_eq!(decode_key("9999999999999"), None);
277 }
278
279 /// A restart replays keys a previous process minted. A fresh minter must
280 /// come out above them even if the clock says otherwise, or a listing
281 /// silently reorders across the restart.
282 #[test]
283 fn a_minter_never_repeats_a_key_it_was_shown() {
284 let minter = Minter::new();
285 let ahead = encode_key(((micros_now() + 60_000_000) << 10) | 7);
286 minter.observe(&ahead);
287 let next = minter.next();
288 assert!(next > ahead, "{next} should sort after {ahead}");
289 }
290
291 #[test]
292 fn encoding_preserves_numeric_order() {
293 assert!(encode_key(1) < encode_key(2));
294 assert!(encode_key(31) < encode_key(32));
295 assert!(encode_key(1 << 40) < encode_key(1 << 41));
296 }
297}