up_rust/
uuid.rs

1/********************************************************************************
2 * Copyright (c) 2023 Contributors to the Eclipse Foundation
3 *
4 * See the NOTICE file(s) distributed with this work for additional
5 * information regarding copyright ownership.
6 *
7 * This program and the accompanying materials are made available under the
8 * terms of the Apache License Version 2.0 which is available at
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * SPDX-License-Identifier: Apache-2.0
12 ********************************************************************************/
13
14use rand::RngCore;
15use std::time::{Duration, SystemTime};
16use std::{hash::Hash, str::FromStr};
17
18pub use crate::up_core_api::uuid::UUID;
19
20use uuid_simd::{AsciiCase, Out};
21
22const BITMASK_VERSION: u64 = 0b1111 << 12;
23const VERSION_7: u64 = 0b0111 << 12;
24const BITMASK_VARIANT: u64 = 0b11 << 62;
25const VARIANT_RFC4122: u64 = 0b10 << 62;
26
27fn is_correct_version(msb: u64) -> bool {
28    msb & BITMASK_VERSION == VERSION_7
29}
30
31fn is_correct_variant(lsb: u64) -> bool {
32    lsb & BITMASK_VARIANT == VARIANT_RFC4122
33}
34
35#[derive(Debug)]
36pub struct UuidConversionError {
37    message: String,
38}
39
40impl UuidConversionError {
41    pub fn new<T: Into<String>>(message: T) -> UuidConversionError {
42        UuidConversionError {
43            message: message.into(),
44        }
45    }
46}
47
48impl std::fmt::Display for UuidConversionError {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        write!(f, "Error converting Uuid: {}", self.message)
51    }
52}
53
54impl std::error::Error for UuidConversionError {}
55
56impl UUID {
57    /// Creates a new UUID from a byte array.
58    ///
59    /// # Arguments
60    ///
61    /// `bytes` - the byte array
62    ///
63    /// # Returns
64    ///
65    /// a uProtocol [`UUID`] with the given timestamp and random values.
66    ///
67    /// # Errors
68    ///
69    /// Returns an error if the given bytes contain an invalid version and/or variant identifier.
70    pub(crate) fn from_bytes(bytes: &[u8; 16]) -> Result<Self, UuidConversionError> {
71        let mut msb = [0_u8; 8];
72        let mut lsb = [0_u8; 8];
73        msb.copy_from_slice(&bytes[..8]);
74        lsb.copy_from_slice(&bytes[8..]);
75        Self::from_u64_pair(u64::from_be_bytes(msb), u64::from_be_bytes(lsb))
76    }
77
78    /// Creates a new UUID from a high/low value pair.
79    ///
80    /// NOTE: This function does *not* check if the given bytes represent a [valid uProtocol UUID](Self::is_uprotocol_uuid).
81    ///       It should therefore only be used in cases where the bytes passed in are known to be valid.
82    ///
83    /// # Arguments
84    ///
85    /// `msb` - the most significant 8 bytes
86    /// `lsb` - the least significant 8 bytes
87    ///
88    /// # Returns
89    ///
90    /// a uProtocol [`UUID`] with the given timestamp and random values.
91    pub(crate) fn from_bytes_unchecked(msb: [u8; 8], lsb: [u8; 8]) -> Self {
92        UUID {
93            msb: u64::from_be_bytes(msb),
94            lsb: u64::from_be_bytes(lsb),
95            ..Default::default()
96        }
97    }
98
99    /// Creates a new UUID from a high/low value pair.
100    ///
101    /// # Arguments
102    ///
103    /// `msb` - the most significant 8 bytes
104    /// `lsb` - the least significant 8 bytes
105    ///
106    /// # Returns
107    ///
108    /// a uProtocol [`UUID`] with the given timestamp and random values.
109    ///
110    /// # Errors
111    ///
112    /// Returns an error if the given bytes contain an invalid version and/or variant identifier.
113    // [impl->dsn~uuid-spec~1]
114    pub(crate) fn from_u64_pair(msb: u64, lsb: u64) -> Result<Self, UuidConversionError> {
115        if !is_correct_version(msb) {
116            return Err(UuidConversionError::new("not a v7 UUID"));
117        }
118        if !is_correct_variant(lsb) {
119            return Err(UuidConversionError::new("not an RFC4122 UUID"));
120        }
121        Ok(UUID {
122            msb,
123            lsb,
124            ..Default::default()
125        })
126    }
127
128    // [impl->dsn~uuid-spec~1]
129    pub(crate) fn build_for_timestamp(duration_since_unix_epoch: Duration) -> UUID {
130        let timestamp_millis = u64::try_from(duration_since_unix_epoch.as_millis())
131            .expect("system time is set to a time too far in the future");
132        Self::build_for_timestamp_millis(timestamp_millis)
133    }
134
135    // [impl->dsn~uuid-spec~1]
136    pub(crate) fn build_for_timestamp_millis(timestamp_millis: u64) -> UUID {
137        // fill upper 48 bits with timestamp
138        let mut msb = (timestamp_millis << 16).to_be_bytes();
139        // fill remaining bits with random bits
140        rand::rng().fill_bytes(&mut msb[6..]);
141        // set version (7)
142        msb[6] = msb[6] & 0b00001111 | 0b01110000;
143
144        let mut lsb = [0u8; 8];
145        // fill lsb with random bits
146        rand::rng().fill_bytes(&mut lsb);
147        // set variant (RFC4122)
148        lsb[0] = lsb[0] & 0b00111111 | 0b10000000;
149        Self::from_bytes_unchecked(msb, lsb)
150    }
151
152    /// Creates a new UUID that can be used for uProtocol messages.
153    ///
154    /// # Panics
155    ///
156    /// if the system clock is set to an instant before the UNIX Epoch.
157    ///
158    /// # Examples
159    ///
160    /// ```
161    /// use up_rust::UUID;
162    ///
163    /// let uuid = UUID::build();
164    /// assert!(uuid.is_uprotocol_uuid());
165    /// ```
166    // [impl->dsn~uuid-spec~1]
167    // [utest->dsn~uuid-spec~1]
168    pub fn build() -> UUID {
169        let duration_since_unix_epoch = SystemTime::UNIX_EPOCH
170            .elapsed()
171            .expect("current system time is set to a point in time before UNIX Epoch");
172        Self::build_for_timestamp(duration_since_unix_epoch)
173    }
174
175    /// Serializes this UUID to a hyphenated string as defined by
176    /// [RFC 4122, Section 3](https://www.rfc-editor.org/rfc/rfc4122.html#section-3)
177    /// using lower case characters.
178    ///
179    /// # Examples
180    ///
181    /// ```rust
182    /// use up_rust::UUID;
183    ///
184    /// // timestamp = 1, ver = 0b0111
185    /// let msb = 0x0000000000017000_u64;
186    /// // variant = 0b10, random = 0x0010101010101a1a
187    /// let lsb = 0x8010101010101a1a_u64;
188    /// let uuid = UUID { msb, lsb, ..Default::default() };
189    /// assert_eq!(uuid.to_hyphenated_string(), "00000000-0001-7000-8010-101010101a1a");
190    /// ```
191    // [impl->req~uuid-hex-and-dash~1]
192    pub fn to_hyphenated_string(&self) -> String {
193        let mut bytes = [0_u8; 16];
194        bytes[..8].clone_from_slice(self.msb.to_be_bytes().as_slice());
195        bytes[8..].clone_from_slice(self.lsb.to_be_bytes().as_slice());
196        let mut out_bytes = [0_u8; 36];
197        let out =
198            uuid_simd::format_hyphenated(&bytes, Out::from_mut(&mut out_bytes), AsciiCase::Lower);
199        String::from_utf8(out.to_vec()).unwrap()
200    }
201
202    /// Returns the point in time that this UUID has been created at.
203    ///
204    /// # Returns
205    ///
206    /// The number of milliseconds since UNIX EPOCH if this UUID is a uProtocol UUID,
207    /// or [`Option::None`] otherwise.
208    ///
209    /// # Examples
210    ///
211    /// ```rust
212    /// use up_rust::UUID;
213    ///
214    /// // timestamp = 0x018D548EA8E0 (Monday, 29 January 2024, 9:30:52 AM GMT)
215    /// // ver = 0b0111
216    /// let msb = 0x018D548EA8E07000u64;
217    /// // variant = 0b10
218    /// let lsb = 0x8000000000000000u64;
219    /// let creation_time = UUID { msb, lsb, ..Default::default() }.get_time();
220    /// assert_eq!(creation_time.unwrap(), 0x018D548EA8E0_u64);
221    ///
222    /// // timestamp = 1, (invalid) ver = 0b1100
223    /// let msb = 0x000000000001C000u64;
224    /// // variant = 0b10
225    /// let lsb = 0x8000000000000000u64;
226    /// let creation_time = UUID { msb, lsb, ..Default::default() }.get_time();
227    /// assert!(creation_time.is_none());
228    /// ```
229    // [impl->dsn~uuid-spec~1]
230    // [utest->dsn~uuid-spec~1]
231    pub fn get_time(&self) -> Option<u64> {
232        if self.is_uprotocol_uuid() {
233            // the timestamp is contained in the 48 most significant bits
234            Some(self.msb >> 16)
235        } else {
236            None
237        }
238    }
239
240    /// Checks if this is a valid uProtocol UUID.
241    ///
242    /// # Returns
243    ///
244    /// `true` if this UUID meets the formal requirements defined by the
245    /// [uProtocol spec](https://github.com/eclipse-uprotocol/uprotocol-spec).
246    ///
247    /// # Examples
248    ///
249    /// ```rust
250    /// use up_rust::UUID;
251    ///
252    /// // timestamp = 1, ver = 0b0111
253    /// let msb = 0x0000000000017000u64;
254    /// // variant = 0b10
255    /// let lsb = 0x8000000000000000u64;
256    /// assert!(UUID { msb, lsb, ..Default::default() }.is_uprotocol_uuid());
257    ///
258    /// // timestamp = 1, (invalid) ver = 0b1100
259    /// let msb = 0x000000000001C000u64;
260    /// // variant = 0b10
261    /// let lsb = 0x8000000000000000u64;
262    /// assert!(!UUID { msb, lsb, ..Default::default() }.is_uprotocol_uuid());
263    ///
264    /// // timestamp = 1, ver = 0b0111
265    /// let msb = 0x0000000000017000u64;
266    /// // (invalid) variant = 0b01
267    /// let lsb = 0x4000000000000000u64;
268    /// assert!(!UUID { msb, lsb, ..Default::default() }.is_uprotocol_uuid());
269    /// ```
270    // [impl->dsn~uuid-spec~1]
271    // [utest->dsn~uuid-spec~1]
272    pub fn is_uprotocol_uuid(&self) -> bool {
273        is_correct_version(self.msb) && is_correct_variant(self.lsb)
274    }
275}
276
277impl Eq for UUID {}
278
279impl Hash for UUID {
280    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
281        let bytes = (self.msb, self.lsb);
282        bytes.hash(state)
283    }
284}
285
286impl From<UUID> for String {
287    fn from(value: UUID) -> Self {
288        Self::from(&value)
289    }
290}
291
292impl From<&UUID> for String {
293    fn from(value: &UUID) -> Self {
294        value.to_hyphenated_string()
295    }
296}
297
298impl FromStr for UUID {
299    type Err = UuidConversionError;
300
301    /// Parses a string into a UUID.
302    ///
303    /// # Returns
304    ///
305    /// a uProtocol [`UUID`] based on the bytes encoded in the string.
306    ///
307    /// # Errors
308    ///
309    /// Returns an error
310    /// * if the given string does not represent a UUID as defined by
311    ///   [RFC 4122, Section 3](https://www.rfc-editor.org/rfc/rfc4122.html#section-3), or
312    /// * if the bytes encoded in the string contain an invalid version and/or variant identifier.
313    ///
314    /// # Examples
315    ///
316    /// ```rust
317    /// use up_rust::UUID;
318    ///
319    /// // parsing a valid uProtocol UUID succeeds
320    /// let parsing_attempt = "00000000-0001-7000-8010-101010101a1A".parse::<UUID>();
321    /// assert!(parsing_attempt.is_ok());
322    /// let uuid = parsing_attempt.unwrap();
323    /// assert!(uuid.is_uprotocol_uuid());
324    /// assert_eq!(uuid.msb, 0x0000000000017000_u64);
325    /// assert_eq!(uuid.lsb, 0x8010101010101a1a_u64);
326    ///
327    /// // parsing an invalid UUID fails
328    /// assert!("a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8"
329    ///     .parse::<UUID>()
330    ///     .is_err());
331    ///
332    /// // parsing a string that is not a UUID fails
333    /// assert!("this-is-not-a-UUID"
334    ///     .parse::<UUID>()
335    ///     .is_err());
336    /// ```
337    // [impl->req~uuid-hex-and-dash~1]
338    fn from_str(uuid_str: &str) -> Result<Self, Self::Err> {
339        let mut uuid = [0u8; 16];
340        uuid_simd::parse_hyphenated(uuid_str.as_bytes(), Out::from_mut(&mut uuid))
341            .map_err(|err| UuidConversionError::new(err.to_string()))
342            .and_then(|bytes| UUID::from_bytes(bytes))
343    }
344}
345
346#[cfg(test)]
347mod tests {
348
349    use super::*;
350
351    // [utest->dsn~uuid-spec~1]
352    // [utest->req~uuid-type~1]
353    #[test]
354    fn test_from_u64_pair() {
355        // timestamp = 1, ver = 0b0111
356        let msb = 0x0000000000017000_u64;
357        // variant = 0b10
358        let lsb = 0x8000000000000000_u64;
359        let conversion_attempt = UUID::from_u64_pair(msb, lsb);
360        assert!(conversion_attempt.is_ok_and(|uuid| {
361            uuid.is_uprotocol_uuid()
362                && uuid.get_time() == Some(0x1_u64)
363                && uuid.msb == msb
364                && uuid.lsb == lsb
365        }));
366
367        // timestamp = 1, (invalid) ver = 0b0000
368        let msb = 0x0000000000010000_u64;
369        // variant= 0b10
370        let lsb = 0x80000000000000ab_u64;
371        assert!(UUID::from_u64_pair(msb, lsb).is_err());
372
373        // timestamp = 1, ver = 0b0111
374        let msb = 0x0000000000017000_u64;
375        // (invalid) variant= 0b00
376        let lsb = 0x00000000000000ab_u64;
377        assert!(UUID::from_u64_pair(msb, lsb).is_err());
378    }
379
380    // [utest->dsn~uuid-spec~1]
381    #[test]
382    fn test_from_bytes() {
383        // timestamp = 1, ver = 0b0111, variant = 0b10
384        let bytes: [u8; 16] = [
385            0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x70, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
386            0x00, 0x00,
387        ];
388        let conversion_attempt = UUID::from_bytes(&bytes);
389        assert!(conversion_attempt.is_ok());
390        let uuid = conversion_attempt.unwrap();
391        assert!(uuid.is_uprotocol_uuid());
392        assert_eq!(uuid.get_time(), Some(0x1_u64));
393    }
394
395    #[test]
396    // [utest->req~uuid-hex-and-dash~1]
397    fn test_into_string() {
398        // timestamp = 1, ver = 0b0111
399        let msb = 0x0000000000017000_u64;
400        // variant = 0b10, random = 0x0010101010101a1a
401        let lsb = 0x8010101010101a1a_u64;
402        let uuid = UUID {
403            msb,
404            lsb,
405            ..Default::default()
406        };
407
408        assert_eq!(String::from(&uuid), "00000000-0001-7000-8010-101010101a1a");
409        assert_eq!(String::from(uuid), "00000000-0001-7000-8010-101010101a1a");
410    }
411}