Preview of the proposed up-rust native-frame-model branch (up-rust b6b99c6d, up-spec f0e9b17) — not released documentation. branch · write-up

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