1#![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#[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
39impl Default for Guid {
41 fn default() -> Self {
42 Self::new_zeroed()
43 }
44}
45
46macro_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#[macro_export]
69macro_rules! guid {
70 ($x:expr $(,)?) => {
71 const { $crate::Guid::from_str_private($x) }
72 };
73}
74
75impl Guid {
76 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 guid.data4[0] = guid.data4[0] & 0x3f | 0x80;
84
85 guid
86 }
87
88 #[doc(hidden)]
90 pub const fn from_str_private(value: &str) -> Guid {
91 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 const fn parse(value: &[u8]) -> Result<Self, ParseError> {
102 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 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 pub const ZERO: Self = guid!("00000000-0000-0000-0000-000000000000");
143
144 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#[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 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}