| // Copyright 2018 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. |
| |
| use self::constants::{ |
| CHARACTERISTIC_NUMBERS, |
| CUSTOM_SERVICE_UUIDS, |
| DESCRIPTOR_NUMBERS, |
| SERVICE_UUIDS, |
| }; |
| |
| mod constants; |
| |
| /// An assigned number, code, or identifier for a concept in the Bluetooth wireless standard. |
| /// Includes an associated abbreviation and human-readable name for the number. |
| #[derive(Clone, Copy, Debug, PartialEq)] |
| pub struct AssignedNumber { |
| /// Assigned uuid in canonical 8-4-4-4-12 string format |
| pub number: &'static str, |
| /// Short abbreviation of the `name` |
| pub abbreviation: Option<&'static str>, |
| /// Human readable name |
| pub name: &'static str, |
| } |
| |
| impl AssignedNumber { |
| /// Tests if an `identifier` matches any of the fields in the assigned number. |
| /// Matches are case-insensitive. Matches on the number can be in the canonical |
| /// 8-4-4-4-12 uuid string format or the first 8 digits of the uuid with or without |
| /// leading 0s. |
| pub fn matches(&self, identifier: &str) -> bool { |
| let identifier = &identifier.to_uppercase(); |
| self.matches_abbreviation(identifier) || |
| self.matches_name(identifier) || |
| self.matches_number(identifier) |
| } |
| |
| fn matches_abbreviation(&self, identifier: &str) -> bool { |
| self.abbreviation.map(|abbr| abbr == identifier).unwrap_or(false) |
| } |
| |
| fn matches_name(&self, identifier: &str) -> bool { |
| self.name.to_uppercase() == identifier |
| } |
| |
| fn short_id(&self) -> &str { |
| self.number |
| .split("-") |
| .next() |
| .expect("split iter always has at least 1 item") |
| } |
| |
| /// Matches full uuid or short form of Bluetooth SIG assigned numbers. |
| /// Precondition: identifier should already be uppercase when passed into the method. |
| fn matches_number(&self, identifier: &str) -> bool { |
| let identifier = if identifier.starts_with("0X") { &identifier[2..] } else { identifier }; |
| if identifier.len() == 32 + 4 { |
| self.number == identifier |
| } else { |
| // pad out the identifier with leading zeros to a width of 8 |
| self.short_id() == format!("{:0>8}", identifier) |
| } |
| } |
| } |
| |
| /// Search for the Bluetooth SIG number for a given service identifier |
| /// and return associated information. `identifier` can be a human readable |
| /// string, abbreviation, or the number in full uuid format or shortened forms |
| pub fn find_service_uuid(identifier: &str) -> Option<AssignedNumber> { |
| SERVICE_UUIDS |
| .iter() |
| .chain(CUSTOM_SERVICE_UUIDS.iter()) |
| .find(|sn| sn.matches(identifier)) |
| .map(|&an| an) |
| } |
| |
| /// Search for the Bluetooth SIG number for a given characteristic identifier |
| /// and return associated information. `identifier` can be a human readable |
| /// string or the number in full uuid format or shortened forms |
| pub fn find_characteristic_number(identifier: &str) -> Option<AssignedNumber> { |
| CHARACTERISTIC_NUMBERS.iter().find(|cn| cn.matches(identifier)).map(|&an| an) |
| } |
| |
| /// Search for the Bluetooth SIG number for a given descriptor identifier |
| /// and return associated information. `identifier` can be a human readable |
| /// string or the number in full uuid format or shortened forms |
| pub fn find_descriptor_number(identifier: &str) -> Option<AssignedNumber> { |
| DESCRIPTOR_NUMBERS.iter().find(|dn| dn.matches(identifier)).map(|&an| an) |
| } |
| |
| #[macro_export] |
| macro_rules! assigned_number { |
| ($num:expr, $abbr:expr, $name:expr) => ( |
| AssignedNumber { |
| number: concat!("0000", $num, "-0000-1000-8000-00805F9B34FB"), |
| abbreviation: Some($abbr), |
| name: $name |
| } |
| ); |
| ($num:expr, $name:expr) => ( |
| AssignedNumber { |
| number: concat!("0000", $num, "-0000-1000-8000-00805F9B34FB"), |
| abbreviation: None, |
| name: $name |
| } |
| ); |
| } |
| |
| |
| #[cfg(test)] |
| mod tests { |
| use super::{ |
| find_characteristic_number, |
| find_descriptor_number, |
| find_service_uuid, |
| CHARACTERISTIC_NUMBERS, |
| CUSTOM_SERVICE_UUIDS, |
| DESCRIPTOR_NUMBERS, |
| SERVICE_UUIDS, |
| }; |
| |
| #[test] |
| fn test_find_characteristic_number() { |
| assert_eq!(find_characteristic_number("Device Name"), Some(CHARACTERISTIC_NUMBERS[0])); |
| assert_eq!(find_characteristic_number("aPPEARANCE"), Some(CHARACTERISTIC_NUMBERS[1])); |
| assert_eq!(find_characteristic_number("fake characteristic name"), None); |
| assert_eq!(find_characteristic_number("2a00"), Some(CHARACTERISTIC_NUMBERS[0])); |
| assert_eq!(find_characteristic_number("zzzz"), None); |
| } |
| |
| #[test] |
| fn test_find_descriptor_number() { |
| assert_eq!(find_descriptor_number("Report reference"), Some(DESCRIPTOR_NUMBERS[8])); |
| assert_eq!(find_descriptor_number("fake descriptor name"), None); |
| assert_eq!( |
| find_descriptor_number("00002908-0000-1000-8000-00805F9B34FB"), |
| Some(DESCRIPTOR_NUMBERS[8]) |
| ); |
| assert_eq!(find_descriptor_number("2908"), Some(DESCRIPTOR_NUMBERS[8])); |
| assert_eq!(find_descriptor_number("zzzz"), None); |
| } |
| |
| #[test] |
| fn test_find_service_uuid() { |
| assert_eq!(find_service_uuid("GAP"), Some(SERVICE_UUIDS[0])); |
| assert_eq!(find_service_uuid("Gap"), Some(SERVICE_UUIDS[0])); |
| assert_eq!(find_service_uuid("gap"), Some(SERVICE_UUIDS[0])); |
| assert_eq!(find_service_uuid("hIdS"), Some(SERVICE_UUIDS[16])); |
| assert_eq!(find_service_uuid("XYZ"), None); |
| assert_eq!(find_service_uuid("Human Interface Device Service"), Some(SERVICE_UUIDS[16])); |
| assert_eq!(find_service_uuid("Fake Service Name"), None); |
| assert_eq!(find_service_uuid("183A"), Some(SERVICE_UUIDS[39])); |
| assert_eq!(find_service_uuid("0x183a"), Some(SERVICE_UUIDS[39])); |
| assert_eq!(find_service_uuid("0000183a"), Some(SERVICE_UUIDS[39])); |
| assert_eq!(find_service_uuid("0000183A-0000-1000-8000-00805F9B34FB"), Some(SERVICE_UUIDS[39])); |
| assert_eq!(find_service_uuid("0000183A-0000-1000-8000-000000000000"), None); |
| assert_eq!(find_service_uuid("ZZZZZZZZ"), None); |
| assert_eq!(find_service_uuid("ZZZZZZZZ-0000-1000-8000-00805F9B34FB"), None); |
| // found in CUSTOM_SERVICE_UUIDS |
| assert_eq!(find_service_uuid("fdcf"), Some(CUSTOM_SERVICE_UUIDS[0])); |
| assert_eq!(find_service_uuid("FDE2"), Some(CUSTOM_SERVICE_UUIDS[19])); |
| } |
| } |