1use std::fmt;
10use std::str::FromStr;
11
12use crate::error::Error;
13
14#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
16pub struct Guid {
17 a: u32,
18 b: u16,
19 c: u16,
20 d: [u8; 8],
21}
22
23const fn hex_digit(b: u8) -> Option<u8> {
24 match b {
25 b'0'..=b'9' => Some(b - b'0'),
26 b'a'..=b'f' => Some(b - b'a' + 10),
27 b'A'..=b'F' => Some(b - b'A' + 10),
28 _ => None,
29 }
30}
31
32impl Guid {
33 pub const EMPTY: Guid = Guid { a: 0, b: 0, c: 0, d: [0; 8] };
35
36 pub const fn new(a: u32, b: u16, c: u16, d: [u8; 8]) -> Guid {
38 Guid { a, b, c, d }
39 }
40
41 const fn try_parse_bytes(s: &[u8]) -> Option<Guid> {
45 let mut start = 0;
46 let mut end = s.len();
47 if end >= 2 && ((s[0] == b'{' && s[end - 1] == b'}') || (s[0] == b'(' && s[end - 1] == b')')) {
48 start = 1;
49 end -= 1;
50 }
51 let hyphens = match end - start {
52 36 => true,
53 32 => false,
54 _ => return None,
55 };
56 let mut digits = [0u8; 32];
57 let mut count = 0;
58 let mut i = start;
59 while i < end {
60 let position = i - start;
61 if hyphens && (position == 8 || position == 13 || position == 18 || position == 23) {
62 if s[i] != b'-' {
63 return None;
64 }
65 } else {
66 match hex_digit(s[i]) {
67 Some(digit) => {
68 if count == 32 {
69 return None;
70 }
71 digits[count] = digit;
72 count += 1;
73 }
74 None => return None,
75 }
76 }
77 i += 1;
78 }
79 if count != 32 {
80 return None;
81 }
82 let mut bytes = [0u8; 16];
83 let mut k = 0;
84 while k < 16 {
85 bytes[k] = (digits[2 * k] << 4) | digits[2 * k + 1];
86 k += 1;
87 }
88 Some(Guid {
89 a: u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
90 b: u16::from_be_bytes([bytes[4], bytes[5]]),
91 c: u16::from_be_bytes([bytes[6], bytes[7]]),
92 d: [bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15]],
93 })
94 }
95
96 pub const fn parse_const(text: &str) -> Guid {
99 match Guid::try_parse_bytes(text.as_bytes()) {
100 Some(guid) => guid,
101 None => panic!("Unrecognized Guid format."),
102 }
103 }
104
105 pub fn parse(text: &str) -> Result<Guid, Error> {
107 Guid::try_parse_bytes(text.trim().as_bytes())
108 .ok_or_else(|| Error::Format("Unrecognized Guid format.".to_string()))
109 }
110
111 pub fn from_byte_array(bytes: &[u8]) -> Result<Guid, Error> {
114 if bytes.len() != 16 {
115 return Err(Error::Argument("Byte array for Guid must be exactly 16 bytes long.".to_string()));
116 }
117 let mut d = [0u8; 8];
118 d.copy_from_slice(&bytes[8..16]);
119 Ok(Guid {
120 a: u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
121 b: u16::from_le_bytes([bytes[4], bytes[5]]),
122 c: u16::from_le_bytes([bytes[6], bytes[7]]),
123 d,
124 })
125 }
126
127 pub fn to_byte_array(&self) -> [u8; 16] {
130 let mut bytes = [0u8; 16];
131 bytes[0..4].copy_from_slice(&self.a.to_le_bytes());
132 bytes[4..6].copy_from_slice(&self.b.to_le_bytes());
133 bytes[6..8].copy_from_slice(&self.c.to_le_bytes());
134 bytes[8..16].copy_from_slice(&self.d);
135 bytes
136 }
137}
138
139impl fmt::Display for Guid {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 write!(
143 f,
144 "{:08x}-{:04x}-{:04x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
145 self.a,
146 self.b,
147 self.c,
148 self.d[0],
149 self.d[1],
150 self.d[2],
151 self.d[3],
152 self.d[4],
153 self.d[5],
154 self.d[6],
155 self.d[7]
156 )
157 }
158}
159
160impl FromStr for Guid {
161 type Err = Error;
162
163 fn from_str(s: &str) -> Result<Self, Self::Err> {
164 Guid::parse(s)
165 }
166}
167
168#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn parses_and_formats_the_hyphenated_form() {
175 let guid = Guid::parse("6E3C5310-E2B1-4C3D-8688-01183AA48C5B").unwrap();
176 assert_eq!(guid.to_string(), "6e3c5310-e2b1-4c3d-8688-01183aa48c5b");
177 assert_eq!(guid, Guid::parse_const("6e3c5310-e2b1-4c3d-8688-01183aa48c5b"));
178 assert_eq!(guid, Guid::parse("{6E3C5310-E2B1-4C3D-8688-01183AA48C5B}").unwrap());
179 assert_eq!(guid, Guid::parse("6E3C5310E2B14C3D868801183AA48C5B").unwrap());
180 assert_eq!(guid, "6E3C5310-E2B1-4C3D-8688-01183AA48C5B".parse::<Guid>().unwrap());
181 }
182
183 #[test]
184 fn byte_array_has_the_first_three_groups_little_endian() {
185 let guid = Guid::parse_const("6E3C5310-E2B1-4C3D-8688-01183AA48C5B");
186 let bytes = guid.to_byte_array();
187 assert_eq!(
188 bytes,
189 [0x10, 0x53, 0x3C, 0x6E, 0xB1, 0xE2, 0x3D, 0x4C, 0x86, 0x88, 0x01, 0x18, 0x3A, 0xA4, 0x8C, 0x5B]
190 );
191 assert_eq!(Guid::from_byte_array(&bytes).unwrap(), guid);
192 }
193
194 #[test]
195 fn rejects_what_is_not_an_identifier() {
196 assert!(matches!(Guid::parse(""), Err(Error::Format(_))));
197 assert!(matches!(Guid::parse("6E3C5310-E2B1-4C3D-8688-01183AA48C5"), Err(Error::Format(_))));
198 assert!(matches!(Guid::parse("6E3C5310+E2B1-4C3D-8688-01183AA48C5B"), Err(Error::Format(_))));
199 assert!(matches!(Guid::parse("6E3C5310-E2B1-4C3D-8688-01183AA48C5G"), Err(Error::Format(_))));
200 assert!(matches!(Guid::from_byte_array(&[0; 15]), Err(Error::Argument(_))));
201 }
202}