bson/
oid.rs

1//! Module containing functionality related to BSON ObjectIds.
2//! For more information, see the documentation for the [`ObjectId`] type.
3
4#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
5use std::{convert::TryInto, time::SystemTime};
6use std::{
7    fmt,
8    str::FromStr,
9    sync::{
10        atomic::{AtomicUsize, Ordering},
11        LazyLock,
12    },
13};
14
15use rand::{random, rng, Rng};
16
17use crate::error::{Error, Result};
18
19const TIMESTAMP_SIZE: usize = 4;
20const PROCESS_ID_SIZE: usize = 5;
21const COUNTER_SIZE: usize = 3;
22
23const TIMESTAMP_OFFSET: usize = 0;
24const PROCESS_ID_OFFSET: usize = TIMESTAMP_OFFSET + TIMESTAMP_SIZE;
25const COUNTER_OFFSET: usize = PROCESS_ID_OFFSET + PROCESS_ID_SIZE;
26
27const MAX_U24: usize = 0xFF_FFFF;
28
29static OID_COUNTER: LazyLock<AtomicUsize> =
30    LazyLock::new(|| AtomicUsize::new(rng().random_range(0..=MAX_U24)));
31
32/// A wrapper around a raw 12-byte ObjectId.
33///
34/// ## `serde` integration
35/// When serialized to BSON via `serde`, this type produces a BSON ObjectId. In non-BSON formats, it
36/// will serialize to and deserialize from that format's equivalent of the [extended JSON representation](https://www.mongodb.com/docs/manual/reference/mongodb-extended-json/) of a BSON ObjectId.
37///
38/// [`ObjectId`]s can be deserialized from hex strings in all formats.
39///
40/// e.g.
41/// ```rust
42/// use serde::{Serialize, Deserialize};
43/// use bson::oid::ObjectId;
44///
45/// #[derive(Serialize, Deserialize)]
46/// struct Foo {
47///     oid: ObjectId,
48/// }
49///
50/// # fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
51/// let f = Foo { oid: ObjectId::new() };
52/// println!("bson: {}", bson::serialize_to_document(&f)?);
53/// println!("json: {}", serde_json::to_string(&f)?);
54/// # Ok(())
55/// # }
56/// ```
57/// Produces the following output:
58/// ```text
59/// bson: { "oid": ObjectId("63ceed18f71dda7d8cf21e8e") }
60/// json: {"oid":{"$oid":"63ceed18f71dda7d8cf21e8e"}}
61/// ```
62///
63/// ### `serde_helpers`
64/// The `bson` crate provides a number of useful helpers for serializing and deserializing
65/// various types to and from different formats. For example, to serialize an
66/// [`ObjectId`] as a hex string, you can use
67/// [`crate::serde_helpers::object_id::AsHexString`].
68/// Check out the [`crate::serde_helpers`] module documentation for a list of all of the helpers
69/// offered by the crate.
70///
71/// e.g.
72/// ```rust
73/// use serde::{Serialize, Deserialize};
74/// use serde_with::serde_as;
75/// use bson::oid::ObjectId;
76/// use bson::serde_helpers::object_id;
77///
78/// #[serde_as]
79/// #[derive(Serialize, Deserialize)]
80/// struct Foo {
81///     // Serializes as a BSON ObjectId or extJSON in non-BSON formats
82///     oid: ObjectId,
83///
84///     // Serializes as a hex string in all formats
85///     #[serde_as(as = "object_id::AsHexString")]
86///     oid_as_hex: ObjectId,
87/// }
88/// # fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
89/// let f = Foo { oid: ObjectId::new(), oid_as_hex: ObjectId::new() };
90/// println!("bson: {}", bson::serialize_to_document(&f)?);
91/// println!("json: {}", serde_json::to_string(&f)?);
92/// # Ok(())
93/// # }
94/// ```
95/// Produces the following output:
96/// ```text
97/// bson: { "oid": ObjectId("63ceeffd37518221cdc6cda2"), "oid_as_hex": "63ceeffd37518221cdc6cda3" }
98/// json: {"oid":{"$oid":"63ceeffd37518221cdc6cda2"},"oid_as_hex":"63ceeffd37518221cdc6cda3"}
99/// ```
100#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
101pub struct ObjectId {
102    id: [u8; 12],
103}
104
105impl Default for ObjectId {
106    fn default() -> Self {
107        Self::new()
108    }
109}
110
111impl FromStr for ObjectId {
112    type Err = Error;
113
114    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
115        Self::parse_str(s)
116    }
117}
118
119impl From<[u8; 12]> for ObjectId {
120    fn from(bytes: [u8; 12]) -> Self {
121        Self { id: bytes }
122    }
123}
124
125impl ObjectId {
126    /// Generates a new [`ObjectId`], represented in bytes.
127    /// See the [docs](http://www.mongodb.com/docs/manual/reference/object-id/)
128    /// for more information.
129    pub fn new() -> Self {
130        let timestamp = Self::gen_timestamp();
131        let process_id = Self::gen_process_id();
132        let counter = Self::gen_count();
133
134        Self::from_parts(timestamp, process_id, counter)
135    }
136
137    /// Constructs a new ObjectId wrapper around the raw byte representation.
138    pub const fn from_bytes(bytes: [u8; 12]) -> ObjectId {
139        ObjectId { id: bytes }
140    }
141
142    /// Construct an `ObjectId` from its parts.
143    /// See the [docs](http://www.mongodb.com/docs/manual/reference/object-id/)
144    /// for more information.
145    pub fn from_parts(seconds_since_epoch: u32, process_id: [u8; 5], counter: [u8; 3]) -> Self {
146        let mut bytes = [0; 12];
147
148        bytes[TIMESTAMP_OFFSET..(TIMESTAMP_OFFSET + TIMESTAMP_SIZE)]
149            .clone_from_slice(&u32::to_be_bytes(seconds_since_epoch));
150        bytes[PROCESS_ID_OFFSET..(PROCESS_ID_OFFSET + PROCESS_ID_SIZE)]
151            .clone_from_slice(&process_id);
152        bytes[COUNTER_OFFSET..(COUNTER_OFFSET + COUNTER_SIZE)].clone_from_slice(&counter);
153
154        Self::from_bytes(bytes)
155    }
156
157    /// Creates an ObjectID using a 12-byte (24-char) hexadecimal string.
158    pub fn parse_str(s: impl AsRef<str>) -> Result<ObjectId> {
159        let s = s.as_ref();
160
161        let bytes: Vec<u8> =
162            hex::decode(s.as_bytes()).map_err(|e| Error::from_hex_error(e, s.len()))?;
163        if bytes.len() != 12 {
164            Err(Error::oid_invalid_length(bytes.len()))
165        } else {
166            let mut byte_array: [u8; 12] = [0; 12];
167            byte_array[..].copy_from_slice(&bytes[..]);
168            Ok(ObjectId::from_bytes(byte_array))
169        }
170    }
171
172    /// Retrieves the timestamp from an [`ObjectId`].
173    pub fn timestamp(&self) -> crate::DateTime {
174        let mut buf = [0; 4];
175        buf.copy_from_slice(&self.id[0..4]);
176        let seconds_since_epoch = u32::from_be_bytes(buf);
177
178        // This doesn't overflow since u32::MAX * 1000 < i64::MAX
179        crate::DateTime::from_millis(seconds_since_epoch as i64 * 1000)
180    }
181
182    /// Returns the raw byte representation of an ObjectId.
183    pub const fn bytes(&self) -> [u8; 12] {
184        self.id
185    }
186
187    /// Convert this [`ObjectId`] to its hex string representation.
188    pub fn to_hex(self) -> String {
189        hex::encode(self.id)
190    }
191
192    /// Generates a new timestamp representing the current seconds since epoch.
193    fn gen_timestamp() -> u32 {
194        #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
195        let timestamp: u32 = (js_sys::Date::now() / 1000.0) as u32;
196        #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
197        let timestamp: u32 = SystemTime::now()
198            .duration_since(SystemTime::UNIX_EPOCH)
199            .expect("system clock is before 1970")
200            .as_secs()
201            .try_into()
202            .unwrap(); // will succeed until 2106 since timestamp is unsigned
203
204        timestamp
205    }
206
207    /// Generate a random 5-byte array.
208    fn gen_process_id() -> [u8; 5] {
209        static BUF: LazyLock<[u8; 5]> = LazyLock::new(random);
210
211        *BUF
212    }
213
214    /// Gets an incremental 3-byte count.
215    /// Represented in Big Endian.
216    fn gen_count() -> [u8; 3] {
217        let u_counter = OID_COUNTER.fetch_add(1, Ordering::SeqCst);
218
219        // Mod result instead of OID_COUNTER to prevent threading issues.
220        let u = u_counter % (MAX_U24 + 1);
221
222        // Convert usize to writable u64, then extract the first three bytes.
223        let u_int = u as u64;
224
225        let buf = u_int.to_be_bytes();
226        let buf_u24: [u8; 3] = [buf[5], buf[6], buf[7]];
227        buf_u24
228    }
229}
230
231impl fmt::Display for ObjectId {
232    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
233        f.write_str(&self.to_hex())
234    }
235}
236
237impl fmt::Debug for ObjectId {
238    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
239        f.debug_tuple("ObjectId").field(&self.to_hex()).finish()
240    }
241}
242
243#[cfg(test)]
244use crate::tests::LOCK;
245
246#[test]
247fn count_generated_is_big_endian() {
248    let _guard = LOCK.run_exclusively();
249    let start = 1_122_866;
250    OID_COUNTER.store(start, Ordering::SeqCst);
251
252    // Test count generates correct value 1122866
253    let count_bytes = ObjectId::gen_count();
254
255    let mut buf: [u8; 4] = [0; 4];
256    buf[1..=COUNTER_SIZE].clone_from_slice(&count_bytes[..COUNTER_SIZE]);
257
258    let count = u32::from_be_bytes(buf);
259    assert_eq!(start as u32, count);
260
261    // Test OID formats count correctly as big endian
262    let oid = ObjectId::new();
263
264    assert_eq!(0x11u8, oid.bytes()[COUNTER_OFFSET]);
265    assert_eq!(0x22u8, oid.bytes()[COUNTER_OFFSET + 1]);
266    assert_eq!(0x33u8, oid.bytes()[COUNTER_OFFSET + 2]);
267}
268
269#[test]
270fn test_counter_overflow_u24_max() {
271    let _guard = LOCK.run_exclusively();
272    let start = MAX_U24;
273    OID_COUNTER.store(start, Ordering::SeqCst);
274    let oid = ObjectId::new();
275    assert_eq!(0xFFu8, oid.bytes()[COUNTER_OFFSET]);
276    assert_eq!(0xFFu8, oid.bytes()[COUNTER_OFFSET + 1]);
277    assert_eq!(0xFFu8, oid.bytes()[COUNTER_OFFSET + 2]);
278    // Test counter overflows to 0 when set to MAX_24 + 1
279    let oid_new = ObjectId::new();
280    assert_eq!(0x00u8, oid_new.bytes()[COUNTER_OFFSET]);
281    assert_eq!(0x00u8, oid_new.bytes()[COUNTER_OFFSET + 1]);
282    assert_eq!(0x00u8, oid_new.bytes()[COUNTER_OFFSET + 2]);
283}
284
285#[test]
286fn test_counter_overflow_usize_max() {
287    let _guard = LOCK.run_exclusively();
288    let start = usize::MAX;
289    OID_COUNTER.store(start, Ordering::SeqCst);
290    // Test counter overflows to u24_max when set to usize_max
291    let oid = ObjectId::new();
292    assert_eq!(0xFFu8, oid.bytes()[COUNTER_OFFSET]);
293    assert_eq!(0xFFu8, oid.bytes()[COUNTER_OFFSET + 1]);
294    assert_eq!(0xFFu8, oid.bytes()[COUNTER_OFFSET + 2]);
295    // Test counter overflows to 0 when set to usize_max + 1
296    let oid_new = ObjectId::new();
297    assert_eq!(0x00u8, oid_new.bytes()[COUNTER_OFFSET]);
298    assert_eq!(0x00u8, oid_new.bytes()[COUNTER_OFFSET + 1]);
299    assert_eq!(0x00u8, oid_new.bytes()[COUNTER_OFFSET + 2]);
300}
301
302#[cfg(test)]
303mod test {
304    use time::macros::datetime;
305
306    #[test]
307    fn test_display() {
308        let id = super::ObjectId::parse_str("53e37d08776f724e42000000").unwrap();
309
310        assert_eq!(format!("{}", id), "53e37d08776f724e42000000")
311    }
312
313    #[test]
314    fn test_debug() {
315        let id = super::ObjectId::parse_str("53e37d08776f724e42000000").unwrap();
316
317        assert_eq!(
318            format!("{:?}", id),
319            "ObjectId(\"53e37d08776f724e42000000\")"
320        );
321        assert_eq!(
322            format!("{:#?}", id),
323            "ObjectId(\n    \"53e37d08776f724e42000000\",\n)"
324        );
325    }
326
327    #[test]
328    fn test_timestamp() {
329        let id = super::ObjectId::parse_str("000000000000000000000000").unwrap();
330        // "Jan 1st, 1970 00:00:00 UTC"
331        assert_eq!(datetime!(1970-01-01 0:00 UTC), id.timestamp().to_time_0_3());
332
333        let id = super::ObjectId::parse_str("7FFFFFFF0000000000000000").unwrap();
334        // "Jan 19th, 2038 03:14:07 UTC"
335        assert_eq!(
336            datetime!(2038-01-19 3:14:07 UTC),
337            id.timestamp().to_time_0_3()
338        );
339
340        let id = super::ObjectId::parse_str("800000000000000000000000").unwrap();
341        // "Jan 19th, 2038 03:14:08 UTC"
342        assert_eq!(
343            datetime!(2038-01-19 3:14:08 UTC),
344            id.timestamp().to_time_0_3()
345        );
346
347        let id = super::ObjectId::parse_str("FFFFFFFF0000000000000000").unwrap();
348        // "Feb 7th, 2106 06:28:15 UTC"
349        assert_eq!(
350            datetime!(2106-02-07 6:28:15 UTC),
351            id.timestamp().to_time_0_3()
352        );
353    }
354}