blob: 53b544c28defa6dd9f31632a17d006c8e61f2013 [file] [log] [blame]
// 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()
)
);
}
}