guid_create/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2//! # guid-create
3//!
4//! Rust helper for randomly creating GUIDs.
5//!
6//! ```rust,ignore
7//! extern crate guid_create;
8//! use guid_create::GUID;
9//!
10//! // Create GUIDs
11//! let guid = GUID::rand();
12//! let guid = GUID::parse("87935CDE-7094-4C2B-A0F4-DD7D512DD261").unwrap();
13//! let guid = GUID::build_from_components(0x87935CDE, 0x7094, 0x4C2B, &[0xA0, 0xF4, 0xDD, 0x7D, 0x51, 0x2D, 0xD2, 0x61], );
14//! let guid = GUID::build_from_slice(&[ 0x87, 0x93, 0x5C, 0xDE, 0x70, 0x94, 0x4C, 0x2B, 0xA0, 0xF4, 0xDD, 0x7D, 0x51, 0x2D, 0xD2, 0x61,]);
15//!
16//! // View GUIDs
17//! guid.to_string();  // 87935CDE-7094-4C2B-A0F4-DD7D512DD261
18//!
19//! // Check GUIDs
20//! guid.data1();
21//! guid.data2();
22//! guid.data3();
23//! guid.data4();
24//! ```
25#[cfg(test)]
26extern crate quickcheck;
27#[cfg(test)]
28#[macro_use(quickcheck)]
29extern crate quickcheck_macros;
30
31use core::{convert::TryInto, fmt};
32
33#[cfg(windows)]
34use winapi::shared::guiddef::GUID as WinGuid;
35
36/// Parsing error type.
37#[derive(Debug)]
38pub struct ParseError;
39
40impl fmt::Display for ParseError {
41    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
42        write!(
43            f,
44            "Malformed GUID, expecting XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
45        )
46    }
47}
48impl core::error::Error for ParseError {}
49
50/// A GUID backed by 16 byte array.
51#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, Hash)]
52#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
53#[repr(transparent)]
54pub struct GUID {
55    data: [u8; 16],
56}
57
58impl From<CGuid> for GUID {
59    fn from(item: CGuid) -> Self {
60        GUID::build_from_components(item.a, item.b, item.c, &item.d)
61    }
62}
63
64impl From<GUID> for CGuid {
65    fn from(item: GUID) -> Self {
66        CGuid {
67            a: item.data1(),
68            b: item.data2(),
69            c: item.data3(),
70            d: item.data4(),
71        }
72    }
73}
74
75impl fmt::Display for CGuid {
76    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
77        let guid: GUID = (*self).into();
78        guid.fmt(f)
79    }
80}
81
82#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, Hash)]
83#[repr(C)]
84pub struct CGuid {
85    /// The low field of the timestamp.
86    a: u32,
87    /// The middle field of the timestamp.
88    b: u16,
89    /// The high field of the timestamp multiplexed with the version number.
90    c: u16,
91    /// Contains, in this order:
92    /// - The high field of the clock sequence multiplexed with the variant.
93    /// - The low field of the clock sequence.
94    /// - The spatially unique node identifier.
95    d: [u8; 8],
96}
97
98impl GUID {
99    /// Construct a `GUID` from components.
100    ///
101    /// ``` rust
102    /// let guid = guid_create::GUID::build_from_components(
103    ///     0x87935CDE,
104    ///     0x7094,
105    ///     0x4C2B,
106    ///     &[ 0xA0, 0xF4, 0xDD, 0x7D, 0x51, 0x2D, 0xD2, 0x61 ]
107    /// );
108    ///
109    /// assert_eq!(guid.data1(), 0x87935CDE);
110    /// assert_eq!(guid.data2(), 0x7094);
111    /// assert_eq!(guid.data3(), 0x4C2B);
112    /// assert_eq!(guid.data4(), [ 0xA0, 0xF4, 0xDD, 0x7D, 0x51, 0x2D, 0xD2, 0x61 ]);
113    /// assert_eq!(guid.to_string(), "87935CDE-7094-4C2B-A0F4-DD7D512DD261");
114    /// ```
115    pub const fn build_from_components(d1: u32, d2: u16, d3: u16, d4: &[u8; 8]) -> Self {
116        let d1 = d1.to_be_bytes();
117        let d2 = d2.to_be_bytes();
118        let d3 = d3.to_be_bytes();
119        #[rustfmt::skip]
120        let data = [
121            d1[0], d1[1], d1[2], d1[3],
122            d2[0], d2[1],
123            d3[0], d3[1],
124            d4[0], d4[1], d4[2], d4[3], d4[4], d4[5], d4[6], d4[7],
125        ];
126
127        GUID { data }
128    }
129
130    /// Construct a `GUID` from 16 bytes.
131    ///
132    /// ``` rust
133    /// let guid = guid_create::GUID::build_from_slice(&[
134    ///     0x87, 0x93, 0x5C, 0xDE, 0x70, 0x94, 0x4C, 0x2B, 0xA0, 0xF4, 0xDD, 0x7D, 0x51, 0x2D,
135    ///     0xD2, 0x61,
136    /// ]);
137    ///
138    /// assert_eq!(guid.data1(), 0x87935CDE);
139    /// assert_eq!(guid.data2(), 0x7094);
140    /// assert_eq!(guid.data3(), 0x4C2B);
141    /// assert_eq!(
142    ///     guid.data4(),
143    ///     [0xA0, 0xF4, 0xDD, 0x7D, 0x51, 0x2D, 0xD2, 0x61]
144    /// );
145    /// assert_eq!(guid.to_string(), "87935CDE-7094-4C2B-A0F4-DD7D512DD261");
146    /// ```
147    pub const fn build_from_slice(data: &[u8; 16]) -> Self {
148        GUID { data: *data }
149    }
150
151    /// Construct a `GUID` from a string.
152    ///
153    /// ``` rust
154    /// let guid = guid_create::GUID::parse("87935CDE-7094-4C2B-A0F4-DD7D512DD261").unwrap();
155    ///
156    /// assert_eq!(guid.data1(), 0x87935CDE);
157    /// assert_eq!(guid.data2(), 0x7094);
158    /// assert_eq!(guid.data3(), 0x4C2B);
159    /// assert_eq!(guid.data4(), [ 0xA0, 0xF4, 0xDD, 0x7D, 0x51, 0x2D, 0xD2, 0x61 ]);
160    /// assert_eq!(guid.to_string(), "87935CDE-7094-4C2B-A0F4-DD7D512DD261");
161    /// ```
162    pub fn parse(guid: &str) -> Result<Self, ParseError> {
163        fn n(ch: u8) -> Result<u8, ParseError> {
164            match ch {
165                b'0'..=b'9' => Ok(ch - 48),
166                b'A'..=b'F' => Ok(ch - 55),
167                b'a'..=b'f' => Ok(ch - 87),
168                _ => Err(ParseError),
169            }
170        }
171        fn hexbyte(s: &[u8]) -> Result<(u8, &[u8]), ParseError> {
172            match s {
173                [a, b, tail @ ..] => n(*a)
174                    .and_then(|a| n(*b).map(|b| a * 16 + b))
175                    .map(|x| (x, tail)),
176                _ => Err(ParseError),
177            }
178        }
179        fn strip_dash(s: &[u8]) -> Result<&[u8], ParseError> {
180            match s {
181                [b'-', tail @ ..] => Ok(tail),
182                _ => Err(ParseError),
183            }
184        }
185
186        let mut data = [0u8; 16];
187
188        let mut s = guid.as_bytes();
189
190        fn fill<'a>(buf: &mut [u8], mut s: &'a [u8]) -> Result<&'a [u8], ParseError> {
191            for l in buf {
192                let (d, s_) = hexbyte(s)?;
193                *l = d;
194                s = s_;
195            }
196            Ok(s)
197        }
198
199        // first four bytes
200        s = fill(&mut data[..4], s)?;
201        s = strip_dash(s)?;
202
203        // second two bytes
204        s = fill(&mut data[4..6], s)?;
205        s = strip_dash(s)?;
206
207        // third two bytes
208        s = fill(&mut data[6..8], s)?;
209        s = strip_dash(s)?;
210
211        // fourth two bytes
212        s = fill(&mut data[8..10], s)?;
213        s = strip_dash(s)?;
214
215        // trailing bytes
216        s = fill(&mut data[10..], s)?;
217
218        // should be empty!
219        if s.is_empty() {
220            Ok(Self { data })
221        } else {
222            Err(ParseError)
223        }
224    }
225
226    /// Generates a new GUID with 16 random bytes.
227    #[cfg(feature = "rand")]
228    pub fn rand() -> GUID {
229        GUID {
230            data: rand::random(),
231        }
232    }
233
234    /// The first four bytes.
235    ///
236    /// ``` rust
237    /// extern crate guid_create;
238    /// let guid = guid_create::GUID::build_from_components(
239    ///     500,
240    ///     600,
241    ///     700,
242    ///     &[ 0xA0, 0xF4, 0xDD, 0x7D, 0x51, 0x2D, 0xD2, 0x61 ]
243    /// );
244    ///
245    /// assert_eq!(guid.data1(), 500);
246    /// ```
247    pub fn data1(&self) -> u32 {
248        u32::from_be_bytes(
249            self.data[0..4]
250                .try_into()
251                .expect("slice with incorrect length"),
252        )
253    }
254
255    /// Bytes 5 and 6.
256    ///
257    /// ``` rust
258    /// extern crate guid_create;
259    /// let guid = guid_create::GUID::build_from_components(
260    ///     500,
261    ///     600,
262    ///     700,
263    ///     &[ 0xA0, 0xF4, 0xDD, 0x7D, 0x51, 0x2D, 0xD2, 0x61 ]
264    /// );
265    ///
266    /// assert_eq!(guid.data2(), 600);
267    /// ```
268    pub fn data2(&self) -> u16 {
269        u16::from_be_bytes(
270            self.data[4..6]
271                .try_into()
272                .expect("slice with incorrect length"),
273        )
274    }
275
276    /// Bytes 7 and 8.
277    ///
278    /// ``` rust
279    /// extern crate guid_create;
280    /// let guid = guid_create::GUID::build_from_components(
281    ///     500,
282    ///     600,
283    ///     700,
284    ///     &[ 0xA0, 0xF4, 0xDD, 0x7D, 0x51, 0x2D, 0xD2, 0x61 ]
285    /// );
286    ///
287    /// assert_eq!(guid.data3(), 700);
288    /// ```
289    pub fn data3(&self) -> u16 {
290        u16::from_be_bytes(
291            self.data[6..8]
292                .try_into()
293                .expect("slice with incorrect length"),
294        )
295    }
296
297    /// The last eight bytes.
298    ///
299    /// ``` rust
300    /// extern crate guid_create;
301    /// let guid = guid_create::GUID::build_from_components(
302    ///     500,
303    ///     600,
304    ///     700,
305    ///     &[ 0xA0, 0xF4, 0xDD, 0x7D, 0x51, 0x2D, 0xD2, 0x61 ]
306    /// );
307    ///
308    /// assert_eq!(
309    ///     guid.data4(),
310    ///     [0xA0, 0xF4, 0xDD, 0x7D, 0x51, 0x2D, 0xD2, 0x61]
311    /// );
312    /// ```
313    pub fn data4(&self) -> [u8; 8] {
314        self.data[8..16]
315            .try_into()
316            .expect("slice with incorrect length")
317    }
318
319    /// Convert the `GUID` to a `winapi` [GUID](https://docs.rs/winapi/0.3.4/x86_64-pc-windows-msvc/winapi/shared/guiddef/struct.GUID.html)
320    /// > Only present on windows targets
321    ///
322    /// ``` rust
323    /// extern crate guid_create;
324    /// let guid = guid_create::GUID::build_from_components(
325    /// 	0x87935CDE,
326    /// 	0x7094,
327    /// 	0x4C2B,
328    /// 	&[ 0xA0, 0xF4, 0xDD, 0x7D, 0x51, 0x2D, 0xD2, 0x61 ]
329    /// 	);
330    ///
331    /// let win = guid.as_winapi_guid();
332    /// assert_eq!(win.Data1, 0x87935CDE);
333    /// assert_eq!(win.Data2, 0x7094);
334    /// assert_eq!(win.Data3, 0x4C2B);
335    /// assert_eq!(win.Data4, [ 0xA0, 0xF4, 0xDD, 0x7D, 0x51, 0x2D, 0xD2, 0x61 ]);
336    /// ```
337    #[cfg(windows)]
338    pub fn as_winapi_guid(&self) -> WinGuid {
339        WinGuid {
340            Data1: self.data1(),
341            Data2: self.data2(),
342            Data3: self.data3(),
343            Data4: self.data4(),
344        }
345    }
346
347    /// Convert a `winapi` [GUID](https://docs.rs/winapi/0.3.4/x86_64-pc-windows-msvc/winapi/shared/guiddef/struct.GUID.html) to a `GUID`
348    /// > Only present on windows targets
349    ///
350    /// ``` rust
351    /// extern crate guid_create;
352    /// extern crate winapi;
353    /// let win = winapi::shared::guiddef::GUID {
354    /// 	Data1: 0x87935CDE,
355    /// 	Data2: 0x7094,
356    /// 	Data3: 0x4C2B,
357    /// 	Data4: [ 0xA0, 0xF4, 0xDD, 0x7D, 0x51, 0x2D, 0xD2, 0x61 ]
358    /// 	};
359    ///
360    /// let guid = guid_create::GUID::from_winapi_guid(win);
361    /// assert_eq!(guid.data1(), 0x87935CDE);
362    /// assert_eq!(guid.data2(), 0x7094);
363    /// assert_eq!(guid.data3(), 0x4C2B);
364    /// assert_eq!(guid.data4(), [ 0xA0, 0xF4, 0xDD, 0x7D, 0x51, 0x2D, 0xD2, 0x61 ]);
365    /// ```
366    #[cfg(windows)]
367    pub fn from_winapi_guid(guid: WinGuid) -> Self {
368        GUID::build_from_components(guid.Data1, guid.Data2, guid.Data3, &guid.Data4)
369    }
370}
371
372#[cfg(feature = "serde")]
373use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
374
375#[cfg(feature = "serde")]
376impl<'de> Deserialize<'de> for GUID {
377    fn deserialize<D>(deserializer: D) -> Result<GUID, D::Error>
378    where
379        D: Deserializer<'de>,
380    {
381        let string_guid = String::deserialize(deserializer)?;
382        let guid = GUID::parse(&string_guid)
383            .map_err(|_| de::Error::custom(format!("cannot convert {string_guid} to guid")))?;
384        Ok(guid)
385    }
386}
387
388#[cfg(feature = "serde")]
389impl Serialize for GUID {
390    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
391        serializer.serialize_str(&*self.to_string())
392    }
393}
394
395impl fmt::Display for GUID {
396    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
397        write!(
398            f,
399            "{:08X}-{:04X}-{:04X}-{:04X}-{:08X}{:04X}",
400            self.data1(),
401            self.data2(),
402            self.data3(),
403            u16::from_be_bytes([self.data[8], self.data[9]]),
404            u32::from_be_bytes([self.data[10], self.data[11], self.data[12], self.data[13]]),
405            u16::from_be_bytes([self.data[14], self.data[15]]),
406        )
407    }
408}
409
410#[cfg(test)]
411impl quickcheck::Arbitrary for GUID {
412    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
413        let mut data = [0u8; 16];
414        data.fill_with(|| u8::arbitrary(g));
415        Self { data }
416    }
417}
418
419#[cfg(test)]
420mod tests {
421    use super::*;
422
423    #[test]
424    fn travis_test() {}
425
426    #[test]
427    #[cfg(feature = "std")]
428    fn string_lengths() {
429        for _ in 0..10000 {
430            let guid = GUID::rand();
431            let s = guid.to_string();
432            println!("{}", s);
433            assert_eq!(s.len(), 36);
434        }
435    }
436
437    #[cfg(windows)]
438    #[test]
439    fn win_guid() {
440        for _ in 0..10000 {
441            let guid = GUID::rand();
442            let win = guid.as_winapi_guid();
443            assert_eq!(guid.data1(), win.Data1);
444            assert_eq!(guid.data2(), win.Data2);
445            assert_eq!(guid.data3(), win.Data3);
446            assert_eq!(guid.data4(), win.Data4);
447            let convert_back = GUID::from_winapi_guid(win);
448            assert_eq!(guid, convert_back);
449        }
450    }
451
452    #[test]
453    #[cfg(feature = "std")]
454    fn create_from_components() {
455        let guid = GUID::build_from_components(
456            0x87935CDE,
457            0x7094,
458            0x4C2B,
459            &[0xA0, 0xF4, 0xDD, 0x7D, 0x51, 0x2D, 0xD2, 0x61],
460        );
461
462        assert_eq!(guid.data1(), 0x87935CDE);
463        assert_eq!(guid.data2(), 0x7094);
464        assert_eq!(guid.data3(), 0x4C2B);
465        assert_eq!(
466            guid.data4(),
467            [0xA0, 0xF4, 0xDD, 0x7D, 0x51, 0x2D, 0xD2, 0x61]
468        );
469        assert_eq!(guid.to_string(), "87935CDE-7094-4C2B-A0F4-DD7D512DD261");
470    }
471
472    #[test]
473    #[cfg(feature = "std")]
474    fn create_from_array() {
475        let guid = GUID::build_from_slice(&[
476            0x87, 0x93, 0x5C, 0xDE, 0x70, 0x94, 0x4C, 0x2B, 0xA0, 0xF4, 0xDD, 0x7D, 0x51, 0x2D,
477            0xD2, 0x61,
478        ]);
479
480        println!("{}", guid);
481
482        assert_eq!(guid.data1(), 0x87935CDE);
483        assert_eq!(guid.data2(), 0x7094);
484        assert_eq!(guid.data3(), 0x4C2B);
485        assert_eq!(
486            guid.data4(),
487            [0xA0, 0xF4, 0xDD, 0x7D, 0x51, 0x2D, 0xD2, 0x61]
488        );
489        assert_eq!(guid.to_string(), "87935CDE-7094-4C2B-A0F4-DD7D512DD261");
490    }
491
492    #[test]
493    #[cfg(feature = "std")]
494    fn parse_strings() {
495        for _ in 0..10000 {
496            let guid = GUID::rand();
497            let s = guid.to_string();
498            let guid2 = GUID::parse(&s).unwrap();
499            assert_eq!(guid, guid2);
500        }
501    }
502
503    #[quickcheck]
504    #[cfg(feature = "std")]
505    fn no_panic_parse(s: String) {
506        GUID::parse(&s).ok();
507    }
508
509    #[quickcheck]
510    #[cfg(feature = "std")]
511    fn parse_success(guid: GUID) -> bool {
512        let s = guid.to_string();
513        let g2 = GUID::parse(&s).unwrap();
514        g2 == guid
515    }
516}