bt_common/
uuids.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/// A Uuid as defined by the Core Specification (v5.4, Vol 3, Part B, Sec 2.5.1)
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7pub struct Uuid(uuid::Uuid);
8
9impl Uuid {
10    // Non-changing parts of the Bluetooth Base UUID, for easy comparison and
11    // construction.
12    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
155/// Constructs an AssignedUuid from a 16-bit UUID and a name, and an optional id
156/// string.
157macro_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        // Should shorten the UUID16s
208        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        // Should shorten the UUID16s
221        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        // This is the "Running Speed and Cadence" service.
230        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        // The "Wind Chill" characteristic.
238        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        // The "VideoSource" service class uuid
246        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        // Assigned UUID info does not need to be unique
258        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        // Assigning the same UUID twice favors the second one.
265        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}