fuchsia_bluetooth/
assigned_numbers.rs

1// Copyright 2018 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
5use self::constants::{
6    CHARACTERISTIC_NUMBERS, CUSTOM_SERVICE_UUIDS, DESCRIPTOR_NUMBERS, SERVICE_UUIDS,
7};
8use crate::types::Uuid;
9
10pub(crate) mod constants;
11pub mod ltv;
12
13/// An assigned number, code, or identifier for a concept in the Bluetooth wireless standard.
14/// Includes an associated abbreviation and human-readable name for the number.
15#[derive(Clone, Copy, Debug, PartialEq)]
16pub struct AssignedNumber {
17    /// 16-bit Bluetooth UUID.
18    pub number: u16,
19    /// Short abbreviation of the `name`
20    pub abbreviation: Option<&'static str>,
21    /// Human readable name
22    pub name: &'static str,
23}
24
25impl AssignedNumber {
26    /// Tests if an `identifier` matches any of the fields in the assigned number.
27    /// Matches are case-insensitive. Matches on the number can be in the canonical
28    /// 8-4-4-4-12 uuid string format or the first 8 digits of the uuid with or without
29    /// leading 0s.
30    pub fn matches(&self, identifier: &str) -> bool {
31        let identifier = &identifier.to_uppercase();
32        self.matches_abbreviation(identifier)
33            || self.matches_name(identifier)
34            || self.matches_number(identifier)
35    }
36
37    fn matches_abbreviation(&self, identifier: &str) -> bool {
38        self.abbreviation.map(|abbr| abbr == identifier).unwrap_or(false)
39    }
40
41    fn matches_name(&self, identifier: &str) -> bool {
42        self.name.to_uppercase() == identifier
43    }
44
45    /// Matches full uuid or short form of Bluetooth SIG assigned numbers.
46    /// Precondition: identifier should already be uppercase when passed into the method.
47    fn matches_number(&self, identifier: &str) -> bool {
48        let identifier = if identifier.starts_with("0X") { &identifier[2..] } else { identifier };
49        let string = Uuid::new16(self.number).to_string().to_uppercase();
50        if identifier.len() == 32 + 4 {
51            identifier == string
52        } else {
53            let short_form =
54                string.split("-").next().expect("split iter always has at least 1 item");
55            // pad out the identifier with leading zeros to a width of 8
56            short_form == format!("{:0>8}", identifier)
57        }
58    }
59}
60
61/// Search for the Bluetooth SIG number for a given service identifier
62/// and return associated information. `identifier` can be a human readable
63/// string, abbreviation, or the number in full uuid format or shortened forms
64pub fn find_service_uuid(identifier: &str) -> Option<AssignedNumber> {
65    SERVICE_UUIDS
66        .iter()
67        .chain(CUSTOM_SERVICE_UUIDS.iter())
68        .find(|sn| sn.matches(identifier))
69        .copied()
70}
71
72/// Search for the Bluetooth SIG number for a given characteristic identifier
73/// and return associated information. `identifier` can be a human readable
74/// string or the number in full uuid format or shortened forms
75pub fn find_characteristic_number(identifier: &str) -> Option<AssignedNumber> {
76    CHARACTERISTIC_NUMBERS.iter().find(|cn| cn.matches(identifier)).map(|&an| an)
77}
78
79/// Search for the Bluetooth SIG number for a given descriptor identifier
80/// and return associated information. `identifier` can be a human readable
81/// string or the number in full uuid format or shortened forms
82pub fn find_descriptor_number(identifier: &str) -> Option<AssignedNumber> {
83    DESCRIPTOR_NUMBERS.iter().find(|dn| dn.matches(identifier)).map(|&an| an)
84}
85
86#[macro_export]
87macro_rules! assigned_number {
88    ($num:expr, $abbr:expr, $name:expr) => {
89        AssignedNumber { number: $num, abbreviation: Some($abbr), name: $name }
90    };
91    ($num:expr, $name:expr) => {
92        AssignedNumber { number: $num, abbreviation: None, name: $name }
93    };
94}
95
96#[cfg(test)]
97mod tests {
98    use super::{
99        find_characteristic_number, find_descriptor_number, find_service_uuid,
100        CHARACTERISTIC_NUMBERS, CUSTOM_SERVICE_UUIDS, DESCRIPTOR_NUMBERS, SERVICE_UUIDS,
101    };
102
103    #[test]
104    fn test_find_characteristic_number() {
105        assert_eq!(find_characteristic_number("Device Name"), Some(CHARACTERISTIC_NUMBERS[0]));
106        assert_eq!(find_characteristic_number("aPPEARANCE"), Some(CHARACTERISTIC_NUMBERS[1]));
107        assert_eq!(find_characteristic_number("fake characteristic name"), None);
108        assert_eq!(find_characteristic_number("2a00"), Some(CHARACTERISTIC_NUMBERS[0]));
109        assert_eq!(find_characteristic_number("zzzz"), None);
110    }
111
112    #[test]
113    fn test_find_descriptor_number() {
114        assert_eq!(find_descriptor_number("Report reference"), Some(DESCRIPTOR_NUMBERS[8]));
115        assert_eq!(find_descriptor_number("fake descriptor name"), None);
116        assert_eq!(
117            find_descriptor_number("00002908-0000-1000-8000-00805F9B34FB"),
118            Some(DESCRIPTOR_NUMBERS[8])
119        );
120        assert_eq!(find_descriptor_number("2908"), Some(DESCRIPTOR_NUMBERS[8]));
121        assert_eq!(find_descriptor_number("zzzz"), None);
122    }
123
124    #[test]
125    fn test_find_service_uuid() {
126        assert_eq!(find_service_uuid("GAP"), Some(SERVICE_UUIDS[0]));
127        assert_eq!(find_service_uuid("Gap"), Some(SERVICE_UUIDS[0]));
128        assert_eq!(find_service_uuid("gap"), Some(SERVICE_UUIDS[0]));
129        assert_eq!(find_service_uuid("hIdS"), Some(SERVICE_UUIDS[16]));
130        assert_eq!(find_service_uuid("XYZ"), None);
131        assert_eq!(find_service_uuid("Human Interface Device Service"), Some(SERVICE_UUIDS[16]));
132        assert_eq!(find_service_uuid("Fake Service Name"), None);
133        assert_eq!(find_service_uuid("183A"), Some(SERVICE_UUIDS[39]));
134        assert_eq!(find_service_uuid("0x183a"), Some(SERVICE_UUIDS[39]));
135        assert_eq!(find_service_uuid("0000183a"), Some(SERVICE_UUIDS[39]));
136        assert_eq!(
137            find_service_uuid("0000183A-0000-1000-8000-00805F9B34FB"),
138            Some(SERVICE_UUIDS[39])
139        );
140        assert_eq!(
141            find_service_uuid("0000183a-0000-1000-8000-00805f9b34fb"),
142            Some(SERVICE_UUIDS[39])
143        );
144        assert_eq!(find_service_uuid("0000183A-0000-1000-8000-000000000000"), None);
145        assert_eq!(find_service_uuid("ZZZZZZZZ"), None);
146        assert_eq!(find_service_uuid("ZZZZZZZZ-0000-1000-8000-00805F9B34FB"), None);
147        // found in CUSTOM_SERVICE_UUIDS
148        assert_eq!(find_service_uuid("fdcf"), Some(CUSTOM_SERVICE_UUIDS[0]));
149        assert_eq!(find_service_uuid("FDE2"), Some(CUSTOM_SERVICE_UUIDS[19]));
150    }
151}