bluez_async/
characteristic.rs

1use bitflags::bitflags;
2use bluez_generated::OrgBluezGattCharacteristic1Properties;
3use dbus::Path;
4use serde::{Deserialize, Serialize};
5use std::convert::{TryFrom, TryInto};
6use std::fmt::{self, Display, Formatter};
7use uuid::Uuid;
8
9use crate::{BluetoothError, ServiceId};
10
11/// Opaque identifier for a GATT characteristic on a Bluetooth device.
12#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
13pub struct CharacteristicId {
14    #[serde(with = "crate::serde_path")]
15    pub(crate) object_path: Path<'static>,
16}
17
18impl CharacteristicId {
19    pub(crate) fn new(object_path: &str) -> Self {
20        Self {
21            object_path: object_path.to_owned().into(),
22        }
23    }
24
25    /// Get the ID of the service on which this characteristic was advertised.
26    pub fn service(&self) -> ServiceId {
27        let index = self
28            .object_path
29            .rfind('/')
30            .expect("CharacteristicId object_path must contain a slash.");
31        ServiceId::new(&self.object_path[0..index])
32    }
33}
34
35impl From<CharacteristicId> for Path<'static> {
36    fn from(id: CharacteristicId) -> Self {
37        id.object_path
38    }
39}
40
41impl Display for CharacteristicId {
42    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
43        write!(
44            f,
45            "{}",
46            self.object_path
47                .to_string()
48                .strip_prefix("/org/bluez/")
49                .ok_or(fmt::Error)?
50        )
51    }
52}
53
54/// Information about a GATT characteristic on a Bluetooth device.
55#[derive(Clone, Debug, Eq, PartialEq)]
56pub struct CharacteristicInfo {
57    /// An opaque identifier for the characteristic on the device, including a reference to which
58    /// adapter it was discovered on.
59    pub id: CharacteristicId,
60    /// The 128-bit UUID of the characteristic.
61    pub uuid: Uuid,
62    /// The set of flags (a.k.a. properties) of the characteristic, defining how the characteristic
63    /// can be used.
64    pub flags: CharacteristicFlags,
65    /// Characteristic MTU.
66    ///
67    /// This is valid both for ReadValue and WriteValue, but either method can use long procedures
68    /// when supported.
69    pub mtu: Option<u16>,
70}
71
72impl CharacteristicInfo {
73    pub(crate) fn from_properties(
74        id: CharacteristicId,
75        characteristic_properties: OrgBluezGattCharacteristic1Properties,
76    ) -> Result<Self, BluetoothError> {
77        let uuid = Uuid::parse_str(
78            characteristic_properties
79                .uuid()
80                .ok_or(BluetoothError::RequiredPropertyMissing("UUID"))?,
81        )?;
82        let flags = characteristic_properties
83            .flags()
84            .ok_or(BluetoothError::RequiredPropertyMissing("Flags"))?
85            .as_slice()
86            .try_into()?;
87        Ok(Self {
88            id,
89            uuid,
90            flags,
91            mtu: characteristic_properties.mtu(),
92        })
93    }
94}
95
96bitflags! {
97    /// The set of flags (a.k.a. properties) of a characteristic, defining how the characteristic
98    /// can be used.
99    #[derive(Copy, Clone, Debug, Eq, PartialEq)]
100    pub struct CharacteristicFlags: u16 {
101        const BROADCAST = 0x01;
102        const READ = 0x02;
103        const WRITE_WITHOUT_RESPONSE = 0x04;
104        const WRITE = 0x08;
105        const NOTIFY = 0x10;
106        const INDICATE = 0x20;
107        const SIGNED_WRITE = 0x40;
108        const EXTENDED_PROPERTIES = 0x80;
109        const RELIABLE_WRITE = 0x100;
110        const WRITABLE_AUXILIARIES = 0x200;
111        const ENCRYPT_READ = 0x400;
112        const ENCRYPT_WRITE = 0x800;
113        const ENCRYPT_AUTHENTICATED_READ = 0x1000;
114        const ENCRYPT_AUTHENTICATED_WRITE = 0x2000;
115        const AUTHORIZE = 0x4000;
116    }
117}
118
119impl TryFrom<&[String]> for CharacteristicFlags {
120    type Error = BluetoothError;
121
122    fn try_from(value: &[String]) -> Result<Self, BluetoothError> {
123        let mut flags = Self::empty();
124        for flag_string in value {
125            let flag = match flag_string.as_ref() {
126                "broadcast" => Self::BROADCAST,
127                "read" => Self::READ,
128                "write-without-response" => Self::WRITE_WITHOUT_RESPONSE,
129                "write" => Self::WRITE,
130                "notify" => Self::NOTIFY,
131                "indicate" => Self::INDICATE,
132                "authenticated-signed-writes" => Self::SIGNED_WRITE,
133                "extended-properties" => Self::EXTENDED_PROPERTIES,
134                "reliable-write" => Self::RELIABLE_WRITE,
135                "writable-auxiliaries" => Self::WRITABLE_AUXILIARIES,
136                "encrypt-read" => Self::ENCRYPT_READ,
137                "encrypt-write" => Self::ENCRYPT_WRITE,
138                "encrypt-authenticated-read" => Self::ENCRYPT_AUTHENTICATED_READ,
139                "encrypt-authenticated-write" => Self::ENCRYPT_AUTHENTICATED_WRITE,
140                "authorize" => Self::AUTHORIZE,
141                _ => return Err(BluetoothError::FlagParseError(flag_string.to_owned())),
142            };
143            flags.insert(flag);
144        }
145        Ok(flags)
146    }
147}
148
149impl TryFrom<Vec<String>> for CharacteristicFlags {
150    type Error = BluetoothError;
151
152    fn try_from(value: Vec<String>) -> Result<Self, BluetoothError> {
153        value.as_slice().try_into()
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160    use dbus::arg::{PropMap, Variant};
161    use std::{collections::HashMap, convert::TryInto};
162
163    #[test]
164    fn characteristic_service() {
165        let service_id = ServiceId::new("/org/bluez/hci0/dev_11_22_33_44_55_66/service0022");
166        let characteristic_id =
167            CharacteristicId::new("/org/bluez/hci0/dev_11_22_33_44_55_66/service0022/char0033");
168        assert_eq!(characteristic_id.service(), service_id);
169    }
170
171    #[test]
172    fn parse_flags() {
173        let flags: CharacteristicFlags = vec!["read".to_string(), "encrypt-write".to_string()]
174            .try_into()
175            .unwrap();
176        assert_eq!(
177            flags,
178            CharacteristicFlags::READ | CharacteristicFlags::ENCRYPT_WRITE
179        )
180    }
181
182    #[test]
183    fn parse_flags_fail() {
184        let flags: Result<CharacteristicFlags, BluetoothError> =
185            vec!["read".to_string(), "invalid flag".to_string()].try_into();
186        assert!(
187            matches!(flags, Err(BluetoothError::FlagParseError(string)) if string == "invalid flag")
188        );
189    }
190
191    #[test]
192    fn to_string() {
193        let characteristic_id =
194            CharacteristicId::new("/org/bluez/hci0/dev_11_22_33_44_55_66/service0022/char0033");
195        assert_eq!(
196            characteristic_id.to_string(),
197            "hci0/dev_11_22_33_44_55_66/service0022/char0033"
198        );
199    }
200
201    #[test]
202    fn characteristic_info_minimal() {
203        let id =
204            CharacteristicId::new("/org/bluez/hci0/dev_11_22_33_44_55_66/service0022/char0033");
205        let mut characteristic_properties: PropMap = HashMap::new();
206        characteristic_properties.insert(
207            "UUID".to_string(),
208            Variant(Box::new("ebe0ccb9-7a0a-4b0c-8a1a-6ff2997da3a6".to_string())),
209        );
210        characteristic_properties
211            .insert("Flags".to_string(), Variant(Box::new(Vec::<String>::new())));
212
213        let characteristic = CharacteristicInfo::from_properties(
214            id.clone(),
215            OrgBluezGattCharacteristic1Properties(&characteristic_properties),
216        )
217        .unwrap();
218        assert_eq!(
219            characteristic,
220            CharacteristicInfo {
221                id,
222                uuid: Uuid::from_u128(0xebe0ccb9_7a0a_4b0c_8a1a_6ff2997da3a6),
223                flags: CharacteristicFlags::empty(),
224                mtu: None
225            }
226        );
227    }
228}