Skip to main content

mr_ulid/
generator.rs

1#[cfg(feature = "rand")]
2use std::time::SystemTime;
3use std::{fmt, ops::RangeInclusive, sync::Mutex};
4
5#[cfg(feature = "rand")]
6use rand::{
7    RngExt as _,
8    SeedableRng as _,       // cspell:disable-line
9    rngs::{StdRng, SysRng}, // cspell:disable-line
10};
11
12use crate::{RANDOM_BITS, RANDOM_GEN_MAX, TIMESTAMP_MASK, TIMESTAMP_MAX};
13
14/// Trait for entropy sources.
15///
16/// For a type to be used as an entropy source, implement the `EntropySource` trait and
17/// create an [`EntropySourceHandle`] out of it and set the handle using the [`set_entropy_source`] function.
18///
19/// # Example
20///
21/// ```no_run
22/// struct MySource;
23///
24/// impl MySource {
25///     fn new() -> Self {
26///         todo!()
27///     }
28/// }
29///
30/// impl mr_ulid::EntropySource for MySource {
31///     fn timestamp(&mut self) -> Option<u64> {
32///         todo!()
33///     }
34///     fn random(&mut self, range: std::ops::RangeInclusive<u128>) -> Option<u128> {
35///         todo!()
36///     }
37/// }
38///
39/// let my_source = MySource::new();
40/// let handle = mr_ulid::EntropySourceHandle::new(my_source);
41///
42/// mr_ulid::set_entropy_source(handle);
43/// ```
44pub trait EntropySource: Send {
45    /// Returns the current timestamp in milliseconds since the Unix epoch.
46    fn timestamp(&mut self) -> Option<u64>;
47
48    /// Returns a random number in the given range.
49    fn random(&mut self, range: RangeInclusive<u128>) -> Option<u128>;
50}
51
52/// An opaque handle for entropy sources.
53///
54/// A `EntropySourceHandle` wraps different types of entropy sources:
55///
56/// - [`NO_ENTROPY_SOURCE`]
57/// - [`STANDARD_ENTROPY_SOURCE`]
58/// - Types implementing the [`EntropySource`] trait.
59///
60/// A `EntropySourceHandle` is accepted by [`set_entropy_source`] function.
61///
62#[repr(transparent)]
63pub struct EntropySourceHandle {
64    inner: InnerHandle,
65}
66
67enum InnerHandle {
68    NoOp,
69    #[cfg(feature = "rand")]
70    Standard,
71    Custom(Box<dyn EntropySource>),
72}
73
74impl EntropySourceHandle {
75    /// Creates an `EntropySourceHandle` from a type implementing the `EntropySource` trait.
76    #[must_use]
77    pub fn new(source: impl EntropySource + 'static) -> Self {
78        Self {
79            inner: InnerHandle::Custom(Box::new(source)),
80        }
81    }
82}
83
84impl fmt::Debug for EntropySourceHandle {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        f.write_str("EntropySourceHandle { ... }")
87    }
88}
89
90/// Standard entropy source.
91///
92/// This is the default entropy source used to generate ULIDs if no
93/// other entropy source is set.
94///
95/// This entropy source uses the system clock and the `rand` crate. So if
96/// the `rand` crate is disabled, this entropy source is not available.
97///
98/// # Example
99///
100/// ```
101/// use mr_ulid::Ulid;
102///
103/// mr_ulid::set_entropy_source(mr_ulid::STANDARD_ENTROPY_SOURCE);
104///
105/// assert!(Ulid::try_new().is_some());
106/// ```
107#[cfg(feature = "rand")]
108pub const STANDARD_ENTROPY_SOURCE: EntropySourceHandle = EntropySourceHandle {
109    inner: InnerHandle::Standard,
110};
111
112/// No-Operation entropy source.
113///
114/// An entropy source which never generates any timestamps nor any random values.
115/// Setting this source will result in no ULIDs generated:
116///
117/// - `Ulid::try_new()` will always return `None`.
118/// - `Ulid::new()` will panic.
119///
120/// This entropy source is the default source if the `rand` crate is not enabled.
121///
122/// # Example
123///
124/// ```no_run
125/// use mr_ulid::Ulid;
126///
127/// mr_ulid::set_entropy_source(mr_ulid::NO_ENTROPY_SOURCE);
128///
129/// assert_eq!(Ulid::try_new(), None);
130/// ```
131pub const NO_ENTROPY_SOURCE: EntropySourceHandle = EntropySourceHandle {
132    inner: InnerHandle::NoOp,
133};
134
135struct Generator {
136    source: EntropySourceHandle,
137    #[cfg(feature = "rand")]
138    rng: Option<StdRng>,
139    last_ulid: u128,
140}
141
142impl Generator {
143    #[must_use]
144    fn generate(&mut self) -> Option<u128> {
145        let now = self.timestamp()?;
146        assert!(now < TIMESTAMP_MAX); // Yes, smaller, *not* smaller or equal!
147
148        let timestamp = u128::from(now) << RANDOM_BITS;
149        let last_timestamp = self.last_ulid & TIMESTAMP_MASK;
150
151        let ulid = if timestamp > last_timestamp {
152            // Ensure ULID is always non-zero, regardless of timestamp
153            let random = self.random(1..=RANDOM_GEN_MAX)?;
154            timestamp | random
155        } else {
156            self.last_ulid.checked_add(1)?
157        };
158
159        assert!(ulid > self.last_ulid);
160
161        self.last_ulid = ulid;
162
163        Some(ulid)
164    }
165
166    #[must_use]
167    fn timestamp(&mut self) -> Option<u64> {
168        let candidate = match &mut self.source.inner {
169            InnerHandle::NoOp => None,
170            #[cfg(feature = "rand")]
171            InnerHandle::Standard => {
172                let now = SystemTime::now();
173                let since_epoch = now.duration_since(SystemTime::UNIX_EPOCH).ok()?;
174                let millis = since_epoch.as_millis();
175                u64::try_from(millis).ok()
176            }
177            InnerHandle::Custom(source) => source.timestamp(),
178        }?;
179
180        // The last possible millisecond (TIMESTAMP_MAX) is reserved for our guarantees.
181        (candidate < TIMESTAMP_MAX).then_some(candidate)
182    }
183
184    #[must_use]
185    fn random(&mut self, range: RangeInclusive<u128>) -> Option<u128> {
186        let candidate = match &mut self.source.inner {
187            InnerHandle::NoOp => None,
188            #[cfg(feature = "rand")]
189            InnerHandle::Standard => {
190                let rng = self
191                    .rng
192                    .get_or_insert_with(|| StdRng::try_from_rng(&mut SysRng).unwrap()); // cspell::disable-line
193
194                // TODO: Once Rust 2027 arrives, `RangeInclusive` should be `Copy`, so remove `clone()` then.
195                Some(rng.random_range(range.clone()))
196            }
197            InnerHandle::Custom(source) => {
198                // TODO: dito
199                source.random(range.clone())
200            }
201        }?;
202
203        // A small step for the CPU, a huge step for resilience...
204        range.contains(&candidate).then_some(candidate)
205    }
206}
207
208static GENERATOR: Mutex<Generator> = {
209    #[cfg(feature = "rand")]
210    let generator = Generator {
211        source: STANDARD_ENTROPY_SOURCE,
212        rng: None,
213        last_ulid: 0,
214    };
215
216    #[cfg(not(feature = "rand"))]
217    let generator = Generator {
218        source: NO_ENTROPY_SOURCE,
219        last_ulid: 0,
220    };
221
222    Mutex::new(generator)
223};
224
225pub(crate) fn generate() -> Option<u128> {
226    let mut generator = GENERATOR.lock().ok()?;
227    generator.generate()
228}
229
230/// Sets the entropy source for generating ULIDs.
231///
232/// Sets a new entropy source and returns the previous set entropy source.
233///
234/// Normally you don't need to call this function unless the `rand` crate is disabled,
235/// or if you're using a custom entropy source.
236pub fn set_entropy_source(source: EntropySourceHandle) -> EntropySourceHandle {
237    let mut generator = GENERATOR.lock().unwrap_or_else(|poisoned| {
238        GENERATOR.clear_poison();
239        poisoned.into_inner()
240    });
241
242    std::mem::replace(&mut generator.source, source)
243}
244
245#[cfg(feature = "rand")]
246#[cfg(test)]
247mod tests {
248    use super::*;
249    use crate::Ulid;
250
251    fn manipulate_generator_last_ulid(last_id: u128) {
252        let mut generator = GENERATOR.lock().unwrap();
253        generator.last_ulid = last_id;
254    }
255
256    struct RestoreStandardSource;
257    impl Drop for RestoreStandardSource {
258        fn drop(&mut self) {
259            set_entropy_source(STANDARD_ENTROPY_SOURCE);
260            manipulate_generator_last_ulid(0);
261        }
262    }
263
264    struct FixedEntropySource {
265        timestamp: u64,
266        random: u128,
267    }
268    impl FixedEntropySource {
269        fn install(timestamp: u64, random: u128) -> RestoreStandardSource {
270            let source = Self { timestamp, random };
271            let handle = EntropySourceHandle::new(source);
272            set_entropy_source(handle);
273            RestoreStandardSource
274        }
275    }
276    impl EntropySource for FixedEntropySource {
277        fn timestamp(&mut self) -> Option<u64> {
278            Some(self.timestamp)
279        }
280        fn random(&mut self, _range: RangeInclusive<u128>) -> Option<u128> {
281            Some(self.random)
282        }
283    }
284
285    #[test]
286    fn test_generator_overflow() {
287        let _restore = FixedEntropySource::install(1, 1);
288
289        let u1 = Ulid::new();
290        assert_eq!(u1.timestamp(), 1);
291        assert_eq!(u1.randomness(), 1);
292
293        let u2 = Ulid::new();
294        assert_eq!(u2.timestamp(), 1);
295        assert_eq!(u2.randomness(), 2);
296
297        manipulate_generator_last_ulid((1 << RANDOM_BITS) | ((1 << RANDOM_BITS) - 2));
298
299        let u3 = Ulid::new();
300        assert_eq!(u3.timestamp(), 1);
301        assert_eq!(u3.randomness(), (1 << RANDOM_BITS) - 1);
302
303        let u4 = Ulid::new();
304        assert_eq!(u4.timestamp(), 2);
305        assert_eq!(u4.randomness(), 0);
306
307        let u5 = Ulid::new();
308        assert_eq!(u5.timestamp(), 2);
309        assert_eq!(u5.randomness(), 1);
310
311        manipulate_generator_last_ulid(u128::MAX - 1);
312
313        let u6 = Ulid::new();
314        assert_eq!(u6.to_u128(), u128::MAX);
315
316        assert!(Ulid::try_new().is_none());
317    }
318
319    #[test]
320    fn test_debug() {
321        struct TestSource;
322
323        impl EntropySource for TestSource {
324            fn timestamp(&mut self) -> Option<u64> {
325                None
326            }
327            fn random(&mut self, _range: RangeInclusive<u128>) -> Option<u128> {
328                None
329            }
330        }
331
332        let handle = EntropySourceHandle::new(TestSource);
333
334        assert_eq!(format!("{handle:?}"), "EntropySourceHandle { ... }");
335        assert_eq!(format!("{STANDARD_ENTROPY_SOURCE:?}"), "EntropySourceHandle { ... }");
336        assert_eq!(format!("{NO_ENTROPY_SOURCE:?}"), "EntropySourceHandle { ... }");
337    }
338}