bluez_async/
characteristic.rs1use 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#[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 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#[derive(Clone, Debug, Eq, PartialEq)]
56pub struct CharacteristicInfo {
57 pub id: CharacteristicId,
60 pub uuid: Uuid,
62 pub flags: CharacteristicFlags,
65 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 #[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}