1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
use bluez_generated::OrgBluezAdapter1Properties;
use dbus::Path;
use std::fmt::{self, Display, Formatter};

use crate::{AddressType, BluetoothError, MacAddress};

/// Opaque identifier for a Bluetooth adapter on the system.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct AdapterId {
    pub(crate) object_path: Path<'static>,
}

impl AdapterId {
    pub(crate) fn new(object_path: &str) -> Self {
        Self {
            object_path: object_path.to_owned().into(),
        }
    }
}

impl Display for AdapterId {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{}",
            self.object_path
                .to_string()
                .strip_prefix("/org/bluez/")
                .ok_or(fmt::Error)?
        )
    }
}

/// Information about a Bluetooth adapter on the system.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AdapterInfo {
    /// An opaque identifier for the adapter. This can be used to perform operations on it.
    pub id: AdapterId,
    /// The MAC address of the adapter.
    pub mac_address: MacAddress,
    /// The type of MAC address the adapter uses.
    pub address_type: AddressType,
    /// The Bluetooth system hostname.
    pub name: String,
    /// The Bluetooth friendly name. This defaults to the system hostname.
    pub alias: String,
    /// Whether the adapter is currently turned on.
    pub powered: bool,
    /// Whether the adapter is currently discovering devices.
    pub discovering: bool,
}

impl AdapterInfo {
    pub(crate) fn from_properties(
        id: AdapterId,
        adapter_properties: OrgBluezAdapter1Properties,
    ) -> Result<AdapterInfo, BluetoothError> {
        let mac_address = adapter_properties
            .address()
            .ok_or(BluetoothError::RequiredPropertyMissing("Address"))?;
        let address_type = adapter_properties
            .address_type()
            .ok_or(BluetoothError::RequiredPropertyMissing("AddressType"))?
            .parse()?;

        Ok(AdapterInfo {
            id,
            mac_address: MacAddress(mac_address.to_owned()),
            address_type,
            name: adapter_properties
                .name()
                .ok_or(BluetoothError::RequiredPropertyMissing("Name"))?
                .to_owned(),
            alias: adapter_properties
                .alias()
                .ok_or(BluetoothError::RequiredPropertyMissing("Alias"))?
                .to_owned(),
            powered: adapter_properties
                .powered()
                .ok_or(BluetoothError::RequiredPropertyMissing("Powered"))?,
            discovering: adapter_properties
                .discovering()
                .ok_or(BluetoothError::RequiredPropertyMissing("Discovering"))?,
        })
    }
}

#[cfg(test)]
mod tests {
    use dbus::arg::{PropMap, Variant};
    use std::collections::HashMap;

    use super::*;

    #[test]
    fn adapter_info_minimal() {
        let id = AdapterId::new("/org/bluez/hci0");
        let mut adapter_properties: PropMap = HashMap::new();
        adapter_properties.insert(
            "Address".to_string(),
            Variant(Box::new("00:11:22:33:44:55".to_string())),
        );
        adapter_properties.insert(
            "AddressType".to_string(),
            Variant(Box::new("public".to_string())),
        );
        adapter_properties.insert("Name".to_string(), Variant(Box::new("name".to_string())));
        adapter_properties.insert("Alias".to_string(), Variant(Box::new("alias".to_string())));
        adapter_properties.insert("Powered".to_string(), Variant(Box::new(false)));
        adapter_properties.insert("Discovering".to_string(), Variant(Box::new(false)));

        let adapter = AdapterInfo::from_properties(
            id.clone(),
            OrgBluezAdapter1Properties(&adapter_properties),
        )
        .unwrap();
        assert_eq!(
            adapter,
            AdapterInfo {
                id,
                mac_address: MacAddress("00:11:22:33:44:55".to_string()),
                address_type: AddressType::Public,
                name: "name".to_string(),
                alias: "alias".to_string(),
                powered: false,
                discovering: false
            }
        )
    }
}