| // Copyright 2023 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /// A Uuid as defined by the Core Specification (v5.4, Vol 3, Part B, Sec 2.5.1) |
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
| pub struct Uuid(uuid::Uuid); |
| |
| impl Uuid { |
| // Non-changing parts of the Bluetooth Base UUID, for easy comparison and |
| // construction. |
| const BASE_UUID_B_PART: u16 = 0x0000; |
| const BASE_UUID_C_PART: u16 = 0x1000; |
| const BASE_UUID_D_PART: [u8; 8] = [0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB]; |
| |
| pub const fn from_u16(value: u16) -> Self { |
| Self::from_u32(value as u32) |
| } |
| |
| pub const fn from_u32(value: u32) -> Self { |
| Uuid(uuid::Uuid::from_fields( |
| value, |
| Self::BASE_UUID_B_PART, |
| Self::BASE_UUID_C_PART, |
| &Self::BASE_UUID_D_PART, |
| )) |
| } |
| |
| pub fn to_u16(&self) -> Option<u16> { |
| let x: u32 = self.to_u32()?; |
| x.try_into().ok() |
| } |
| |
| pub fn to_u32(&self) -> Option<u32> { |
| let (first, second, third, final_bytes) = self.0.as_fields(); |
| if second != Uuid::BASE_UUID_B_PART |
| || third != Uuid::BASE_UUID_C_PART |
| || final_bytes != &Uuid::BASE_UUID_D_PART |
| { |
| return None; |
| } |
| Some(first) |
| } |
| |
| pub fn recognize(self) -> RecognizedUuid { |
| self.into() |
| } |
| } |
| |
| impl From<Uuid> for uuid::Uuid { |
| fn from(value: Uuid) -> Self { |
| value.0 |
| } |
| } |
| |
| impl From<uuid::Uuid> for Uuid { |
| fn from(value: uuid::Uuid) -> Self { |
| Uuid(value) |
| } |
| } |
| |
| impl core::fmt::Display for Uuid { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| if let Some(u) = self.to_u16() { |
| return write!(f, "{u:#x}"); |
| } |
| if let Some(u) = self.to_u32() { |
| return write!(f, "{u:#x}"); |
| } |
| write!(f, "{}", self.0.as_hyphenated()) |
| } |
| } |
| |
| impl core::str::FromStr for Uuid { |
| type Err = crate::packet_encoding::Error; |
| |
| fn from_str(s: &str) -> core::result::Result<Uuid, Self::Err> { |
| if s.len() == 4 || s.len() == 6 { |
| return match u16::from_str_radix(&s[s.len() - 4..], 16) { |
| Ok(short) => Ok(Uuid::from_u16(short)), |
| Err(_) => Err(crate::packet_encoding::Error::InvalidParameter(s.to_owned())), |
| }; |
| } |
| match uuid::Uuid::parse_str(s) { |
| Ok(uuid) => Ok(Uuid(uuid)), |
| Err(e) => Err(crate::packet_encoding::Error::Uuid(e)), |
| } |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub struct AssignedUuid { |
| pub uuid: Uuid, |
| pub name: String, |
| pub id: Option<String>, |
| } |
| |
| #[derive(Clone, Debug)] |
| pub enum RecognizedUuid { |
| Assigned(AssignedUuid), |
| Unrecognized(Uuid), |
| } |
| |
| impl RecognizedUuid { |
| fn as_uuid(&self) -> &Uuid { |
| match self { |
| RecognizedUuid::Assigned(AssignedUuid { uuid, .. }) => uuid, |
| RecognizedUuid::Unrecognized(uuid) => uuid, |
| } |
| } |
| } |
| |
| impl From<Uuid> for RecognizedUuid { |
| fn from(value: Uuid) -> Self { |
| if let Some(assigned) = characteristic_uuids::CHARACTERISTIC_UUIDS.get(&value) { |
| return Self::Assigned(assigned.clone()); |
| } |
| if let Some(assigned) = service_class::SERVICE_CLASS_UUIDS.get(&value) { |
| return Self::Assigned(assigned.clone()); |
| } |
| if let Some(assigned) = service_uuids::SERVICE_UUIDS.get(&value) { |
| return Self::Assigned(assigned.clone()); |
| } |
| Self::Unrecognized(value) |
| } |
| } |
| |
| impl std::ops::Deref for RecognizedUuid { |
| type Target = Uuid; |
| |
| fn deref(&self) -> &Self::Target { |
| self.as_uuid() |
| } |
| } |
| |
| impl std::ops::DerefMut for RecognizedUuid { |
| fn deref_mut(&mut self) -> &mut Self::Target { |
| match self { |
| RecognizedUuid::Assigned(AssignedUuid { uuid, .. }) => uuid, |
| RecognizedUuid::Unrecognized(uuid) => uuid, |
| } |
| } |
| } |
| |
| impl std::fmt::Display for RecognizedUuid { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| write!(f, "{}", self.as_uuid())?; |
| if let RecognizedUuid::Assigned(AssignedUuid { name, .. }) = self { |
| write!(f, " ({name})")?; |
| } |
| Ok(()) |
| } |
| } |
| |
| /// Constructs an AssignedUuid from a 16-bit UUID and a name, and an optional id |
| /// string. |
| macro_rules! assigned_uuid { |
| ($uuid:expr, $name:expr) => { |
| AssignedUuid { uuid: Uuid::from_u16($uuid), name: String::from($name), id: None } |
| }; |
| ($uuid:expr, $name:expr, $id:expr) => { |
| AssignedUuid { |
| uuid: Uuid::from_u16($uuid), |
| name: String::from($name), |
| id: Some(String::from($id)), |
| } |
| }; |
| } |
| |
| macro_rules! assigned_uuid_map { |
| ( $(($uuid:expr, $name:expr, $id:expr)),* $(,)? ) => { |
| { |
| let mut new_map = std::collections::HashMap::new(); |
| $( |
| let _ = new_map.insert(Uuid::from_u16($uuid), assigned_uuid!($uuid, $name, $id)); |
| )* |
| new_map |
| } |
| }; |
| ($(($uuid:expr, $name:expr)),* $(,)? ) => { |
| { |
| let mut new_map = std::collections::HashMap::new(); |
| $( |
| let _ = new_map.insert(Uuid::from_u16($uuid), assigned_uuid!($uuid, $name)); |
| )* |
| new_map |
| } |
| }; |
| } |
| |
| pub mod characteristic_uuids; |
| pub mod descriptors; |
| pub mod service_class; |
| pub mod service_uuids; |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| #[test] |
| fn uuid16() { |
| let uuid = Uuid::from_u16(0x180d); |
| assert_eq!( |
| uuid::Uuid::parse_str("0000180d-0000-1000-8000-00805f9b34fb").unwrap(), |
| uuid::Uuid::from(uuid), |
| ); |
| // Should shorten the UUID16s |
| assert_eq!("0x180d", uuid.to_string()); |
| let as16: u16 = uuid.to_u16().unwrap(); |
| assert_eq!(0x180d, as16); |
| } |
| |
| #[test] |
| fn uuid32() { |
| let uuid = Uuid::from_u32(0xC0DECAFE); |
| assert_eq!( |
| uuid::Uuid::parse_str("c0decafe-0000-1000-8000-00805f9b34fb").unwrap(), |
| uuid::Uuid::from(uuid), |
| ); |
| // Should shorten the UUID16s |
| assert_eq!("0xc0decafe", uuid.to_string()); |
| assert!(uuid.to_u16().is_none()); |
| let as32: u32 = uuid.to_u32().unwrap(); |
| assert_eq!(0xc0decafe, as32); |
| } |
| |
| #[test] |
| fn recognition() { |
| // This is the "Running Speed and Cadence" service. |
| let uuid = Uuid::from_u16(0x1814); |
| assert!(uuid.recognize().to_string().contains("Cadence")); |
| assert!( |
| uuid.recognize().to_string().contains("0x1814"), |
| "{} should contain the shortened uuid", |
| uuid.to_string() |
| ); |
| // The "Wind Chill" characteristic. |
| let uuid = Uuid::from_u16(0x2A79).recognize(); |
| assert!(uuid.to_string().contains("Wind Chill")); |
| assert!( |
| uuid.to_string().contains("0x2a79"), |
| "{} should contain the shortened uuid", |
| uuid.to_string() |
| ); |
| // The "VideoSource" service class uuid |
| let uuid = Uuid::from_u16(0x1303).recognize(); |
| assert!(uuid.to_string().contains("VideoSource")); |
| assert!( |
| uuid.to_string().contains("0x1303"), |
| "{} should contain the shortened uuid", |
| uuid.to_string() |
| ); |
| } |
| |
| #[test] |
| fn assigned_uuid_map() { |
| // Assigned UUID info does not need to be unique |
| let test_map = |
| assigned_uuid_map!((0x1234, "Test", "org.example"), (0x5678, "Test", "org.example"),); |
| assert!(test_map.contains_key(&Uuid::from_u16(0x1234))); |
| assert!(test_map.contains_key(&Uuid::from_u16(0x5678))); |
| assert!(!test_map.contains_key(&Uuid::from_u16(0x9ABC))); |
| |
| // Assigning the same UUID twice favors the second one. |
| let test_map = |
| assigned_uuid_map!((0x1234, "Test", "org.example"), (0x1234, "Test 2", "com.example"),); |
| |
| assert_eq!(test_map.get(&Uuid::from_u16(0x1234)).unwrap().name, "Test 2"); |
| } |
| |
| #[test] |
| fn parse() { |
| assert_eq!("1814".parse(), Ok(Uuid::from_u16(0x1814))); |
| assert_eq!("0x2a79".parse(), Ok(Uuid::from_u16(0x2a79))); |
| let unknown_long_uuid = "2686f39c-bada-4658-854a-a62e7e5e8b8d"; |
| assert_eq!( |
| unknown_long_uuid.parse(), |
| Ok::<Uuid, crate::packet_encoding::Error>( |
| uuid::Uuid::parse_str(unknown_long_uuid).unwrap().into() |
| ) |
| ); |
| } |
| } |