intercom_common/
guid.rs

1// <3 winapi
2// (Re-defining these here as not to pull the whole winapi as runtime dependency)
3
4/// Binary GUID format as defined for the COM interfaces.
5#[repr(C)]
6#[derive(Eq, PartialEq, Clone)]
7pub struct GUID
8{
9    pub data1: u32,
10    pub data2: u16,
11    pub data3: u16,
12    pub data4: [u8; 8],
13}
14
15impl std::fmt::Debug for GUID
16{
17    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error>
18    {
19        write!(f, "{}", self)
20    }
21}
22
23impl GUID
24{
25    pub fn zero_guid() -> GUID
26    {
27        GUID {
28            data1: 0,
29            data2: 0,
30            data3: 0,
31            data4: [0; 8],
32        }
33    }
34
35    /// Parses the given string as a GUID.
36    ///
37    /// Supported formats include:
38    ///
39    /// - Braces and hyphens: {00000000-0000-0000-0000-000000000000}
40    /// - Hyphens only: 00000000-0000-0000-0000-000000000000
41    /// - Raw hexadecimal: 00000000000000000000000000000000
42    pub fn parse(guid: &str) -> Result<GUID, String>
43    {
44        // We support the following formats:
45        //
46        // - {00000000-0000-0000-0000-000000000000} (38 chars)
47        // - 00000000-0000-0000-0000-000000000000 (36 chars)
48        // - 00000000000000000000000000000000 (32 chars)
49        //
50        // Use the total length to make the assumption on the format.
51        enum GuidFormat
52        {
53            Braces,
54            Hyphens,
55            Raw,
56        }
57        let guid_format = match guid.len() {
58            38 => GuidFormat::Braces,
59            36 => GuidFormat::Hyphens,
60            32 => GuidFormat::Raw,
61            _ => {
62                return Err(format!(
63                    "Unrecognized GUID format: '{}' ({})",
64                    guid,
65                    guid.len()
66                ))
67            }
68        };
69
70        // Define the format requirements.
71        //
72        // Some(_) values correspond to required characters while None values
73        // correspond to the hexadecimal digits themselves.
74        #[rustfmt::skip]
75        let format = match guid_format {
76            GuidFormat::Braces => vec![
77                Some( b'{' ), None, None, None, None, None, None, None, None,
78                Some( b'-' ), None, None, None, None,
79                Some( b'-' ), None, None, None, None,
80                Some( b'-' ), None, None, None, None,
81                Some( b'-' ), None, None, None, None, None, None, None, None, None, None, None, None,
82                Some( b'}' )
83            ],
84            GuidFormat::Hyphens => vec![
85                None, None, None, None, None, None, None, None,
86                Some( b'-' ), None, None, None, None,
87                Some( b'-' ), None, None, None, None,
88                Some( b'-' ), None, None, None, None,
89                Some( b'-' ), None, None, None, None, None, None, None, None, None, None, None, None
90            ],
91            GuidFormat::Raw => vec![
92                None, None, None, None, None, None, None, None,
93                None, None, None, None,
94                None, None, None, None,
95                None, None, None, None,
96                None, None, None, None, None, None, None, None, None, None, None, None
97            ]
98        };
99
100        // Read the hexadecimal values into a buffer.
101        let mut buffer = [0u8; 16];
102        let mut digit = 0;
103        for (i_char, chr) in guid.bytes().enumerate() {
104            // If this is a fixed character, ensure we have the correct one.
105            // If we had the correct char, continue onto next character,
106            // otherwise error out. In any case we'll go past this guard
107            // only if we're expeting a hexadecimal-digit.
108            if let Some(b) = format[i_char] {
109                if chr == b {
110                    continue;
111                } else {
112                    return Err(format!("Unexpected character in GUID: {}", chr));
113                }
114            }
115
116            // Convert the hexadecimal character into a numerical value.
117            let value: u8 = match chr {
118                b'0'..=b'9' => chr - b'0',
119                b'a'..=b'f' => chr - b'a' + 10,
120                b'A'..=b'F' => chr - b'A' + 10,
121                _ => return Err(format!("Unrecognized character in GUID: {}", chr)),
122            };
123
124            // Each digit corresponds to one half of a byte in the final [u8]
125            // buffer. Resolve which half and which byte this is.
126            let half = digit % 2;
127            let byte = (digit - half) / 2;
128
129            // Finally add the value into the buffer and proceed to the next
130            // digit.
131            if half == 0 {
132                buffer[byte] += value * 16;
133            } else {
134                buffer[byte] += value;
135            }
136
137            // 'digit' is incremented only when we actually match a hex-digit
138            // in the GUID. 'i_char', which is the loop counter, also includes
139            // all the GUID formatting characters, such as {} and -.
140            digit += 1;
141        }
142
143        // We got the whole buffer. Convert it into a GUID.
144        //
145        // Note: We could probably do this with a memcopy - we might have even
146        // been able to do it directly into the GUID by preallocating it and
147        // then interpreting it as a *[u8] array.
148        //
149        // However I can't be bothered to figure out endianness bits here, so
150        // we'll just go with raw calculations.
151        Ok(GUID {
152            data1: (u32::from(buffer[0]) << 24)
153                + (u32::from(buffer[1]) << 16)
154                + (u32::from(buffer[2]) << 8)
155                + (u32::from(buffer[3])),
156            data2: (u16::from(buffer[4]) << 8) + (u16::from(buffer[5])),
157            data3: (u16::from(buffer[6]) << 8) + (u16::from(buffer[7])),
158            data4: [
159                buffer[8], buffer[9], buffer[10], buffer[11], buffer[12], buffer[13], buffer[14],
160                buffer[15],
161            ],
162        })
163    }
164
165    // Get GUID as bytes.
166    pub fn as_bytes(&self) -> &[u8; 16]
167    {
168        // We know the GUIDs are 128 bits (16 bytes).
169        // Do the equivalent of a reinterpret_cast.
170        unsafe { &*(self as *const _ as *const [u8; 16]) }
171    }
172}
173
174impl Default for GUID
175{
176    fn default() -> GUID
177    {
178        GUID::zero_guid()
179    }
180}
181
182impl std::fmt::Display for GUID
183{
184    /// Formats the GUID in brace-hyphenated format for display.
185    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result
186    {
187        fmt_guid(self, f, &GuidFmtCase::Upper, &GuidFmt::Braces)
188    }
189}
190
191impl std::fmt::LowerHex for GUID
192{
193    /// Formats the GUID in brace-hyphenated format for display.
194    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result
195    {
196        let fmt = if f.sign_minus() {
197            GuidFmt::Hyphens
198        } else {
199            GuidFmt::Raw
200        };
201        fmt_guid(self, f, &GuidFmtCase::Lower, &fmt)
202    }
203}
204
205impl std::fmt::UpperHex for GUID
206{
207    /// Formats the GUID in brace-hyphenated format for display.
208    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result
209    {
210        let fmt = if f.sign_minus() {
211            GuidFmt::Hyphens
212        } else {
213            GuidFmt::Raw
214        };
215        fmt_guid(self, f, &GuidFmtCase::Upper, &fmt)
216    }
217}
218
219enum GuidFmtCase
220{
221    Lower,
222    Upper,
223}
224enum GuidFmt
225{
226    Braces,
227    Hyphens,
228    Raw,
229}
230
231fn fmt_guid(
232    g: &GUID,
233    f: &mut std::fmt::Formatter,
234    case: &GuidFmtCase,
235    fmt: &GuidFmt,
236) -> std::fmt::Result
237{
238    match *case {
239        GuidFmtCase::Lower => {
240            match *fmt {
241                GuidFmt::Braces => {
242                    write!( f,
243                    "{{{:08x}-{:04x}-{:04x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}}}",
244                    g.data1, g.data2, g.data3,
245                    g.data4[0], g.data4[1], g.data4[2], g.data4[3],
246                    g.data4[4], g.data4[5], g.data4[6], g.data4[7] )
247                }
248                GuidFmt::Hyphens => write!(
249                    f,
250                    "{:08x}-{:04x}-{:04x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
251                    g.data1,
252                    g.data2,
253                    g.data3,
254                    g.data4[0],
255                    g.data4[1],
256                    g.data4[2],
257                    g.data4[3],
258                    g.data4[4],
259                    g.data4[5],
260                    g.data4[6],
261                    g.data4[7]
262                ),
263                GuidFmt::Raw => write!(
264                    f,
265                    "{:08x}{:04x}{:04x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
266                    g.data1,
267                    g.data2,
268                    g.data3,
269                    g.data4[0],
270                    g.data4[1],
271                    g.data4[2],
272                    g.data4[3],
273                    g.data4[4],
274                    g.data4[5],
275                    g.data4[6],
276                    g.data4[7]
277                ),
278            }
279        }
280        GuidFmtCase::Upper => {
281            match *fmt {
282                GuidFmt::Braces => {
283                    write!( f,
284                    "{{{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}}}",
285                    g.data1, g.data2, g.data3,
286                    g.data4[0], g.data4[1], g.data4[2], g.data4[3],
287                    g.data4[4], g.data4[5], g.data4[6], g.data4[7] )
288                }
289                GuidFmt::Hyphens => write!(
290                    f,
291                    "{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}",
292                    g.data1,
293                    g.data2,
294                    g.data3,
295                    g.data4[0],
296                    g.data4[1],
297                    g.data4[2],
298                    g.data4[3],
299                    g.data4[4],
300                    g.data4[5],
301                    g.data4[6],
302                    g.data4[7]
303                ),
304                GuidFmt::Raw => write!(
305                    f,
306                    "{:08X}{:04X}{:04X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}",
307                    g.data1,
308                    g.data2,
309                    g.data3,
310                    g.data4[0],
311                    g.data4[1],
312                    g.data4[2],
313                    g.data4[3],
314                    g.data4[4],
315                    g.data4[5],
316                    g.data4[6],
317                    g.data4[7]
318                ),
319            }
320        }
321    }
322}
323
324#[cfg(test)]
325mod test
326{
327    use super::*;
328
329    #[test]
330    fn zero_guid()
331    {
332        let guid = GUID::zero_guid();
333
334        assert_eq!(0, guid.data1);
335        assert_eq!(0, guid.data2);
336        assert_eq!(0, guid.data3);
337        assert_eq!([0, 0, 0, 0, 0, 0, 0, 0], guid.data4);
338    }
339
340    #[test]
341    fn parse_braces()
342    {
343        let expected = GUID {
344            data1: 0x12345678,
345            data2: 0x90ab,
346            data3: 0xcdef,
347            data4: [0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21],
348        };
349
350        let actual = GUID::parse("{12345678-90ab-cdef-fedc-ba0987654321}").unwrap();
351
352        assert_eq!(expected, actual);
353    }
354
355    #[test]
356    fn parse_hyphenated()
357    {
358        let expected = GUID {
359            data1: 0x12345678,
360            data2: 0x90ab,
361            data3: 0xcdef,
362            data4: [0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21],
363        };
364
365        let actual = GUID::parse("12345678-90ab-cdef-fedc-ba0987654321").unwrap();
366
367        assert_eq!(expected, actual);
368    }
369
370    #[test]
371    fn parse_raw()
372    {
373        let expected = GUID {
374            data1: 0x12345678,
375            data2: 0x90ab,
376            data3: 0xcdef,
377            data4: [0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21],
378        };
379
380        let actual = GUID::parse("1234567890abcdeffedcba0987654321").unwrap();
381
382        assert_eq!(expected, actual);
383    }
384
385    #[test]
386    fn format_default()
387    {
388        let expected = "{12345678-90AB-CDEF-FEDC-BA0987654321}";
389        let guid = GUID {
390            data1: 0x12345678,
391            data2: 0x90ab,
392            data3: 0xcdef,
393            data4: [0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21],
394        };
395
396        assert_eq!(expected, format!("{}", guid));
397    }
398
399    #[test]
400    fn format_lowerhex()
401    {
402        let expected = "1234567890abcdeffedcba0987654321";
403        let guid = GUID {
404            data1: 0x12345678,
405            data2: 0x90ab,
406            data3: 0xcdef,
407            data4: [0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21],
408        };
409
410        assert_eq!(expected, format!("{:x}", guid));
411    }
412
413    #[test]
414    fn format_lowerhex_hyphens()
415    {
416        let expected = "12345678-90ab-cdef-fedc-ba0987654321";
417        let guid = GUID {
418            data1: 0x12345678,
419            data2: 0x90ab,
420            data3: 0xcdef,
421            data4: [0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21],
422        };
423
424        assert_eq!(expected, format!("{:-x}", guid));
425    }
426
427    #[test]
428    fn format_upperhex()
429    {
430        let expected = "1234567890ABCDEFFEDCBA0987654321";
431        let guid = GUID {
432            data1: 0x12345678,
433            data2: 0x90ab,
434            data3: 0xcdef,
435            data4: [0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21],
436        };
437
438        assert_eq!(expected, format!("{:X}", guid));
439    }
440
441    #[test]
442    fn format_upperhex_hyphens()
443    {
444        let expected = "12345678-90AB-CDEF-FEDC-BA0987654321";
445        let guid = GUID {
446            data1: 0x12345678,
447            data2: 0x90ab,
448            data3: 0xcdef,
449            data4: [0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21],
450        };
451
452        assert_eq!(expected, format!("{:-X}", guid));
453    }
454}