1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7pub struct Uuid(uuid::Uuid);
8
9impl Uuid {
10 const BASE_UUID_B_PART: u16 = 0x0000;
13 const BASE_UUID_C_PART: u16 = 0x1000;
14 const BASE_UUID_D_PART: [u8; 8] = [0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB];
15
16 pub const fn from_u16(value: u16) -> Self {
17 Self::from_u32(value as u32)
18 }
19
20 pub const fn from_u32(value: u32) -> Self {
21 Uuid(uuid::Uuid::from_fields(
22 value,
23 Self::BASE_UUID_B_PART,
24 Self::BASE_UUID_C_PART,
25 &Self::BASE_UUID_D_PART,
26 ))
27 }
28
29 pub fn to_u16(&self) -> Option<u16> {
30 let x: u32 = self.to_u32()?;
31 x.try_into().ok()
32 }
33
34 pub fn to_u32(&self) -> Option<u32> {
35 let (first, second, third, final_bytes) = self.0.as_fields();
36 if second != Uuid::BASE_UUID_B_PART
37 || third != Uuid::BASE_UUID_C_PART
38 || final_bytes != &Uuid::BASE_UUID_D_PART
39 {
40 return None;
41 }
42 Some(first)
43 }
44
45 pub fn recognize(self) -> RecognizedUuid {
46 self.into()
47 }
48}
49
50impl From<Uuid> for uuid::Uuid {
51 fn from(value: Uuid) -> Self {
52 value.0
53 }
54}
55
56impl From<uuid::Uuid> for Uuid {
57 fn from(value: uuid::Uuid) -> Self {
58 Uuid(value)
59 }
60}
61
62impl core::fmt::Display for Uuid {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 if let Some(u) = self.to_u16() {
65 return write!(f, "{u:#x}");
66 }
67 if let Some(u) = self.to_u32() {
68 return write!(f, "{u:#x}");
69 }
70 write!(f, "{}", self.0.as_hyphenated())
71 }
72}
73
74impl core::str::FromStr for Uuid {
75 type Err = crate::packet_encoding::Error;
76
77 fn from_str(s: &str) -> core::result::Result<Uuid, Self::Err> {
78 if s.len() == 4 || s.len() == 6 {
79 return match u16::from_str_radix(&s[s.len() - 4..], 16) {
80 Ok(short) => Ok(Uuid::from_u16(short)),
81 Err(_) => Err(crate::packet_encoding::Error::InvalidParameter(s.to_owned())),
82 };
83 }
84 match uuid::Uuid::parse_str(s) {
85 Ok(uuid) => Ok(Uuid(uuid)),
86 Err(e) => Err(crate::packet_encoding::Error::Uuid(e)),
87 }
88 }
89}
90
91#[derive(Clone, Debug)]
92pub struct AssignedUuid {
93 pub uuid: Uuid,
94 pub name: String,
95 pub id: Option<String>,
96}
97
98#[derive(Clone, Debug)]
99pub enum RecognizedUuid {
100 Assigned(AssignedUuid),
101 Unrecognized(Uuid),
102}
103
104impl RecognizedUuid {
105 fn as_uuid(&self) -> &Uuid {
106 match self {
107 RecognizedUuid::Assigned(AssignedUuid { uuid, .. }) => uuid,
108 RecognizedUuid::Unrecognized(uuid) => uuid,
109 }
110 }
111}
112
113impl From<Uuid> for RecognizedUuid {
114 fn from(value: Uuid) -> Self {
115 if let Some(assigned) = characteristic_uuids::CHARACTERISTIC_UUIDS.get(&value) {
116 return Self::Assigned(assigned.clone());
117 }
118 if let Some(assigned) = service_class::SERVICE_CLASS_UUIDS.get(&value) {
119 return Self::Assigned(assigned.clone());
120 }
121 if let Some(assigned) = service_uuids::SERVICE_UUIDS.get(&value) {
122 return Self::Assigned(assigned.clone());
123 }
124 Self::Unrecognized(value)
125 }
126}
127
128impl std::ops::Deref for RecognizedUuid {
129 type Target = Uuid;
130
131 fn deref(&self) -> &Self::Target {
132 self.as_uuid()
133 }
134}
135
136impl std::ops::DerefMut for RecognizedUuid {
137 fn deref_mut(&mut self) -> &mut Self::Target {
138 match self {
139 RecognizedUuid::Assigned(AssignedUuid { uuid, .. }) => uuid,
140 RecognizedUuid::Unrecognized(uuid) => uuid,
141 }
142 }
143}
144
145impl std::fmt::Display for RecognizedUuid {
146 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147 write!(f, "{}", self.as_uuid())?;
148 if let RecognizedUuid::Assigned(AssignedUuid { name, .. }) = self {
149 write!(f, " ({name})")?;
150 }
151 Ok(())
152 }
153}
154
155macro_rules! assigned_uuid {
158 ($uuid:expr, $name:expr) => {
159 AssignedUuid { uuid: Uuid::from_u16($uuid), name: String::from($name), id: None }
160 };
161 ($uuid:expr, $name:expr, $id:expr) => {
162 AssignedUuid {
163 uuid: Uuid::from_u16($uuid),
164 name: String::from($name),
165 id: Some(String::from($id)),
166 }
167 };
168}
169
170macro_rules! assigned_uuid_map {
171 ( $(($uuid:expr, $name:expr, $id:expr)),* $(,)? ) => {
172 {
173 let mut new_map = std::collections::HashMap::new();
174 $(
175 let _ = new_map.insert(Uuid::from_u16($uuid), assigned_uuid!($uuid, $name, $id));
176 )*
177 new_map
178 }
179 };
180 ($(($uuid:expr, $name:expr)),* $(,)? ) => {
181 {
182 let mut new_map = std::collections::HashMap::new();
183 $(
184 let _ = new_map.insert(Uuid::from_u16($uuid), assigned_uuid!($uuid, $name));
185 )*
186 new_map
187 }
188 };
189}
190
191pub mod characteristic_uuids;
192pub mod descriptors;
193pub mod service_class;
194pub mod service_uuids;
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 #[test]
201 fn uuid16() {
202 let uuid = Uuid::from_u16(0x180d);
203 assert_eq!(
204 uuid::Uuid::parse_str("0000180d-0000-1000-8000-00805f9b34fb").unwrap(),
205 uuid::Uuid::from(uuid),
206 );
207 assert_eq!("0x180d", uuid.to_string());
209 let as16: u16 = uuid.to_u16().unwrap();
210 assert_eq!(0x180d, as16);
211 }
212
213 #[test]
214 fn uuid32() {
215 let uuid = Uuid::from_u32(0xC0DECAFE);
216 assert_eq!(
217 uuid::Uuid::parse_str("c0decafe-0000-1000-8000-00805f9b34fb").unwrap(),
218 uuid::Uuid::from(uuid),
219 );
220 assert_eq!("0xc0decafe", uuid.to_string());
222 assert!(uuid.to_u16().is_none());
223 let as32: u32 = uuid.to_u32().unwrap();
224 assert_eq!(0xc0decafe, as32);
225 }
226
227 #[test]
228 fn recognition() {
229 let uuid = Uuid::from_u16(0x1814);
231 assert!(uuid.recognize().to_string().contains("Cadence"));
232 assert!(
233 uuid.recognize().to_string().contains("0x1814"),
234 "{} should contain the shortened uuid",
235 uuid.to_string()
236 );
237 let uuid = Uuid::from_u16(0x2A79).recognize();
239 assert!(uuid.to_string().contains("Wind Chill"));
240 assert!(
241 uuid.to_string().contains("0x2a79"),
242 "{} should contain the shortened uuid",
243 uuid.to_string()
244 );
245 let uuid = Uuid::from_u16(0x1303).recognize();
247 assert!(uuid.to_string().contains("VideoSource"));
248 assert!(
249 uuid.to_string().contains("0x1303"),
250 "{} should contain the shortened uuid",
251 uuid.to_string()
252 );
253 }
254
255 #[test]
256 fn assigned_uuid_map() {
257 let test_map =
259 assigned_uuid_map!((0x1234, "Test", "org.example"), (0x5678, "Test", "org.example"),);
260 assert!(test_map.contains_key(&Uuid::from_u16(0x1234)));
261 assert!(test_map.contains_key(&Uuid::from_u16(0x5678)));
262 assert!(!test_map.contains_key(&Uuid::from_u16(0x9ABC)));
263
264 let test_map =
266 assigned_uuid_map!((0x1234, "Test", "org.example"), (0x1234, "Test 2", "com.example"),);
267
268 assert_eq!(test_map.get(&Uuid::from_u16(0x1234)).unwrap().name, "Test 2");
269 }
270
271 #[test]
272 fn parse() {
273 assert_eq!("1814".parse(), Ok(Uuid::from_u16(0x1814)));
274 assert_eq!("0x2a79".parse(), Ok(Uuid::from_u16(0x2a79)));
275 let unknown_long_uuid = "2686f39c-bada-4658-854a-a62e7e5e8b8d";
276 assert_eq!(
277 unknown_long_uuid.parse(),
278 Ok::<Uuid, crate::packet_encoding::Error>(
279 uuid::Uuid::parse_str(unknown_long_uuid).unwrap().into()
280 )
281 );
282 }
283}