bebop/types/
guid.rs

1use std::fmt::{self, Display, Formatter};
2use std::ops::Deref;
3use std::str::FromStr;
4
5/// The Microsoft ordering for GUID bytes, where each GUID_MAPPING[i] is the ith byte if stored in
6/// big endian format.
7const BYTE_MAP: [usize; 16] = [3, 2, 1, 0, 5, 4, 7, 6, 8, 9, 10, 11, 12, 13, 14, 15];
8
9/// A GUID is a unique identifier. Stored internally in the Microsoft Guid format to support
10/// zero-copy deserialization
11#[derive(Eq, PartialEq, Hash, Clone, Copy, Debug)]
12#[repr(transparent)]
13pub struct Guid([u8; 16]);
14
15impl Deref for Guid {
16    type Target = [u8; 16];
17
18    fn deref(&self) -> &Self::Target {
19        &self.0
20    }
21}
22
23impl Display for Guid {
24    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
25        write!(
26            f,
27            "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
28            self.0[BYTE_MAP[0]],
29            self.0[BYTE_MAP[1]],
30            self.0[BYTE_MAP[2]],
31            self.0[BYTE_MAP[3]],
32            self.0[BYTE_MAP[4]],
33            self.0[BYTE_MAP[5]],
34            self.0[BYTE_MAP[6]],
35            self.0[BYTE_MAP[7]],
36            self.0[BYTE_MAP[8]],
37            self.0[BYTE_MAP[9]],
38            self.0[BYTE_MAP[10]],
39            self.0[BYTE_MAP[11]],
40            self.0[BYTE_MAP[12]],
41            self.0[BYTE_MAP[13]],
42            self.0[BYTE_MAP[14]],
43            self.0[BYTE_MAP[15]],
44        )
45    }
46}
47
48impl FromStr for Guid {
49    type Err = &'static str;
50
51    fn from_str(s: &str) -> Result<Self, Self::Err> {
52        match s.len() {
53            36 => Self::from_str_with_hyphens(s),
54            32 => Self::from_str_without_hyphens(s),
55            _ => Err("Invalid length, not a GUID"),
56        }
57    }
58}
59
60const GUID_PARSE_ERR: &str = "Failed to parse GUID bytes";
61
62impl Guid {
63    /// Convert from a byte array ordered by
64    /// https://docs.microsoft.com/en-us/dotnet/api/system.guid.tobytearray?view=net-5.0#System_Guid_ToByteArray
65    /// Will just read first 16 bytes
66    pub const fn from_ms_bytes(raw: &[u8; 16]) -> Self {
67        Self([
68            raw[0], raw[1], raw[2], raw[3], raw[4], raw[5], raw[6], raw[7], raw[8], raw[9],
69            raw[10], raw[11], raw[12], raw[13], raw[14], raw[15],
70        ])
71    }
72
73    /// Mimic format produced by
74    /// https://docs.microsoft.com/en-us/dotnet/api/system.guid.tobytearray?view=net-5.0#System_Guid_ToByteArray
75    pub const fn to_ms_bytes(self) -> [u8; 16] {
76        self.0
77    }
78
79    fn from_str_without_hyphens(s: &str) -> Result<Self, &'static str> {
80        let mut buf = [0u8; 16];
81        // this is inefficient because of the unicode representation used by Rust but probably
82        // fine for now
83        for i in 0..16 {
84            if let Ok(v) = u8::from_str_radix(&s[i * 2..i * 2 + 2], 16) {
85                buf[BYTE_MAP[i]] = v
86            } else {
87                return Err(GUID_PARSE_ERR);
88            }
89        }
90        Ok(Guid(buf))
91    }
92
93    #[inline]
94    fn from_str_with_hyphens(s: &str) -> Result<Self, &'static str> {
95        // avoid extra copy
96        let without_hyphens: String = s.split('-').collect();
97        Self::from_str_without_hyphens(&without_hyphens)
98    }
99
100    pub const fn from_le_bytes(b: [u8; 16]) -> Self {
101        Self([
102            b[15 - BYTE_MAP[0]],
103            b[15 - BYTE_MAP[1]],
104            b[15 - BYTE_MAP[2]],
105            b[15 - BYTE_MAP[3]],
106            b[15 - BYTE_MAP[4]],
107            b[15 - BYTE_MAP[5]],
108            b[15 - BYTE_MAP[6]],
109            b[15 - BYTE_MAP[7]],
110            b[15 - BYTE_MAP[8]],
111            b[15 - BYTE_MAP[9]],
112            b[15 - BYTE_MAP[10]],
113            b[15 - BYTE_MAP[11]],
114            b[15 - BYTE_MAP[12]],
115            b[15 - BYTE_MAP[13]],
116            b[15 - BYTE_MAP[14]],
117            b[15 - BYTE_MAP[15]],
118        ])
119    }
120
121    /// Get the little endian bytes of this GUID.
122    pub const fn to_le_bytes(self) -> [u8; 16] {
123        [
124            self.0[BYTE_MAP[15]],
125            self.0[BYTE_MAP[14]],
126            self.0[BYTE_MAP[13]],
127            self.0[BYTE_MAP[12]],
128            self.0[BYTE_MAP[11]],
129            self.0[BYTE_MAP[10]],
130            self.0[BYTE_MAP[9]],
131            self.0[BYTE_MAP[8]],
132            self.0[BYTE_MAP[7]],
133            self.0[BYTE_MAP[6]],
134            self.0[BYTE_MAP[5]],
135            self.0[BYTE_MAP[4]],
136            self.0[BYTE_MAP[3]],
137            self.0[BYTE_MAP[2]],
138            self.0[BYTE_MAP[1]],
139            self.0[BYTE_MAP[0]],
140        ]
141    }
142
143    pub const fn from_be_bytes(b: [u8; 16]) -> Self {
144        Self([
145            b[BYTE_MAP[0]],
146            b[BYTE_MAP[1]],
147            b[BYTE_MAP[2]],
148            b[BYTE_MAP[3]],
149            b[BYTE_MAP[4]],
150            b[BYTE_MAP[5]],
151            b[BYTE_MAP[6]],
152            b[BYTE_MAP[7]],
153            b[BYTE_MAP[8]],
154            b[BYTE_MAP[9]],
155            b[BYTE_MAP[10]],
156            b[BYTE_MAP[11]],
157            b[BYTE_MAP[12]],
158            b[BYTE_MAP[13]],
159            b[BYTE_MAP[14]],
160            b[BYTE_MAP[15]],
161        ])
162    }
163
164    /// Get the big endian bytes of this GUID.
165    pub const fn to_be_bytes(self) -> [u8; 16] {
166        [
167            self.0[BYTE_MAP[0]],
168            self.0[BYTE_MAP[1]],
169            self.0[BYTE_MAP[2]],
170            self.0[BYTE_MAP[3]],
171            self.0[BYTE_MAP[4]],
172            self.0[BYTE_MAP[5]],
173            self.0[BYTE_MAP[6]],
174            self.0[BYTE_MAP[7]],
175            self.0[BYTE_MAP[8]],
176            self.0[BYTE_MAP[9]],
177            self.0[BYTE_MAP[10]],
178            self.0[BYTE_MAP[11]],
179            self.0[BYTE_MAP[12]],
180            self.0[BYTE_MAP[13]],
181            self.0[BYTE_MAP[14]],
182            self.0[BYTE_MAP[15]],
183        ]
184    }
185}
186
187#[cfg(test)]
188const BYTES_BE: [u8; 16] = [
189    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
190];
191#[cfg(test)]
192const BYTES_LE: [u8; 16] = [
193    0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00,
194];
195#[cfg(test)]
196const BYTES_MS: [u8; 16] = [
197    0x03, 0x02, 0x01, 0x00, 0x05, 0x04, 0x07, 0x06, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
198];
199#[cfg(test)]
200const BYTES_STR: &str = "00010203-0405-0607-0809-0a0b0c0d0e0f";
201#[cfg(test)]
202const BYTES_STR_NO_HYPH: &str = "000102030405060708090a0b0c0d0e0f";
203
204#[test]
205fn from_ms_bytes() {
206    assert_eq!(Guid::from_ms_bytes(&BYTES_MS).0, BYTES_MS);
207}
208
209#[test]
210fn to_ms_bytes() {
211    assert_eq!(Guid(BYTES_MS).to_ms_bytes(), BYTES_MS);
212}
213
214#[test]
215fn from_le_bytes() {
216    assert_eq!(Guid::from_le_bytes(BYTES_LE).0, BYTES_MS);
217}
218
219#[test]
220fn to_le_bytes() {
221    assert_eq!(Guid(BYTES_MS).to_le_bytes(), BYTES_LE);
222}
223
224#[test]
225fn from_be_bytes() {
226    assert_eq!(Guid::from_be_bytes(BYTES_BE).0, BYTES_MS);
227}
228
229#[test]
230fn to_be_bytes() {
231    assert_eq!(Guid(BYTES_MS).to_be_bytes(), BYTES_BE);
232}
233
234#[test]
235fn from_str() {
236    assert_eq!(Guid::from_str(BYTES_STR).unwrap().0, BYTES_MS);
237    assert_eq!(Guid::from_str(BYTES_STR_NO_HYPH).unwrap().0, BYTES_MS);
238    assert_eq!(Guid::from_str_with_hyphens(BYTES_STR).unwrap().0, BYTES_MS);
239    assert_eq!(
240        Guid::from_str_without_hyphens(BYTES_STR_NO_HYPH).unwrap().0,
241        BYTES_MS
242    );
243}
244
245#[test]
246fn to_str() {
247    assert_eq!(Guid(BYTES_MS).to_string(), BYTES_STR);
248}