atproto-record 0.14.5

AT Protocol record signature operations - cryptographic signing and verification for AT Protocol records
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
//! Timestamp Identifier (TID) generation and parsing.
//!
//! TIDs are 64-bit integers encoded as 13-character base32-sortable strings, combining
//! a microsecond timestamp with a random clock identifier for collision resistance.
//! They provide a sortable, distributed identifier scheme for AT Protocol records.
//!
//! ## Format
//!
//! - **Length**: Always 13 ASCII characters
//! - **Encoding**: Base32-sortable character set (`234567abcdefghijklmnopqrstuvwxyz`)
//! - **Structure**: 64-bit big-endian integer with:
//!   - Bit 0 (top): Always 0
//!   - Bits 1-53: Microseconds since UNIX epoch
//!   - Bits 54-63: Random 10-bit clock identifier
//!
//! ## Example
//!
//! ```
//! use atproto_record::tid::Tid;
//!
//! // Generate a new TID
//! let tid = Tid::new();
//! let tid_str = tid.to_string();
//! assert_eq!(tid_str.len(), 13);
//!
//! // Parse a TID string
//! let parsed = tid_str.parse::<Tid>().unwrap();
//! assert_eq!(tid, parsed);
//!
//! // TIDs are sortable by timestamp
//! let tid1 = Tid::new();
//! std::thread::sleep(std::time::Duration::from_micros(10));
//! let tid2 = Tid::new();
//! assert!(tid1 < tid2);
//! ```

use std::fmt;
use std::str::FromStr;
use std::sync::Mutex;
use std::time::{SystemTime, UNIX_EPOCH};

use crate::errors::TidError;

/// Base32-sortable character set for TID encoding.
///
/// This character set maintains lexicographic sort order when encoded TIDs
/// are compared as strings, ensuring timestamp ordering is preserved.
const BASE32_SORTABLE: &[u8; 32] = b"234567abcdefghijklmnopqrstuvwxyz";

/// Reverse lookup table for base32-sortable decoding.
///
/// Maps ASCII character values to their corresponding 5-bit values.
/// Invalid characters are marked with 0xFF.
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
};

/// Timestamp Identifier (TID) for AT Protocol records.
///
/// A TID combines a microsecond-precision timestamp with a random clock identifier
/// to create a sortable, collision-resistant identifier. TIDs are represented as
/// 13-character base32-sortable strings.
///
/// ## Monotonicity
///
/// The TID generator ensures monotonically increasing values even when the system
/// clock moves backwards or multiple TIDs are generated within the same microsecond.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Tid(u64);

/// Thread-local state for monotonic TID generation.
///
/// Tracks the last generated timestamp and clock identifier to ensure
/// monotonically increasing TID values.
static LAST_TID: Mutex<Option<(u64, u16)>> = Mutex::new(None);

impl Tid {
    /// The length of a TID string in characters.
    pub const LENGTH: usize = 13;

    /// Maximum valid timestamp value (53 bits).
    const MAX_TIMESTAMP: u64 = (1u64 << 53) - 1;

    /// Bitmask for extracting the 10-bit clock identifier.
    const CLOCK_ID_MASK: u64 = 0x3FF;

    /// Creates a new TID with the current timestamp and a random clock identifier.
    ///
    /// This function ensures monotonically increasing TID values by tracking the
    /// last generated TID and incrementing the clock identifier when necessary.
    ///
    /// # Example
    ///
    /// ```
    /// use atproto_record::tid::Tid;
    ///
    /// let tid = Tid::new();
    /// println!("Generated TID: {}", tid);
    /// ```
    pub fn new() -> Self {
        Self::new_with_time(Self::current_timestamp_micros())
    }

    /// Creates a new TID with a specific timestamp (for testing).
    ///
    /// # Arguments
    ///
    /// * `timestamp_micros` - Microseconds since UNIX epoch
    ///
    /// # Panics
    ///
    /// Panics if the timestamp exceeds 53 bits (year 2255+).
    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 {
                // New timestamp, generate random clock ID
                Self::random_clock_id()
            } else if timestamp_micros == last_timestamp {
                // Same timestamp, increment clock ID
                if last_clock == Self::CLOCK_ID_MASK as u16 {
                    // Clock ID overflow, use random
                    Self::random_clock_id()
                } else {
                    last_clock + 1
                }
            } else {
                // Clock moved backwards, use last timestamp + 1
                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 {
            // First TID, generate random clock ID
            Self::random_clock_id()
        };

        *last = Some((timestamp_micros, clock_id));
        Self::from_parts(timestamp_micros, clock_id)
    }

