use bluez_generated::OrgBluezDevice1Properties;
use dbus::arg::{cast, PropMap, RefArg, Variant};
use dbus::Path;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::{self, Display, Formatter};
use std::str::FromStr;
use uuid::Uuid;
use crate::{AdapterId, BluetoothError, MacAddress};
#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct DeviceId {
#[serde(with = "crate::serde_path")]
pub(crate) object_path: Path<'static>,
}
impl DeviceId {
pub(crate) fn new(object_path: &str) -> Self {
Self {
object_path: object_path.to_owned().into(),
}
}
pub fn adapter(&self) -> AdapterId {
let index = self
.object_path
.rfind('/')
.expect("DeviceId object_path must contain a slash.");
AdapterId::new(&self.object_path[0..index])
}
}
impl From<DeviceId> for Path<'static> {
fn from(id: DeviceId) -> Self {
id.object_path
}
}
impl Display for DeviceId {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
self.object_path
.to_string()
.strip_prefix("/org/bluez/")
.ok_or(fmt::Error)?
)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DeviceInfo {
pub id: DeviceId,
pub mac_address: MacAddress,
pub address_type: AddressType,
pub name: Option<String>,
pub appearance: Option<u16>,
pub services: Vec<Uuid>,
pub paired: bool,
pub connected: bool,
pub rssi: Option<i16>,
pub tx_power: Option<i16>,
pub manufacturer_data: HashMap<u16, Vec<u8>>,
pub service_data: HashMap<Uuid, Vec<u8>>,
pub services_resolved: bool,
pub alias: Option<String>,
pub class: Option<u32>,
pub bonded: bool,
pub icon: Option<String>,
pub trusted: bool,
pub blocked: bool,
pub legacy_pairing: bool,
pub modalias: Option<String>,
pub wake_allowed: bool,
}
impl DeviceInfo {
pub(crate) fn from_properties(
id: DeviceId,
device_properties: OrgBluezDevice1Properties,
) -> Result<DeviceInfo, BluetoothError> {
let mac_address = device_properties
.address()
.ok_or(BluetoothError::RequiredPropertyMissing("Address"))?
.parse()?;
let address_type = device_properties
.address_type()
.ok_or(BluetoothError::RequiredPropertyMissing("AddressType"))?
.parse()?;
let services = get_services(device_properties);
let manufacturer_data = get_manufacturer_data(device_properties).unwrap_or_default();
let service_data = get_service_data(device_properties).unwrap_or_default();
Ok(DeviceInfo {
id,
mac_address,
address_type,
name: device_properties.name().cloned(),
appearance: device_properties.appearance(),
services,
paired: device_properties
.paired()
.ok_or(BluetoothError::RequiredPropertyMissing("Paired"))?,
connected: device_properties
.connected()
.ok_or(BluetoothError::RequiredPropertyMissing("Connected"))?,
rssi: device_properties.rssi(),
tx_power: device_properties.tx_power(),
manufacturer_data,
service_data,
services_resolved: device_properties
.services_resolved()
.ok_or(BluetoothError::RequiredPropertyMissing("ServicesResolved"))?,
alias: device_properties.alias().cloned(),
class: device_properties.class(),
bonded: device_properties.bonded().unwrap_or_default(),
icon: device_properties.icon().cloned(),
trusted: device_properties
.trusted()
.ok_or(BluetoothError::RequiredPropertyMissing("Trusted"))?,
blocked: device_properties
.blocked()
.ok_or(BluetoothError::RequiredPropertyMissing("Blocked"))?,
legacy_pairing: device_properties
.legacy_pairing()
.ok_or(BluetoothError::RequiredPropertyMissing("LegacyPairing"))?,
modalias: device_properties.modalias().cloned(),
wake_allowed: device_properties.wake_allowed().unwrap_or(false),
})
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum AddressType {
Public,
Random,
}
impl AddressType {
fn as_str(&self) -> &'static str {
match self {
Self::Public => "public",
Self::Random => "random",
}
}
}
impl Display for AddressType {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for AddressType {
type Err = BluetoothError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"public" => Ok(Self::Public),
"random" => Ok(Self::Random),
_ => Err(BluetoothError::AddressTypeParseError(s.to_owned())),
}
}
}
fn get_manufacturer_data(
device_properties: OrgBluezDevice1Properties,
) -> Option<HashMap<u16, Vec<u8>>> {
Some(convert_manufacturer_data(
device_properties.manufacturer_data()?,
))
}
pub(crate) fn convert_manufacturer_data(
data: &HashMap<u16, Variant<Box<dyn RefArg>>>,
) -> HashMap<u16, Vec<u8>> {
data.iter()
.filter_map(|(&k, v)| {
if let Some(v) = cast::<Vec<u8>>(&v.0) {
Some((k, v.to_owned()))
} else {
log::warn!("Manufacturer data had wrong type: {:?}", &v.0);
None
}
})
.collect()
}
fn get_service_data(
device_properties: OrgBluezDevice1Properties,
) -> Option<HashMap<Uuid, Vec<u8>>> {
Some(convert_service_data(device_properties.service_data()?))
}
pub(crate) fn convert_service_data(data: &PropMap) -> HashMap<Uuid, Vec<u8>> {
data.iter()
.filter_map(|(k, v)| match Uuid::parse_str(k) {
Ok(uuid) => {
if let Some(v) = cast::<Vec<u8>>(&v.0) {
Some((uuid, v.to_owned()))
} else {
log::warn!("Service data had wrong type: {:?}", &v.0);
None
}
}
Err(err) => {
log::warn!("Error parsing service data UUID: {}", err);
None
}
})
.collect()
}
fn get_services(device_properties: OrgBluezDevice1Properties) -> Vec<Uuid> {
if let Some(uuids) = device_properties.uuids() {
convert_services(uuids)
} else {
vec![]
}
}
pub(crate) fn convert_services(uuids: &[String]) -> Vec<Uuid> {
uuids
.iter()
.filter_map(|uuid| {
Uuid::parse_str(uuid)
.map_err(|err| {
log::warn!("Error parsing service data UUID: {}", err);
err
})
.ok()
})
.collect()
}
#[cfg(test)]
mod tests {
use crate::uuid_from_u32;
use super::*;
#[test]
fn device_adapter() {
let adapter_id = AdapterId::new("/org/bluez/hci0");
let device_id = DeviceId::new("/org/bluez/hci0/dev_11_22_33_44_55_66");
assert_eq!(device_id.adapter(), adapter_id);
}
#[test]
fn to_string() {
let device_id = DeviceId::new("/org/bluez/hci0/dev_11_22_33_44_55_66");
assert_eq!(device_id.to_string(), "hci0/dev_11_22_33_44_55_66");
}
#[test]
fn service_data() {
let uuid = uuid_from_u32(0x11223344);
let mut service_data: PropMap = HashMap::new();
service_data.insert(uuid.to_string(), Variant(Box::new(vec![1u8, 2, 3])));
let mut device_properties: PropMap = HashMap::new();
device_properties.insert("ServiceData".to_string(), Variant(Box::new(service_data)));
let mut expected_service_data = HashMap::new();
expected_service_data.insert(uuid, vec![1u8, 2, 3]);
assert_eq!(
get_service_data(OrgBluezDevice1Properties(&device_properties)),
Some(expected_service_data)
);
}
#[test]
fn manufacturer_data() {
let manufacturer_id = 0x1122;
let mut manufacturer_data: HashMap<u16, Variant<Box<dyn RefArg>>> = HashMap::new();
manufacturer_data.insert(manufacturer_id, Variant(Box::new(vec![1u8, 2, 3])));
let mut device_properties: PropMap = HashMap::new();
device_properties.insert(
"ManufacturerData".to_string(),
Variant(Box::new(manufacturer_data)),
);
let mut expected_manufacturer_data = HashMap::new();
expected_manufacturer_data.insert(manufacturer_id, vec![1u8, 2, 3]);
assert_eq!(
get_manufacturer_data(OrgBluezDevice1Properties(&device_properties)),
Some(expected_manufacturer_data)
);
}
#[test]
fn device_info_minimal() {
let id = DeviceId::new("/org/bluez/hci0/dev_11_22_33_44_55_66");
let mut device_properties: PropMap = HashMap::new();
device_properties.insert(
"Address".to_string(),
Variant(Box::new("00:11:22:33:44:55".to_string())),
);
device_properties.insert(
"AddressType".to_string(),
Variant(Box::new("public".to_string())),
);
device_properties.insert("Paired".to_string(), Variant(Box::new(false)));
device_properties.insert("Connected".to_string(), Variant(Box::new(false)));
device_properties.insert("ServicesResolved".to_string(), Variant(Box::new(false)));
device_properties.insert("Bonded".to_string(), Variant(Box::new(false)));
device_properties.insert("Trusted".to_string(), Variant(Box::new(false)));
device_properties.insert("Blocked".to_string(), Variant(Box::new(false)));
device_properties.insert("LegacyPairing".to_string(), Variant(Box::new(false)));
let device =
DeviceInfo::from_properties(id.clone(), OrgBluezDevice1Properties(&device_properties))
.unwrap();
assert_eq!(
device,
DeviceInfo {
id,
mac_address: "00:11:22:33:44:55".parse().unwrap(),
address_type: AddressType::Public,
name: None,
appearance: None,
services: vec![],
paired: false,
connected: false,
rssi: None,
tx_power: None,
manufacturer_data: HashMap::new(),
service_data: HashMap::new(),
services_resolved: false,
alias: None,
class: None,
bonded: false,
icon: None,
trusted: false,
blocked: false,
legacy_pairing: false,
modalias: None,
wake_allowed: false,
}
)
}
#[test]
fn get_services_none() {
let device_properties: PropMap = HashMap::new();
assert_eq!(
get_services(OrgBluezDevice1Properties(&device_properties)),
vec![]
)
}
#[test]
fn get_services_some() {
let uuid = uuid_from_u32(0x11223344);
let uuids = vec![uuid.to_string()];
let mut device_properties: PropMap = HashMap::new();
device_properties.insert("UUIDs".to_string(), Variant(Box::new(uuids)));
assert_eq!(
get_services(OrgBluezDevice1Properties(&device_properties)),
vec![uuid]
)
}
#[test]
fn address_type_parse() {
for &address_type in &[AddressType::Public, AddressType::Random] {
assert_eq!(
address_type.to_string().parse::<AddressType>().unwrap(),
address_type
);
}
}
}