1use bluez_generated::OrgBluezDevice1Properties;
2use dbus::Path;
3use dbus::arg::{PropMap, RefArg, Variant, cast};
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 FromStr for DeviceId {
45 type Err = BluetoothError;
46
47 fn from_str(s: &str) -> Result<Self, Self::Err> {
48 if !s.starts_with("hci") {
49 return Err(BluetoothError::DeviceIdParseError(s.to_string()));
50 }
51
52 let path = Path::new(format!("/org/bluez/{}", s))
53 .map_err(|_| BluetoothError::DeviceIdParseError(s.to_string()))?;
54
55 Ok(DeviceId {
56 object_path: path.into_static(),
57 })
58 }
59}
60
61impl Display for DeviceId {
62 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
63 write!(
64 f,
65 "{}",
66 self.object_path
67 .to_string()
68 .strip_prefix("/org/bluez/")
69 .ok_or(fmt::Error)?
70 )
71 }
72}
73
74#[derive(Clone, Debug, Eq, PartialEq)]
77pub struct DeviceInfo {
78 pub id: DeviceId,
81 pub mac_address: MacAddress,
83 pub address_type: AddressType,
85 pub name: Option<String>,
88 pub appearance: Option<u16>,
90 pub services: Vec<Uuid>,
95 pub paired: bool,
97 pub connected: bool,
99 pub rssi: Option<i16>,
101 pub tx_power: Option<i16>,
103 pub manufacturer_data: HashMap<u16, Vec<u8>>,
105 pub service_data: HashMap<Uuid, Vec<u8>>,
108 pub services_resolved: bool,
110 pub alias: Option<String>,
112 pub class: Option<u32>,
115 pub bonded: bool,
118 pub icon: Option<String>,
120 pub trusted: bool,
122 pub blocked: bool,
125 pub legacy_pairing: bool,
129 pub modalias: Option<String>,
131 pub wake_allowed: bool,
133}
134
135impl DeviceInfo {
136 pub(crate) fn from_properties(
137 id: DeviceId,
138 device_properties: OrgBluezDevice1Properties,
139 ) -> Result<DeviceInfo, BluetoothError> {
140 let mac_address = device_properties
141 .address()
142 .ok_or(BluetoothError::RequiredPropertyMissing("Address"))?
143 .parse()?;
144 let address_type = device_properties
145 .address_type()
146 .ok_or(BluetoothError::RequiredPropertyMissing("AddressType"))?
147 .parse()?;
148 let services = get_services(device_properties);
149 let manufacturer_data = get_manufacturer_data(device_properties).unwrap_or_default();
150 let service_data = get_service_data(device_properties).unwrap_or_default();
151
152 Ok(DeviceInfo {
153 id,
154 mac_address,
155 address_type,
156 name: device_properties.name().cloned(),
157 appearance: device_properties.appearance(),
158 services,
159 paired: device_properties
160 .paired()
161 .ok_or(BluetoothError::RequiredPropertyMissing("Paired"))?,
162 connected: device_properties
163 .connected()
164 .ok_or(BluetoothError::RequiredPropertyMissing("Connected"))?,
165 rssi: device_properties.rssi(),
166 tx_power: device_properties.tx_power(),
167 manufacturer_data,
168 service_data,
169 services_resolved: device_properties
170 .services_resolved()
171 .ok_or(BluetoothError::RequiredPropertyMissing("ServicesResolved"))?,
172 alias: device_properties.alias().cloned(),
173 class: device_properties.class(),
174 bonded: device_properties.bonded().unwrap_or_default(),
175 icon: device_properties.icon().cloned(),
176 trusted: device_properties
177 .trusted()
178 .ok_or(BluetoothError::RequiredPropertyMissing("Trusted"))?,
179 blocked: device_properties
180 .blocked()
181 .ok_or(BluetoothError::RequiredPropertyMissing("Blocked"))?,
182 legacy_pairing: device_properties
183 .legacy_pairing()
184 .ok_or(BluetoothError::RequiredPropertyMissing("LegacyPairing"))?,
185 modalias: device_properties.modalias().cloned(),
186 wake_allowed: device_properties.wake_allowed().unwrap_or(false),
187 })
188 }
189}
190
191#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
193pub enum AddressType {
194 Public,
196 Random,
198}
199
200impl AddressType {
201 fn as_str(&self) -> &'static str {
202 match self {
203 Self::Public => "public",
204 Self::Random => "random",
205 }
206 }
207}
208
209impl Display for AddressType {
210 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
211 f.write_str(self.as_str())
212 }
213}
214
215impl FromStr for AddressType {
216 type Err = BluetoothError;
217
218 fn from_str(s: &str) -> Result<Self, Self::Err> {
219 match s {
220 "public" => Ok(Self::Public),
221 "random" => Ok(Self::Random),
222 _ => Err(BluetoothError::AddressTypeParseError(s.to_owned())),
223 }
224 }
225}
226
227fn get_manufacturer_data(
228 device_properties: OrgBluezDevice1Properties,
229) -> Option<HashMap<u16, Vec<u8>>> {
230 Some(convert_manufacturer_data(
231 device_properties.manufacturer_data()?,
232 ))
233}
234
235pub(crate) fn convert_manufacturer_data(
236 data: &HashMap<u16, Variant<Box<dyn RefArg>>>,
237) -> HashMap<u16, Vec<u8>> {
238 data.iter()
239 .filter_map(|(&k, v)| {
240 if let Some(v) = cast::<Vec<u8>>(&v.0) {
241 Some((k, v.to_owned()))
242 } else {
243 log::warn!("Manufacturer data had wrong type: {:?}", &v.0);
244 None
245 }
246 })
247 .collect()
248}
249
250fn get_service_data(
251 device_properties: OrgBluezDevice1Properties,
252) -> Option<HashMap<Uuid, Vec<u8>>> {
253 Some(convert_service_data(device_properties.service_data()?))
254}
255
256pub(crate) fn convert_service_data(data: &PropMap) -> HashMap<Uuid, Vec<u8>> {
257 data.iter()
258 .filter_map(|(k, v)| match Uuid::parse_str(k) {
259 Ok(uuid) => {
260 if let Some(v) = cast::<Vec<u8>>(&v.0) {
261 Some((uuid, v.to_owned()))
262 } else {
263 log::warn!("Service data had wrong type: {:?}", &v.0);
264 None
265 }
266 }
267 Err(err) => {
268 log::warn!("Error parsing service data UUID: {}", err);
269 None
270 }
271 })
272 .collect()
273}
274
275fn get_services(device_properties: OrgBluezDevice1Properties) -> Vec<Uuid> {
276 if let Some(uuids) = device_properties.uuids() {
277 convert_services(uuids)
278 } else {
279 vec![]
280 }
281}
282
283pub(crate) fn convert_services(uuids: &[String]) -> Vec<Uuid> {
284 uuids
285 .iter()
286 .filter_map(|uuid| {
287 Uuid::parse_str(uuid)
288 .map_err(|err| {
289 log::warn!("Error parsing service data UUID: {}", err);
290 err
291 })
292 .ok()
293 })
294 .collect()
295}
296
297#[cfg(test)]
298mod tests {
299 use crate::uuid_from_u32;
300
301 use super::*;
302
303 #[test]
304 fn device_adapter() {
305 let adapter_id = AdapterId::new("/org/bluez/hci0");
306 let device_id = DeviceId::new("/org/bluez/hci0/dev_11_22_33_44_55_66");
307 assert_eq!(device_id.adapter(), adapter_id);
308 }
309
310 #[test]
311 fn to_string() {
312 let device_id = DeviceId::new("/org/bluez/hci0/dev_11_22_33_44_55_66");
313 assert_eq!(device_id.to_string(), "hci0/dev_11_22_33_44_55_66");
314 }
315
316 #[test]
317 fn from_string() {
318 let device_id = DeviceId::from_str("hci0/dev_11_22_33_44_55_66");
319 assert_eq!(device_id.unwrap().to_string(), "hci0/dev_11_22_33_44_55_66");
320
321 let invalid_id = DeviceId::from_str("dev_11_22_33_44_55_66");
322 assert_eq!(
323 invalid_id.unwrap_err().to_string(),
324 "Error parsing DeviceId string: dev_11_22_33_44_55_66"
325 );
326 }
327
328 #[test]
329 fn service_data() {
330 let uuid = uuid_from_u32(0x11223344);
331 let mut service_data: PropMap = HashMap::new();
332 service_data.insert(uuid.to_string(), Variant(Box::new(vec![1u8, 2, 3])));
333 let mut device_properties: PropMap = HashMap::new();
334 device_properties.insert("ServiceData".to_string(), Variant(Box::new(service_data)));
335
336 let mut expected_service_data = HashMap::new();
337 expected_service_data.insert(uuid, vec![1u8, 2, 3]);
338
339 assert_eq!(
340 get_service_data(OrgBluezDevice1Properties(&device_properties)),
341 Some(expected_service_data)
342 );
343 }
344
345 #[test]
346 fn manufacturer_data() {
347 let manufacturer_id = 0x1122;
348 let mut manufacturer_data: HashMap<u16, Variant<Box<dyn RefArg>>> = HashMap::new();
349 manufacturer_data.insert(manufacturer_id, Variant(Box::new(vec![1u8, 2, 3])));
350 let mut device_properties: PropMap = HashMap::new();
351 device_properties.insert(
352 "ManufacturerData".to_string(),
353 Variant(Box::new(manufacturer_data)),
354 );
355
356 let mut expected_manufacturer_data = HashMap::new();
357 expected_manufacturer_data.insert(manufacturer_id, vec![1u8, 2, 3]);
358
359 assert_eq!(
360 get_manufacturer_data(OrgBluezDevice1Properties(&device_properties)),
361 Some(expected_manufacturer_data)
362 );
363 }
364
365 #[test]
366 fn device_info_minimal() {
367 let id = DeviceId::new("/org/bluez/hci0/dev_11_22_33_44_55_66");
368 let mut device_properties: PropMap = HashMap::new();
369 device_properties.insert(
370 "Address".to_string(),
371 Variant(Box::new("00:11:22:33:44:55".to_string())),
372 );
373 device_properties.insert(
374 "AddressType".to_string(),
375 Variant(Box::new("public".to_string())),
376 );
377 device_properties.insert("Paired".to_string(), Variant(Box::new(false)));
378 device_properties.insert("Connected".to_string(), Variant(Box::new(false)));
379 device_properties.insert("ServicesResolved".to_string(), Variant(Box::new(false)));
380 device_properties.insert("Bonded".to_string(), Variant(Box::new(false)));
381 device_properties.insert("Trusted".to_string(), Variant(Box::new(false)));
382 device_properties.insert("Blocked".to_string(), Variant(Box::new(false)));
383 device_properties.insert("LegacyPairing".to_string(), Variant(Box::new(false)));
384
385 let device =
386 DeviceInfo::from_properties(id.clone(), OrgBluezDevice1Properties(&device_properties))
387 .unwrap();
388 assert_eq!(
389 device,
390 DeviceInfo {
391 id,
392 mac_address: "00:11:22:33:44:55".parse().unwrap(),
393 address_type: AddressType::Public,
394 name: None,
395 appearance: None,
396 services: vec![],
397 paired: false,
398 connected: false,
399 rssi: None,
400 tx_power: None,
401 manufacturer_data: HashMap::new(),
402 service_data: HashMap::new(),
403 services_resolved: false,
404 alias: None,
405 class: None,
406 bonded: false,
407 icon: None,
408 trusted: false,
409 blocked: false,
410 legacy_pairing: false,
411 modalias: None,
412 wake_allowed: false,
413 }
414 )
415 }
416
417 #[test]
418 fn get_services_none() {
419 let device_properties: PropMap = HashMap::new();
420
421 assert_eq!(
422 get_services(OrgBluezDevice1Properties(&device_properties)),
423 Vec::<Uuid>::new(),
424 )
425 }
426
427 #[test]
428 fn get_services_some() {
429 let uuid = uuid_from_u32(0x11223344);
430 let uuids = vec![uuid.to_string()];
431 let mut device_properties: PropMap = HashMap::new();
432 device_properties.insert("UUIDs".to_string(), Variant(Box::new(uuids)));
433
434 assert_eq!(
435 get_services(OrgBluezDevice1Properties(&device_properties)),
436 vec![uuid]
437 )
438 }
439
440 #[test]
441 fn address_type_parse() {
442 for &address_type in &[AddressType::Public, AddressType::Random] {
443 assert_eq!(
444 address_type.to_string().parse::<AddressType>().unwrap(),
445 address_type
446 );
447 }
448 }
449}