1use crate::adapter::{Clock, Rng};
5use crate::error::SessionError;
6use std::time::UNIX_EPOCH;
7
8const CROCKFORD: &[u8; 32] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
10
11fn encode_crockford(val: u128) -> String {
14 let mut out = [0u8; 26];
15 for (i, slot) in out.iter_mut().enumerate() {
16 let idx = ((val >> (5 * (25 - i))) & 0x1f) as usize;
17 *slot = match CROCKFORD.get(idx) {
19 Some(c) => *c,
20 None => b'0',
21 };
22 }
23 String::from_utf8(out.into()).unwrap_or_default()
25}
26
27pub fn mint_ulid(clock: &impl Clock, rng: &impl Rng) -> Result<String, SessionError> {
30 let millis = clock
31 .now()
32 .duration_since(UNIX_EPOCH)
33 .map_err(|e| SessionError::new(format!("system clock is before the unix epoch: {e}")))?
34 .as_millis();
35 let time48 = millis & 0xFFFF_FFFF_FFFF;
36
37 let mut rand_bytes = [0u8; 10]; rng.fill_bytes(&mut rand_bytes)?;
39 let mut rand80: u128 = 0;
40 for b in rand_bytes {
41 rand80 = (rand80 << 8) | u128::from(b);
42 }
43
44 let val = (time48 << 80) | rand80;
45 Ok(encode_crockford(val))
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51 use crate::adapter::{FakeClock, FakeRng};
52 use std::time::{Duration, UNIX_EPOCH};
53
54 fn crockford_chars() -> &'static [u8] {
55 CROCKFORD
56 }
57
58 #[test]
59 fn mints_26_char_string() {
60 let clock = FakeClock(UNIX_EPOCH + Duration::from_millis(1));
61 let rng = FakeRng(0);
62 let id = mint_ulid(&clock, &rng).unwrap();
63 assert_eq!(id.len(), 26);
64 for ch in id.bytes() {
65 assert!(crockford_chars().contains(&ch), "unexpected char: {ch}");
66 }
67 }
68
69 #[test]
70 fn is_deterministic_under_fakes() {
71 let clock = FakeClock(UNIX_EPOCH + Duration::from_millis(1));
72 let rng = FakeRng(0);
73 let first = mint_ulid(&clock, &rng).unwrap();
74 let second = mint_ulid(&clock, &rng).unwrap();
75 assert_eq!(first, second);
76 }
77
78 #[test]
79 fn different_time_changes_prefix() {
80 let clock1 = FakeClock(UNIX_EPOCH + Duration::from_millis(1000));
81 let clock2 = FakeClock(UNIX_EPOCH + Duration::from_millis(2000));
82 let rng = FakeRng(0x00);
83 let id1 = mint_ulid(&clock1, &rng).unwrap();
84 let id2 = mint_ulid(&clock2, &rng).unwrap();
85 assert_ne!(&id1[..10], &id2[..10]);
87 assert_eq!(&id1[10..], &id2[10..]);
89 }
90
91 #[test]
92 fn different_rng_changes_suffix() {
93 let clock = FakeClock(UNIX_EPOCH + Duration::from_millis(1000));
94 let rng0 = FakeRng(0x00);
95 let rng1 = FakeRng(0xFF);
96 let id0 = mint_ulid(&clock, &rng0).unwrap();
97 let id1 = mint_ulid(&clock, &rng1).unwrap();
98 assert_eq!(&id0[..10], &id1[..10]);
100 assert_ne!(&id0[10..], &id1[10..]);
102 }
103
104 #[test]
105 fn known_vector() {
106 let clock = FakeClock(UNIX_EPOCH);
110 let rng = FakeRng(0x00);
111 let id = mint_ulid(&clock, &rng).unwrap();
112 assert_eq!(id, "00000000000000000000000000");
113 }
114
115 #[test]
116 fn clock_before_epoch_errors() {
117 if let Some(before_epoch) = UNIX_EPOCH.checked_sub(Duration::from_secs(1)) {
118 let clock = FakeClock(before_epoch);
119 let rng = FakeRng(0x00);
120 assert!(mint_ulid(&clock, &rng).is_err());
121 }
122 }
124}