1use std::{error, fmt, str::FromStr};
4
5use serde::{Deserialize, Serialize};
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
22pub struct Guid {
23 pub data1: u32,
24 pub data2: u16,
25 pub data3: u16,
26 pub data4: [u8; 8],
27}
28
29impl Guid {
30 pub fn new_v4() -> Self {
32 let mut bytes = [0u8; 16];
33 rand::fill(&mut bytes);
34 bytes[7] = (bytes[7] & 0x0f) | 0x40;
35 bytes[8] = (bytes[8] & 0x3f) | 0x80;
36 Self::from_bytes(&bytes).unwrap()
37 }
38
39 pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
41 let bytes: [u8; 16] = bytes.try_into().map_err(|_| Error::PacketLength)?;
42 Ok(Self {
43 data1: u32::from_le_bytes(bytes[0..4].try_into().unwrap()),
44 data2: u16::from_le_bytes(bytes[4..6].try_into().unwrap()),
45 data3: u16::from_le_bytes(bytes[6..8].try_into().unwrap()),
46 data4: bytes[8..16].try_into().unwrap(),
47 })
48 }
49
50 pub const fn to_bytes(&self) -> [u8; 16] {
52 let mut bytes = [0u8; 16];
53 let d1 = self.data1.to_le_bytes();
54 let d2 = self.data2.to_le_bytes();
55 let d3 = self.data3.to_le_bytes();
56 bytes[0] = d1[0];
57 bytes[1] = d1[1];
58 bytes[2] = d1[2];
59 bytes[3] = d1[3];
60 bytes[4] = d2[0];
61 bytes[5] = d2[1];
62 bytes[6] = d3[0];
63 bytes[7] = d3[1];
64 let mut i = 0;
65 while i < 8 {
66 bytes[8 + i] = self.data4[i];
67 i += 1;
68 }
69 bytes
70 }
71}
72
73impl TryFrom<&[u8]> for Guid {
74 type Error = Error;
75
76 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
77 Self::from_bytes(value)
78 }
79}
80
81impl fmt::Display for Guid {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 write!(
84 f,
85 "{:08x}-{:04x}-{:04x}-{:02x}{:02x}-",
86 self.data1, self.data2, self.data3, self.data4[0], self.data4[1]
87 )?;
88 for byte in &self.data4[2..] {
89 write!(f, "{byte:02x}")?;
90 }
91 Ok(())
92 }
93}
94
95impl FromStr for Guid {
96 type Err = Error;
97
98 fn from_str(value: &str) -> Result<Self, Self::Err> {
99 if value.len() != 36
100 || value.as_bytes()[8] != b'-'
101 || value.as_bytes()[13] != b'-'
102 || value.as_bytes()[18] != b'-'
103 || value.as_bytes()[23] != b'-'
104 {
105 return Err(Error::StringSyntax);
106 }
107 let parse = |start, end| {
108 u64::from_str_radix(&value[start..end], 16).map_err(|_| Error::StringSyntax)
109 };
110 let data1 = u32::try_from(parse(0, 8)?).unwrap();
111 let data2 = u16::try_from(parse(9, 13)?).unwrap();
112 let data3 = u16::try_from(parse(14, 18)?).unwrap();
113 let data4a = u16::try_from(parse(19, 23)?).unwrap();
114 let data4b = parse(24, 36)?;
115 let mut data4 = [0u8; 8];
116 data4[0..2].copy_from_slice(&data4a.to_be_bytes());
117 data4[2..8].copy_from_slice(&data4b.to_be_bytes()[2..]);
118 Ok(Self {
119 data1,
120 data2,
121 data3,
122 data4,
123 })
124 }
125}
126
127#[derive(Clone, Copy, Debug, PartialEq, Eq)]
129pub enum Error {
130 PacketLength,
132 StringSyntax,
134}
135
136impl fmt::Display for Error {
137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138 match self {
139 Self::PacketLength => f.write_str("GUID packet must contain exactly 16 bytes"),
140 Self::StringSyntax => f.write_str("invalid canonical GUID string"),
141 }
142 }
143}
144
145impl error::Error for Error {}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn string_and_native_packet_round_trip() {
153 let guid: Guid = "00112233-4455-6677-8899-aabbccddeeff".parse().unwrap();
154 assert_eq!(
155 guid.to_bytes(),
156 [
157 0x33, 0x22, 0x11, 0x00, 0x55, 0x44, 0x77, 0x66, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
158 0xee, 0xff,
159 ]
160 );
161 assert_eq!(guid.to_string(), "00112233-4455-6677-8899-aabbccddeeff");
162 assert_eq!(
163 "00112233-4455-6677-8899-AABBCCDDEEFF"
164 .parse::<Guid>()
165 .unwrap(),
166 guid
167 );
168 assert_eq!(Guid::from_bytes(&guid.to_bytes()).unwrap(), guid);
169 }
170
171 #[test]
172 fn rejects_noncanonical_text_and_packet_lengths() {
173 assert!(
174 "{00112233-4455-6677-8899-aabbccddeeff}"
175 .parse::<Guid>()
176 .is_err()
177 );
178 assert!("00112233445566778899aabbccddeeff".parse::<Guid>().is_err());
179 assert!(Guid::from_bytes(&[0; 15]).is_err());
180 }
181
182 #[test]
183 fn serde_round_trip() {
184 let guid: Guid = "00112233-4455-6677-8899-aabbccddeeff".parse().unwrap();
185 let encoded = postcard::to_stdvec(&guid).unwrap();
186 assert_eq!(postcard::from_bytes::<Guid>(&encoded).unwrap(), guid);
187 }
188
189 #[test]
190 fn generated_guid_is_version_4_with_rfc_variant() {
191 let guid = Guid::new_v4();
192 assert_eq!(guid.data3 >> 12, 4);
193 assert_eq!(guid.data4[0] >> 6, 2);
194 }
195}