guid/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Provides the [`Guid`] type with the same layout as the Windows type `GUID`.
5
6#![forbid(unsafe_code)]
7
8use std::str::FromStr;
9use thiserror::Error;
10use zerocopy::FromBytes;
11use zerocopy::FromZeros;
12use zerocopy::Immutable;
13use zerocopy::IntoBytes;
14use zerocopy::KnownLayout;
15
16/// Windows format GUID.
17#[repr(C)]
18#[derive(
19    Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, IntoBytes, FromBytes, Immutable, KnownLayout,
20)]
21#[cfg_attr(
22    feature = "mesh",
23    derive(mesh_protobuf::Protobuf),
24    mesh(package = "msguid")
25)]
26#[cfg_attr(feature = "inspect", derive(inspect::Inspect), inspect(display))]
27#[expect(missing_docs)]
28pub struct Guid {
29    #[cfg_attr(feature = "mesh", mesh(1))]
30    pub data1: u32,
31    #[cfg_attr(feature = "mesh", mesh(2))]
32    pub data2: u16,
33    #[cfg_attr(feature = "mesh", mesh(3))]
34    pub data3: u16,
35    #[cfg_attr(feature = "mesh", mesh(4))]
36    pub data4: [u8; 8],
37}
38
39// Default + FromBytes: null-guid is a reasonable return default
40impl Default for Guid {
41    fn default() -> Self {
42        Self::new_zeroed()
43    }
44}
45
46// These two macros are used to work around the fact that ? can't be used in const fn.
47macro_rules! option_helper {
48    ($e:expr) => {
49        match $e {
50            Some(v) => v,
51            None => return None,
52        }
53    };
54}
55
56macro_rules! result_helper {
57    ($e:expr) => {
58        match $e {
59            Some(v) => v,
60            None => return Err(ParseError::Digit),
61        }
62    };
63}
64
65/// Creates a new GUID from a string, parsing the GUID at compile time.
66/// "{00000000-0000-0000-0000-000000000000}" and
67/// "00000000-0000-0000-0000-000000000000".
68#[macro_export]
69macro_rules! guid {
70    ($x:expr $(,)?) => {
71        const { $crate::Guid::from_str_private($x) }
72    };
73}
74
75impl Guid {
76    /// Return a new randomly-generated Version 4 UUID
77    pub fn new_random() -> Self {
78        let mut guid = Guid::default();
79        getrandom::fill(guid.as_mut_bytes()).expect("rng failure");
80
81        guid.data3 = guid.data3 & 0xfff | 0x4000;
82        // Variant 1
83        guid.data4[0] = guid.data4[0] & 0x3f | 0x80;
84
85        guid
86    }
87
88    /// Do not use. Use the [`guid`] macro instead.
89    #[doc(hidden)]
90    pub const fn from_str_private(value: &str) -> Guid {
91        // Unwrap and expect are not supported in const fn.
92        match Self::parse(value.as_bytes()) {
93            Ok(guid) => guid,
94            Err(ParseError::Length) => panic!("Invalid GUID length."),
95            Err(ParseError::Format) => panic!("Invalid GUID format."),
96            Err(ParseError::Digit) => panic!("Invalid GUID digit."),
97        }
98    }
99
100    /// Helper used by `from_str_private`, `from_str`, and `TryFrom<&[u8]>`.
101    const fn parse(value: &[u8]) -> Result<Self, ParseError> {
102        // Slicing is not possible in const fn, so use an index offset.
103        let offset = if value.len() == 38 {
104            if value[0] != b'{' || value[37] != b'}' {
105                return Err(ParseError::Format);
106            }
107
108            1
109        } else if value.len() == 36 {
110            0
111        } else {
112            return Err(ParseError::Length);
113        };
114
115        if value[offset + 8] != b'-'
116            || value[offset + 13] != b'-'
117            || value[offset + 18] != b'-'
118            || value[offset + 23] != b'-'
119        {
120            return Err(ParseError::Format);
121        }
122
123        // No for loops in const fn, so do it one at a time.
124        Ok(Guid {
125            data1: result_helper!(u32_from_hex(value, offset)),
126            data2: result_helper!(u16_from_hex(value, offset + 9)),
127            data3: result_helper!(u16_from_hex(value, offset + 14)),
128            data4: [
129                result_helper!(u8_from_hex(value, offset + 19)),
130                result_helper!(u8_from_hex(value, offset + 21)),
131                result_helper!(u8_from_hex(value, offset + 24)),
132                result_helper!(u8_from_hex(value, offset + 26)),
133                result_helper!(u8_from_hex(value, offset + 28)),
134                result_helper!(u8_from_hex(value, offset + 30)),
135                result_helper!(u8_from_hex(value, offset + 32)),
136                result_helper!(u8_from_hex(value, offset + 34)),
137            ],
138        })
139    }
140
141    /// The all-zero GUID.
142    pub const ZERO: Self = guid!("00000000-0000-0000-0000-000000000000");
143
144    /// Returns true if this is the all-zero GUID.
145    pub fn is_zero(&self) -> bool {
146        self == &Self::ZERO
147    }
148}
149
150impl std::fmt::Display for Guid {
151    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152        write!(
153            f,
154            "{:08x}-{:04x}-{:04x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
155            self.data1,
156            self.data2,
157            self.data3,
158            self.data4[0],
159            self.data4[1],
160            self.data4[2],
161            self.data4[3],
162            self.data4[4],
163            self.data4[5],
164            self.data4[6],
165            self.data4[7],
166        )
167    }
168}
169
170impl std::fmt::LowerHex for Guid {
171    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172        std::fmt::Display::fmt(self, f)
173    }
174}
175
176impl std::fmt::UpperHex for Guid {
177    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178        write!(
179            f,
180            "{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}",
181            self.data1,
182            self.data2,
183            self.data3,
184            self.data4[0],
185            self.data4[1],
186            self.data4[2],
187            self.data4[3],
188            self.data4[4],
189            self.data4[5],
190            self.data4[6],
191            self.data4[7],
192        )
193    }
194}
195
196impl std::fmt::Debug for Guid {
197    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
198        std::fmt::Display::fmt(self, f)
199    }
200}
201
202/// An error parsing a GUID.
203#[derive(Debug, Error)]
204#[expect(missing_docs)]
205pub enum ParseError {
206    #[error("invalid GUID length")]
207    Length,
208    #[error("invalid GUID format")]
209    Format,
210    #[error("invalid GUID digit")]
211    Digit,
212}
213
214const fn char_to_hex(value: u8) -> Option<u8> {
215    Some(match value {
216        b'0'..=b'9' => value - b'0',
217        b'a'..=b'f' => 10 + value - b'a',
218        b'A'..=b'F' => 10 + value - b'A',
219        _ => return None,
220    })
221}
222
223const fn u8_from_hex(input: &[u8], index: usize) -> Option<u8> {
224    Some(
225        option_helper!(char_to_hex(input[index])) << 4
226            | option_helper!(char_to_hex(input[index + 1])),
227    )
228}
229
230const fn u16_from_hex(input: &[u8], index: usize) -> Option<u16> {
231    Some(
232        (option_helper!(u8_from_hex(input, index)) as u16) << 8
233            | (option_helper!(u8_from_hex(input, index + 2)) as u16),
234    )
235}
236
237const fn u32_from_hex(input: &[u8], index: usize) -> Option<u32> {
238    Some(
239        (option_helper!(u16_from_hex(input, index)) as u32) << 16
240            | (option_helper!(u16_from_hex(input, index + 4)) as u32),
241    )
242}
243
244impl FromStr for Guid {
245    type Err = ParseError;
246    fn from_str(s: &str) -> Result<Self, Self::Err> {
247        Self::try_from(s.as_bytes())
248    }
249}
250
251impl TryFrom<&[u8]> for Guid {
252    type Error = ParseError;
253
254    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
255        Guid::parse(value)
256    }
257}
258
259impl From<Guid> for [u8; 16] {
260    fn from(value: Guid) -> Self {
261        value.as_bytes().try_into().unwrap()
262    }
263}
264
265mod windows {
266    #![cfg(windows)]
267    use super::Guid;
268
269    impl From<winapi::shared::guiddef::GUID> for Guid {
270        fn from(guid: winapi::shared::guiddef::GUID) -> Self {
271            Self {
272                data1: guid.Data1,
273                data2: guid.Data2,
274                data3: guid.Data3,
275                data4: guid.Data4,
276            }
277        }
278    }
279
280    impl From<Guid> for winapi::shared::guiddef::GUID {
281        fn from(guid: Guid) -> Self {
282            Self {
283                Data1: guid.data1,
284                Data2: guid.data2,
285                Data3: guid.data3,
286                Data4: guid.data4,
287            }
288        }
289    }
290
291    impl From<windows_sys::core::GUID> for Guid {
292        fn from(guid: windows_sys::core::GUID) -> Self {
293            Self {
294                data1: guid.data1,
295                data2: guid.data2,
296                data3: guid.data3,
297                data4: guid.data4,
298            }
299        }
300    }
301
302    impl From<Guid> for windows_sys::core::GUID {
303        fn from(guid: Guid) -> Self {
304            Self {
305                data1: guid.data1,
306                data2: guid.data2,
307                data3: guid.data3,
308                data4: guid.data4,
309            }
310        }
311    }
312
313    impl From<windows::core::GUID> for Guid {
314        fn from(guid: windows::core::GUID) -> Self {
315            Self {
316                data1: guid.data1,
317                data2: guid.data2,
318                data3: guid.data3,
319                data4: guid.data4,
320            }
321        }
322    }
323
324    impl From<Guid> for windows::core::GUID {
325        fn from(guid: Guid) -> Self {
326            Self {
327                data1: guid.data1,
328                data2: guid.data2,
329                data3: guid.data3,
330                data4: guid.data4,
331            }
332        }
333    }
334}
335
336#[cfg(test)]
337mod tests {
338    use super::Guid;
339    use super::guid;
340
341    #[test]
342    fn test_display_guid() {
343        let guid = Guid {
344            data1: 0xcf127acc,
345            data2: 0xc960,
346            data3: 0x41e4,
347            data4: [0x9b, 0x1e, 0x51, 0x3e, 0x8a, 0x89, 0x14, 0x7d],
348        };
349        assert_eq!(format!("{}", &guid), "cf127acc-c960-41e4-9b1e-513e8a89147d");
350    }
351
352    #[test]
353    fn test_parse_guid() {
354        let guid = Guid {
355            data1: 0xcf127acc,
356            data2: 0xc960,
357            data3: 0x41e4,
358            data4: [0x9b, 0x1e, 0x51, 0x3e, 0x8a, 0x89, 0x14, 0x7d],
359        };
360        assert_eq!(
361            guid,
362            b"cf127acc-c960-41e4-9b1e-513e8a89147d"[..]
363                .try_into()
364                .expect("valid GUID")
365        );
366        assert_eq!(
367            guid,
368            b"{cf127acc-c960-41e4-9b1e-513e8a89147d}"[..]
369                .try_into()
370                .expect("valid braced GUID")
371        );
372
373        // Test GUID parsing at compile time.
374        const TEST_GUID: Guid = guid!("cf127acc-c960-41e4-9b1e-513e8a89147d");
375        assert_eq!(guid, TEST_GUID);
376        const TEST_BRACED_GUID: Guid = guid!("{cf127acc-c960-41e4-9b1e-513e8a89147d}");
377        assert_eq!(guid, TEST_BRACED_GUID);
378    }
379}