1use bluez_generated::OrgBluezDevice1Properties;
2use dbus::arg::{cast, PropMap, RefArg, Variant};
3use dbus::Path;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::fmt::{self, Display, Formatter};
7use std::str::FromStr;
8use uuid::Uuid;
9
10use crate::{AdapterId, BluetoothError, MacAddress};
11
12#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
16pub struct DeviceId {
17 #[serde(with = "crate::serde_path")]
18 pub(crate) object_path: Path<'static>,
19}
20
21impl DeviceId {
22 pub(crate) fn new(object_path: &str) -> Self {
23 Self {
24 object_path: object_path.to_owned().into(),
25 }
26 }
27
28 pub fn adapter(&self) -> AdapterId {
30 let index = self
31 .object_path
32 .rfind('/')
33 .expect("DeviceId object_path must contain a slash.");
34 AdapterId::new(&self.object_path[0..index])
35 }
36}
37
38impl From<DeviceId> for Path<'static> {
39 fn from(id: DeviceId) -> Self {
40 id.object_path
41 }
42}
43
44impl Display for DeviceId {
45 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
46 write!(
47 f,
48 "{}",
49 self.object_path
50 .to_string()
51 .strip_prefix("/org/bluez/")
52 .ok_or(fmt::Error)?
53 )
54 }
55}
56
57#[derive(Clone, Debug, Eq, PartialEq)]
60pub struct DeviceInfo {
61 pub id: DeviceId,
64 pub mac_address: MacAddress,
66 pub address_type: AddressType,
68 pub name: Option<String>,
71 pub appearance: Option<u16>,
73 pub services: Vec<Uuid>,
78 pub paired: bool,
80 pub connected: bool,
82 pub rssi: Option<i16>,
84 pub tx_power: Option<i16>,
86 pub manufacturer_data: HashMap<u16, Vec<u8>>,
88 pub service_data: HashMap<Uuid, Vec<u8>>,
91 pub services_resolved: bool,
93 pub alias: Option<String>,
95 pub class: Option<u32>,
98 pub bonded: bool,
101 pub icon: Option<String>,
103 pub trusted: bool,
105 pub blocked: bool,
108 pub legacy_pairing: bool,
112 pub modalias: Option<String>,
114 pub wake_allowed: bool,
116}
117
118impl DeviceInfo {
119 pub(crate) fn from_properties(
120 id: DeviceId,
121 device_properties: OrgBluezDevice1Properties,
122 ) -> Result<DeviceInfo, BluetoothError> {
123 let mac_address = device_properties
124 .address()
125 .ok_or(BluetoothError::RequiredPropertyMissing("Address"))?
126 .parse()?;
127 let address_type = device_properties
128 .address_type()
129 .ok_or(BluetoothError::RequiredPropertyMissing("AddressType"))?
130 .parse()?;
131 let services = get_services(device_properties);
132 let manufacturer_data = get_manufacturer_data(device_properties).unwrap_or_default();
133 let service_data = get_service_data(device_properties).unwrap_or_default();
134
135 Ok(DeviceInfo {
136 id,
137 mac_address,
138 address_type,
139 name: device_properties.name().cloned(),
140 appearance: device_properties.appearance(),
141 services,
142 paired: device_properties
143 .paired()
144 .ok_or(BluetoothError::RequiredPropertyMissing("Paired"))?,
145 connected: device_properties
146 .connected()
147 .ok_or(BluetoothError::RequiredPropertyMissing("Connected"))?,
148 rssi: device_properties.rssi(),
149 tx_power: device_properties.tx_power(),
150 manufacturer_data,
151 service_data,
152 services_resolved: device_properties
153 .services_resolved()
154 .ok_or(BluetoothError::RequiredPropertyMissing("ServicesResolved"))?,
155 alias: device_properties.alias().cloned(),
156 class: device_properties.class(),
157 bonded: device_properties.bonded().unwrap_or_default(),
158 icon: device_properties.icon().cloned(),
159 trusted: device_properties
160 .trusted()
161 .ok_or(BluetoothError::RequiredPropertyMissing("Trusted"))?,
162 blocked: device_properties
163 .blocked()
164 .ok_or(BluetoothError::RequiredPropertyMissing("Blocked"))?,
165 legacy_pairing: device_properties
166 .legacy_pairing()
167 .ok_or(BluetoothError::RequiredPropertyMissing("LegacyPairing"))?,
168 modalias: device_properties.modalias().cloned(),
169 wake_allowed: device_properties.wake_allowed().unwrap_or(false),
170 })
171 }
172}
173
174#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
176pub enum AddressType {
177 Public,
179 Random,
181}
182
183impl AddressType {
184 fn as_str(&self) -> &'static str {
185 match self {
186 Self::Public => "public",
187 Self::Random => "random",
188 }
189 }
190}
191
192impl Display for AddressType {
193 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
194 f.write_str(self.as_str())
195 }
196}
197
198impl FromStr for AddressType {
199 type Err = BluetoothError;
200
201 fn from_str(s: &str) -> Result<Self, Self::Err> {
202 match s {
203 "public" => Ok(Self::Public),
204 "random" => Ok(Self::Random),
205 _ => Err(BluetoothError::AddressTypeParseError(s.to_owned())),
206 }
207 }
208}
209
210fn get_manufacturer_data(
211 device_properties: OrgBluezDevice1Properties,
212) -> Option<HashMap<u16, Vec<u8>>> {
213 Some(convert_manufacturer_data(
214 device_properties.manufacturer_data()?,
215 ))
216}
217
218pub(crate) fn convert_manufacturer_data(
219 data: &HashMap<u16, Variant<Box<dyn RefArg>>>,
220) -> HashMap<u16, Vec<u8>> {
221 data.iter()
222 .filter_map(|(&k, v)| {
223 if let Some(v) = cast::<Vec<u8>>(&v.0) {
224 Some((k, v.to_owned()))
225 } else {
226 log::warn!("Manufacturer data had wrong type: {:?}", &v.0);
227 None
228 }
229 })
230 .collect()
231}
232
233fn get_service_data(
234 device_properties: OrgBluezDevice1Properties,
235) -> Option<HashMap<Uuid, Vec<u8>>> {
236 Some(convert_service_data(device_properties.service_data()?))
237}
238
239pub(crate) fn convert_service_data(data: &PropMap) -> HashMap<Uuid, Vec<u8>> {
240 data.iter()
241 .filter_map(|(k, v)| match Uuid::parse_str(k) {
242 Ok(uuid) => {
243 if let Some(v) = cast::<Vec<u8>>(&v.0) {
244 Some((uuid, v.to_owned()))
245 } else {
246 log::warn!("Service data had wrong type: {:?}", &v.0);
247 None
248 }
249 }
250 Err(err) => {
251 log::warn!("Error parsing service data UUID: {}", err);
252 None
253 }
254 })
255 .collect()
256}
257
258fn get_services(device_properties: OrgBluezDevice1Properties) -> Vec<Uuid> {
259 if let Some(uuids) = device_properties.uuids() {
260 convert_services(uuids)
261 } else {
262 vec![]
263 }
264}
265
266pub(crate) fn convert_services(uuids: &[String]) -> Vec<Uuid> {
267 uuids
268 .iter()
269 .filter_map(|uuid| {
270 Uuid::parse_str(uuid)
271 .map_err(|err| {
272 log::warn!("Error parsing service data UUID: {}", err);
273 err
274 })
275 .ok()
276 })
277 .collect()
278}
279
280#[cfg(test)]
281mod tests {
282 use crate::uuid_from_u32;
283
284 use super::*;
285
286 #[test]
287 fn device_adapter() {
288 let adapter_id = AdapterId::new("/org/bluez/hci0");
289 let device_id = DeviceId::new("/org/bluez/hci0/dev_11_22_33_44_55_66");
290 assert_eq!(device_id.adapter(), adapter_id);
291 }
292
293 #[test]
294 fn to_string() {
295 let device_id = DeviceId::new("/org/bluez/hci0/dev_11_22_33_44_55_66");
296 assert_eq!(device_id.to_string(), "hci0/dev_11_22_33_44_55_66");
297 }
298
299 #[test]
300 fn service_data() {
301 let uuid = uuid_from_u32(0x11223344);
302 let mut service_data: PropMap = HashMap::new();
303 service_data.insert(uuid.to_string(), Variant(Box::new(vec![1u8, 2, 3])));
304 let mut device_properties: PropMap = HashMap::new();
305 device_properties.insert("ServiceData".to_string(), Variant(Box::new(service_data)));
306
307 let mut expected_service_data = HashMap::new();
308 expected_service_data.insert(uuid, vec![1u8, 2, 3]);
309
310 assert_eq!(
311 get_service_data(OrgBluezDevice1Properties(&device_properties)),
312 Some(expected_service_data)
313 );
314 }
315
316 #[test]
317 fn manufacturer_data() {
318 let manufacturer_id = 0x1122;
319 let mut manufacturer_data: HashMap<u16, Variant<Box<dyn RefArg>>> = HashMap::new();
320 manufacturer_data.insert(manufacturer_id, Variant(Box::new(vec![1u8, 2, 3])));
321 let mut device_properties: PropMap = HashMap::new();
322 device_properties.insert(
323 "ManufacturerData".to_string(),
324 Variant(Box::new(manufacturer_data)),
325 );
326
327 let mut expected_manufacturer_data = HashMap::new();
328 expected_manufacturer_data.insert(manufacturer_id, vec![1u8, 2, 3]);
329
330 assert_eq!(
331 get_manufacturer_data(OrgBluezDevice1Properties(&device_properties)),
332 Some(expected_manufacturer_data)
333 );
334 }
335
336 #[test]
337 fn device_info_minimal() {
338 let id = DeviceId::new("/org/bluez/hci0/dev_11_22_33_44_55_66");
339 let mut device_properties: PropMap = HashMap::new();
340 device_properties.insert(
341 "Address".to_string(),
342 Variant(Box::new("00:11:22:33:44:55".to_string())),
343 );
344 device_properties.insert(
345 "AddressType".to_string(),
346 Variant(Box::new("public".to_string())),
347 );
348 device_properties.insert("Paired".to_string(), Variant(Box::new(false)));
349 device_properties.insert("Connected".to_string(), Variant(Box::new(false)));
350 device_properties.insert("ServicesResolved".to_string(), Variant(Box::new(false)));
351 device_properties.insert("Bonded".to_string(), Variant(Box::new(false)));
352 device_properties.insert("Trusted".to_string(), Variant(Box::new(false)));
353 device_properties.insert("Blocked".to_string(), Variant(Box::new(false)));
354 device_properties.insert("LegacyPairing".to_string(), Variant(Box::new(false)));
355
356 let device =
357 DeviceInfo::from_properties(id.clone(), OrgBluezDevice1Properties(&device_properties))
358 .unwrap();
359 assert_eq!(
360 device,
361 DeviceInfo {
362 id,
363 mac_address: "00:11:22:33:44:55".parse().unwrap(),
364 address_type: AddressType::Public,
365 name: None,
366 appearance: None,
367 services: vec![],
368 paired: false,
369 connected: false,
370 rssi: None,
371 tx_power: None,
372 manufacturer_data: HashMap::new(),
373 service_data: HashMap::new(),
374 services_resolved: false,
375 alias: None,
376 class: None,
377 bonded: false,
378 icon: None,
379 trusted: false,
380 blocked: false,
381 legacy_pairing: false,
382 modalias: None,
383 wake_allowed: false,
384 }
385 )
386 }
387
388 #[test]
389 fn get_services_none() {
390 let device_properties: PropMap = HashMap::new();
391
392 assert_eq!(
393 get_services(OrgBluezDevice1Properties(&device_properties)),
394 vec![]
395 )
396 }
397
398 #[test]
399 fn get_services_some() {
400 let uuid = uuid_from_u32(0x11223344);
401 let uuids = vec![uuid.to_string()];
402 let mut device_properties: PropMap = HashMap::new();
403 device_properties.insert("UUIDs".to_string(), Variant(Box::new(uuids)));
404
405 assert_eq!(
406 get_services(OrgBluezDevice1Properties(&device_properties)),
407 vec![uuid]
408 )
409 }
410
411 #[test]
412 fn address_type_parse() {
413 for &address_type in &[AddressType::Public, AddressType::Random] {
414 assert_eq!(
415 address_type.to_string().parse::<AddressType>().unwrap(),
416 address_type
417 );
418 }
419 }
420}