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 // fill upper 48 bits with timestamp
133 let mut msb = (timestamp_millis << 16).to_be_bytes();
134 // fill remaining bits with random bits
135 rand::thread_rng().fill_bytes(&mut msb[6..]);
136 // set version (7)
137 msb[6] = msb[6] & 0b00001111 | 0b01110000;
138
139 let mut lsb = [0u8; 8];
140 // fill lsb with random bits
141 rand::thread_rng().fill_bytes(&mut lsb);
142 // set variant (RFC4122)
143 lsb[0] = lsb[0] & 0b00111111 | 0b10000000;
144 Self::from_bytes_unchecked(msb, lsb)
145 }
146
147 /// Creates a new UUID that can be used for uProtocol messages.
148 ///
149 /// # Panics
150 ///
151 /// if the system clock is set to an instant before the UNIX Epoch.
152 ///
153 /// # Examples
154 ///
155 /// ```
156 /// use up_rust::UUID;
157 ///
158 /// let uuid = UUID::build();
159 /// assert!(uuid.is_uprotocol_uuid());
160 /// ```
161 // [impl->dsn~uuid-spec~1]
162 // [utest->dsn~uuid-spec~1]
163 pub fn build() -> UUID {
164 let duration_since_unix_epoch = SystemTime::UNIX_EPOCH
165 .elapsed()
166 .expect("current system time is set to a point in time before UNIX Epoch");
167 Self::build_for_timestamp(duration_since_unix_epoch)
168 }
169
170 /// Serializes this UUID to a hyphenated string as defined by
171 /// [RFC 4122, Section 3](https://www.rfc-editor.org/rfc/rfc4122.html#section-3)
172 /// using lower case characters.
173 ///
174 /// # Examples
175 ///
176 /// ```rust
177 /// use up_rust::UUID;
178 ///
179 /// // timestamp = 1, ver = 0b0111
180 /// let msb = 0x0000000000017000_u64;
181 /// // variant = 0b10, random = 0x0010101010101a1a
182 /// let lsb = 0x8010101010101a1a_u64;
183 /// let uuid = UUID { msb, lsb, ..Default::default() };
184 /// assert_eq!(uuid.to_hyphenated_string(), "00000000-0001-7000-8010-101010101a1a");
185 /// ```
186 pub fn to_hyphenated_string(&self) -> String {
187 let mut bytes = [0_u8; 16];
188 bytes[..8].clone_from_slice(self.msb.to_be_bytes().as_slice());
189 bytes[8..].clone_from_slice(self.lsb.to_be_bytes().as_slice());
190 let mut out_bytes = [0_u8; 36];
191 let out =
192 uuid_simd::format_hyphenated(&bytes, Out::from_mut(&mut out_bytes), AsciiCase::Lower);
193 String::from_utf8(out.to_vec()).unwrap()
194 }
195
196 /// Returns the point in time that this UUID has been created at.
197 ///
198 /// # Returns
199 ///
200 /// The number of milliseconds since UNIX EPOCH if this UUID is a uProtocol UUID,
201 /// or [`Option::None`] otherwise.
202 ///
203 /// # Examples
204 ///
205 /// ```rust
206 /// use up_rust::UUID;
207 ///
208 /// // timestamp = 0x018D548EA8E0 (Monday, 29 January 2024, 9:30:52 AM GMT)
209 /// // ver = 0b0111
210 /// let msb = 0x018D548EA8E07000u64;
211 /// // variant = 0b10
212 /// let lsb = 0x8000000000000000u64;
213 /// let creation_time = UUID { msb, lsb, ..Default::default() }.get_time();
214 /// assert_eq!(creation_time.unwrap(), 0x018D548EA8E0_u64);
215 ///
216 /// // timestamp = 1, (invalid) ver = 0b1100
217 /// let msb = 0x000000000001C000u64;
218 /// // variant = 0b10
219 /// let lsb = 0x8000000000000000u64;
220 /// let creation_time = UUID { msb, lsb, ..Default::default() }.get_time();
221 /// assert!(creation_time.is_none());
222 /// ```
223 // [impl->dsn~uuid-spec~1]
224 // [utest->dsn~uuid-spec~1]
225 pub fn get_time(&self) -> Option<u64> {
226 if self.is_uprotocol_uuid() {
227 // the timstamp is contained in the 48 most significant bits
228 Some(self.msb >> 16)
229 } else {
230 None
231 }
232 }
233
234 /// Checks if this is a valid uProtocol UUID.
235 ///
236 /// # Returns
237 ///
238 /// `true` if this UUID meets the formal requirements defined by the
239 /// [uProtocol spec](https://github.com/eclipse-uprotocol/uprotocol-spec).
240 ///
241 /// # Examples
242 ///
243 /// ```rust
244 /// use up_rust::UUID;
245 ///
246 /// // timestamp = 1, ver = 0b0111
247 /// let msb = 0x0000000000017000u64;
248 /// // variant = 0b10
249 /// let lsb = 0x8000000000000000u64;
250 /// assert!(UUID { msb, lsb, ..Default::default() }.is_uprotocol_uuid());
251 ///
252 /// // timestamp = 1, (invalid) ver = 0b1100
253 /// let msb = 0x000000000001C000u64;
254 /// // variant = 0b10
255 /// let lsb = 0x8000000000000000u64;
256 /// assert!(!UUID { msb, lsb, ..Default::default() }.is_uprotocol_uuid());
257 ///
258 /// // timestamp = 1, ver = 0b0111
259 /// let msb = 0x0000000000017000u64;
260 /// // (invalid) variant = 0b01
261 /// let lsb = 0x4000000000000000u64;
262 /// assert!(!UUID { msb, lsb, ..Default::default() }.is_uprotocol_uuid());
263 /// ```
264 // [impl->dsn~uuid-spec~1]
265 // [utest->dsn~uuid-spec~1]
266 pub fn is_uprotocol_uuid(&self) -> bool {
267 is_correct_version(self.msb) && is_correct_variant(self.lsb)
268 }
269}
270
271impl Eq for UUID {}
272
273impl Hash for UUID {
274 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
275 let bytes = (self.msb, self.lsb);
276 bytes.hash(state)
277 }
278}
279
280impl From<UUID> for String {
281 fn from(value: UUID) -> Self {
282 Self::from(&value)
283 }
284}
285
286impl From<&UUID> for String {
287 fn from(value: &UUID) -> Self {
288 value.to_hyphenated_string()
289 }
290}
291
292impl FromStr for UUID {
293 type Err = UuidConversionError;
294
295 /// Parses a string into a UUID.
296 ///
297 /// # Returns
298 ///
299 /// a uProtocol [`UUID`] based on the bytes encoded in the string.
300 ///
301 /// # Errors
302 ///
303 /// Returns an error
304 /// * if the given string does not represent a UUID as defined by
305 /// [RFC 4122, Section 3](https://www.rfc-editor.org/rfc/rfc4122.html#section-3), or
306 /// * if the bytes encoded in the string contain an invalid version and/or variant identifier.
307 ///
308 /// # Examples
309 ///
310 /// ```rust
311 /// use up_rust::UUID;
312 ///
313 /// // parsing a valid uProtocol UUID succeeds
314 /// let parsing_attempt = "00000000-0001-7000-8010-101010101a1A".parse::<UUID>();
315 /// assert!(parsing_attempt.is_ok());
316 /// let uuid = parsing_attempt.unwrap();
317 /// assert!(uuid.is_uprotocol_uuid());
318 /// assert_eq!(uuid.msb, 0x0000000000017000_u64);
319 /// assert_eq!(uuid.lsb, 0x8010101010101a1a_u64);
320 ///
321 /// // parsing an invalid UUID fails
322 /// assert!("a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8"
323 /// .parse::<UUID>()
324 /// .is_err());
325 ///
326 /// // parsing a string that is not a UUID fails
327 /// assert!("this-is-not-a-UUID"
328 /// .parse::<UUID>()
329 /// .is_err());
330 /// ```
331 fn from_str(uuid_str: &str) -> Result<Self, Self::Err> {
332 let mut uuid = [0u8; 16];
333 uuid_simd::parse_hyphenated(uuid_str.as_bytes(), Out::from_mut(&mut uuid))
334 .map_err(|err| UuidConversionError::new(err.to_string()))
335 .and_then(|bytes| UUID::from_bytes(bytes))
336 }
337}
338
339#[cfg(test)]
340mod tests {
341
342 use protobuf::Message;
343
344 use super::*;
345
346 // [utest->dsn~uuid-spec~1]
347 #[test]
348 fn test_from_u64_pair() {
349 // timestamp = 1, ver = 0b0111
350 let msb = 0x0000000000017000_u64;
351 // variant = 0b10
352 let lsb = 0x8000000000000000_u64;
353 let conversion_attempt = UUID::from_u64_pair(msb, lsb);
354 assert!(conversion_attempt.is_ok());
355 let uuid = conversion_attempt.unwrap();
356 assert!(uuid.is_uprotocol_uuid());
357 assert_eq!(uuid.get_time(), Some(0x1_u64));
358
359 // timestamp = 1, (invalid) ver = 0b0000
360 let msb = 0x0000000000010000_u64;
361 // variant= 0b10
362 let lsb = 0x80000000000000ab_u64;
363 assert!(UUID::from_u64_pair(msb, lsb).is_err());
364
365 // timestamp = 1, ver = 0b0111
366 let msb = 0x0000000000017000_u64;
367 // (invalid) variant= 0b00
368 let lsb = 0x00000000000000ab_u64;
369 assert!(UUID::from_u64_pair(msb, lsb).is_err());
370 }
371
372 // [utest->dsn~uuid-spec~1]
373 #[test]
374 fn test_from_bytes() {
375 // timestamp = 1, ver = 0b0111, variant = 0b10
376 let bytes: [u8; 16] = [
377 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x70, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
378 0x00, 0x00,
379 ];
380 let conversion_attempt = UUID::from_bytes(&bytes);
381 assert!(conversion_attempt.is_ok());
382 let uuid = conversion_attempt.unwrap();
383 assert!(uuid.is_uprotocol_uuid());
384 assert_eq!(uuid.get_time(), Some(0x1_u64));
385 }
386
387 #[test]
388 fn test_into_string() {
389 // timestamp = 1, ver = 0b0111
390 let msb = 0x0000000000017000_u64;
391 // variant = 0b10, random = 0x0010101010101a1a
392 let lsb = 0x8010101010101a1a_u64;
393 let uuid = UUID {
394 msb,
395 lsb,
396 ..Default::default()
397 };
398
399 assert_eq!(String::from(&uuid), "00000000-0001-7000-8010-101010101a1a");
400 assert_eq!(String::from(uuid), "00000000-0001-7000-8010-101010101a1a");
401 }
402
403 // [utest->req~uuid-proto~1]
404 #[test]
405 fn test_protobuf_serialization() {
406 let uuid = UUID::build();
407 let bytes = uuid.write_to_bytes().unwrap();
408 let deserialized_uuid = UUID::parse_from_bytes(bytes.as_slice()).unwrap();
409 assert_eq!(uuid, deserialized_uuid);
410 }
411}