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}