Skip to main content

bson_objectid/
lib.rs

1use std::sync::atomic::{AtomicUsize, Ordering};
2
3#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
4use std::{convert::TryInto, time::SystemTime};
5
6use hex::{self};
7use once_cell::sync::Lazy;
8use rand::{random, rng, Rng};
9
10const TIMESTAMP_SIZE: usize = 4;
11const PROCESS_ID_SIZE: usize = 5;
12const COUNTER_SIZE: usize = 3;
13
14const TIMESTAMP_OFFSET: usize = 0;
15const PROCESS_ID_OFFSET: usize = TIMESTAMP_OFFSET + TIMESTAMP_SIZE;
16const COUNTER_OFFSET: usize = PROCESS_ID_OFFSET + PROCESS_ID_SIZE;
17
18const MAX_U24: usize = 0xFF_FFFF;
19
20static OID_COUNTER: Lazy<AtomicUsize> =
21    Lazy::new(|| AtomicUsize::new(rng().random_range(0..=MAX_U24)));
22
23#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
24pub struct ObjectId {
25    id: [u8; 12],
26}
27
28impl ObjectId {
29    /// Generates a new [`ObjectId`], represented in bytes.
30    /// See the [docs](http://www.mongodb.com/docs/manual/reference/object-id/)
31    /// for more information.
32    pub fn new() -> Self {
33        let timestamp = Self::gen_timestamp();
34        let process_id = Self::gen_process_id();
35        let counter = Self::gen_count();
36
37        Self::from_parts(timestamp, process_id, counter)
38    }
39
40    /// Constructs a new ObjectId wrapper around the raw byte representation.
41    pub const fn from_bytes(bytes: [u8; 12]) -> ObjectId {
42        ObjectId { id: bytes }
43    }
44
45    /// Construct an `ObjectId` from its parts.
46    /// See the [docs](http://www.mongodb.com/docs/manual/reference/object-id/)
47    /// for more information.
48    pub fn from_parts(seconds_since_epoch: u32, process_id: [u8; 5], counter: [u8; 3]) -> Self {
49        let mut bytes = [0; 12];
50
51        bytes[TIMESTAMP_OFFSET..(TIMESTAMP_OFFSET + TIMESTAMP_SIZE)]
52            .clone_from_slice(&u32::to_be_bytes(seconds_since_epoch));
53        bytes[PROCESS_ID_OFFSET..(PROCESS_ID_OFFSET + PROCESS_ID_SIZE)]
54            .clone_from_slice(&process_id);
55        bytes[COUNTER_OFFSET..(COUNTER_OFFSET + COUNTER_SIZE)].clone_from_slice(&counter);
56
57        Self::from_bytes(bytes)
58    }
59
60    /// Convert this [`ObjectId`] to its hex string representation.
61    pub fn to_hex(self) -> String {
62        hex::encode(self.id)
63    }
64
65    /// Generates a new timestamp representing the current seconds since epoch.
66    fn gen_timestamp() -> u32 {
67        #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
68        let timestamp: u32 = (js_sys::Date::now() / 1000.0) as u32;
69        #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
70        let timestamp: u32 = SystemTime::now()
71            .duration_since(SystemTime::UNIX_EPOCH)
72            .expect("system clock is before 1970")
73            .as_secs()
74            .try_into()
75            .unwrap(); // will succeed until 2106 since timestamp is unsigned
76
77        timestamp
78    }
79
80    /// Generate a random 5-byte array.
81    fn gen_process_id() -> [u8; 5] {
82        static BUF: Lazy<[u8; 5]> = Lazy::new(random);
83
84        *BUF
85    }
86
87    /// Gets an incremental 3-byte count.
88    /// Represented in Big Endian.
89    fn gen_count() -> [u8; 3] {
90        let u_counter = OID_COUNTER.fetch_add(1, Ordering::SeqCst);
91
92        // Mod result instead of OID_COUNTER to prevent threading issues.
93        let u = u_counter % (MAX_U24 + 1);
94
95        // Convert usize to writable u64, then extract the first three bytes.
96        let u_int = u as u64;
97
98        let buf = u_int.to_be_bytes();
99        let buf_u24: [u8; 3] = [buf[5], buf[6], buf[7]];
100        buf_u24
101    }
102}
103
104pub fn oid() -> String {
105    ObjectId::new().to_hex()
106}