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}