    /// Creates a TID from timestamp and clock identifier components.
    ///
    /// # Arguments
    ///
    /// * `timestamp_micros` - Microseconds since UNIX epoch (53 bits max)
    /// * `clock_id` - Random clock identifier (10 bits max)
    ///
    /// # Panics
    ///
    /// Panics if timestamp exceeds 53 bits or clock_id exceeds 10 bits.
    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"
        );

        // Combine: top bit 0, 53 bits timestamp, 10 bits clock ID
        let value = (timestamp_micros << 10) | (clock_id as u64);
        Tid(value)
    }

    /// Returns the timestamp component in microseconds since UNIX epoch.
    ///
    /// # Example
    ///
    /// ```
    /// use atproto_record::tid::Tid;
    ///
    /// let tid = Tid::new();
    /// let timestamp = tid.timestamp_micros();
    /// println!("Timestamp: {} μs", timestamp);
    /// ```
    pub fn timestamp_micros(&self) -> u64 {
        self.0 >> 10
    }

    /// Returns the clock identifier component (10 bits).
    ///
    /// # Example
    ///
    /// ```
    /// use atproto_record::tid::Tid;
    ///
    /// let tid = Tid::new();
    /// let clock_id = tid.clock_id();
    /// println!("Clock ID: {}", clock_id);
    /// ```
    pub fn clock_id(&self) -> u16 {
        (self.0 & Self::CLOCK_ID_MASK) as u16
    }

    /// Returns the raw 64-bit integer value.
    pub fn as_u64(&self) -> u64 {
        self.0
    }

    /// Encodes the TID as a 13-character base32-sortable string.
    ///
    /// # Example
    ///
    /// ```
    /// use atproto_record::tid::Tid;
    ///
    /// let tid = Tid::new();
    /// let encoded = tid.encode();
    /// assert_eq!(encoded.len(), 13);
    /// ```
    pub fn encode(&self) -> String {
        let mut chars = [0u8; Self::LENGTH];
        let mut value = self.0;

        // Encode from right to left (least significant to most significant)
        for i in (0..Self::LENGTH).rev() {
            chars[i] = BASE32_SORTABLE[(value & 0x1F) as usize];
            value >>= 5;
        }

        // BASE32_SORTABLE only contains valid UTF-8 ASCII characters
        String::from_utf8(chars.to_vec()).expect("base32-sortable encoding is always valid UTF-8")
    }

    /// Decodes a base32-sortable string into a TID.
    ///
    /// # Errors
    ///
    /// Returns [`TidError::InvalidLength`] if the string is not exactly 13 characters.
    /// Returns [`TidError::InvalidCharacter`] if the string contains invalid characters.
    /// Returns [`TidError::InvalidFormat`] if the decoded value has the top bit set.
    ///
    /// # Example
    ///
    /// ```
    /// use atproto_record::tid::Tid;
    ///
    /// let tid_str = "3jzfcijpj2z2a";
    /// let tid = Tid::decode(tid_str).unwrap();
    /// assert_eq!(tid.to_string(), tid_str);
    /// ```
    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);
        }

        // Verify top bit is 0
        if value & (1u64 << 63) != 0 {
            return Err(TidError::InvalidFormat {
                reason: "Top bit must be 0".to_string(),
            });
        }

        Ok(Tid(value))
    }

    /// Gets the current timestamp in microseconds since UNIX epoch.
    fn current_timestamp_micros() -> u64 {
        SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("System time before UNIX epoch")
            .as_micros() as u64
    }

    /// Generates a random 10-bit clock identifier.
    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);

        // Should have different clock IDs or incremented clock ID
        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() {
        // Examples from the specification
        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() {
        // First character must be in valid range per spec
        let tid = Tid::new();
        let encoded = tid.encode();
        let first_char = encoded.chars().next().unwrap();

        // First char must be 234567abcdefghij (values 0-15 in base32-sortable)
        assert!("234567abcdefghij".contains(first_char));
    }

    #[test]
    fn test_tid_sortability() {
        // TIDs with increasing timestamps should sort correctly as strings
        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() {
        // Simulate clock moving backwards
        let timestamp1 = 2000000u64;
        let tid1 = Tid::new_with_time(timestamp1);

        let timestamp2 = 1000000u64; // Earlier timestamp
        let tid2 = Tid::new_with_time(timestamp2);

        // TID should still be monotonically increasing
        assert!(tid2 > tid1);
    }
}