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