use dbus::{
arg::{PropMap, RefArg, Variant},
nonblock::Proxy,
};
use dbus_crossroads::{Crossroads, IfaceBuilder, IfaceToken};
use futures::channel::oneshot;
use std::{
collections::{BTreeMap, BTreeSet, HashMap},
fmt,
sync::Arc,
time::Duration,
};
use strum::{Display, EnumString};
use uuid::Uuid;
use crate::{read_dict, Adapter, Result, SessionInner, SERVICE_NAME, TIMEOUT};
pub(crate) const MANAGER_INTERFACE: &str = "org.bluez.LEAdvertisingManager1";
pub(crate) const ADVERTISEMENT_INTERFACE: &str = "org.bluez.LEAdvertisement1";
pub(crate) const ADVERTISEMENT_PREFIX: &str = publish_path!("advertising/");
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Display, EnumString)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Type {
#[strum(serialize = "broadcast")]
Broadcast,
#[strum(serialize = "peripheral")]
Peripheral,
}
impl Default for Type {
fn default() -> Self {
Self::Peripheral
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Display, EnumString)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum SecondaryChannel {
#[strum(serialize = "1M")]
OneM,
#[strum(serialize = "2M")]
TwoM,
#[strum(serialize = "Coded")]
Coded,
}
impl Default for SecondaryChannel {
fn default() -> Self {
Self::OneM
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Display, EnumString)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum Feature {
#[strum(serialize = "tx-power")]
TxPower,
#[strum(serialize = "appearance")]
Appearance,
#[strum(serialize = "local-name")]
LocalName,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Display, EnumString)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum PlatformFeature {
#[strum(serialize = "CanSetTxPower")]
CanSetTxPower,
#[strum(serialize = "HardwareOffload")]
HardwareOffload,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub struct Capabilities {
pub max_advertisement_length: u8,
pub max_scan_response_length: u8,
pub min_tx_power: i16,
pub max_tx_power: i16,
}
impl Capabilities {
pub(crate) fn from_dict(dict: &HashMap<String, Variant<Box<dyn RefArg + 'static>>>) -> Result<Self> {
Ok(Self {
max_advertisement_length: *read_dict(dict, "MaxAdvLen")?,
max_scan_response_length: *read_dict(dict, "MaxScnRspLen")?,
min_tx_power: *read_dict(dict, "MinTxPower")?,
max_tx_power: *read_dict(dict, "MaxTxPower")?,
})
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct Advertisement {
pub advertisement_type: Type,
pub service_uuids: BTreeSet<Uuid>,
pub manufacturer_data: BTreeMap<u16, Vec<u8>>,
pub solicit_uuids: BTreeSet<Uuid>,
pub service_data: BTreeMap<Uuid, Vec<u8>>,
pub advertising_data: BTreeMap<u8, Vec<u8>>,
pub discoverable: Option<bool>,
pub discoverable_timeout: Option<Duration>,
pub system_includes: BTreeSet<Feature>,
pub local_name: Option<String>,
pub appearance: Option<u16>,
pub duration: Option<Duration>,
pub timeout: Option<Duration>,
pub secondary_channel: Option<SecondaryChannel>,
pub min_interval: Option<Duration>,
pub max_interval: Option<Duration>,
pub tx_power: Option<i16>,
#[doc(hidden)]
pub _non_exhaustive: (),
}
impl Advertisement {
pub(crate) fn register_interface(cr: &mut Crossroads) -> IfaceToken<Self> {
cr.register(ADVERTISEMENT_INTERFACE, |ib: &mut IfaceBuilder<Self>| {
cr_property!(ib, "Type", la => {
Some(la.advertisement_type.to_string())
});
cr_property!(ib, "ServiceUUIDs", la => {
Some(la.service_uuids.iter().map(|uuid| uuid.to_string()).collect::<Vec<_>>())
});
cr_property!(ib, "ManufacturerData", la => {
Some(la.manufacturer_data.clone().into_iter().map(|(k, v)| (k, Variant(v))).collect::<HashMap<_, _>>())
});
cr_property!(ib, "SolicitUUIDs", la => {
Some(la.solicit_uuids.iter().map(|uuid| uuid.to_string()).collect::<Vec<_>>())
});
cr_property!(ib, "ServiceData", la => {
Some(la.service_data.iter().map(|(k, v)| (k.to_string(), Variant(v.clone()))).collect::<HashMap<_, _>>())
});
cr_property!(ib, "Data", la => {
Some(la.advertising_data.iter().map(|(k, v)| (*k, Variant(v.clone()))).collect::<HashMap<_, _>>())
});
cr_property!(ib, "Discoverable", la => {
la.discoverable
});
cr_property!(ib, "DiscoverableTimeout", la => {
la.discoverable_timeout.map(|t| t.as_secs().min(u16::MAX as _) as u16)
});
cr_property!(ib, "Includes", la => {
Some(la.system_includes.iter().map(|v| v.to_string()).collect::<Vec<_>>())
});
cr_property!(ib, "LocalName", la => {
la.local_name.clone()
});
cr_property!(ib, "Appearance", la => {
la.appearance
});
cr_property!(ib, "Duration", la => {
la.duration.map(|t| t.as_secs().min(u16::MAX as _) as u16)
});
cr_property!(ib, "Timeout", la => {
la.timeout.map(|t| t.as_secs().min(u16::MAX as _) as u16)
});
cr_property!(ib, "SecondaryChannel", la => {
la.secondary_channel.map(|v| v.to_string())
});
cr_property!(ib, "MinInterval", la => {
la.min_interval.map(|t| t.as_millis().min(u32::MAX as _) as u32)
});
cr_property!(ib, "MaxInterval", la => {
la.max_interval.map(|t| t.as_millis().min(u32::MAX as _) as u32)
});
cr_property!(ib, "TxPower", la => {
la.tx_power
});
})
}
pub(crate) async fn register(
self, inner: Arc<SessionInner>, adapter_name: Arc<String>,
) -> Result<AdvertisementHandle> {
let name = dbus::Path::new(format!("{}{}", ADVERTISEMENT_PREFIX, Uuid::new_v4().as_simple())).unwrap();
log::trace!("Publishing advertisement at {}", &name);
{
let mut cr = inner.crossroads.lock().await;
cr.insert(name.clone(), &[inner.le_advertisment_token], self);
}
log::trace!("Registering advertisement at {}", &name);
let proxy =
Proxy::new(SERVICE_NAME, Adapter::dbus_path(&adapter_name)?, TIMEOUT, inner.connection.clone());
let () =
proxy.method_call(MANAGER_INTERFACE, "RegisterAdvertisement", (name.clone(), PropMap::new())).await?;
let (drop_tx, drop_rx) = oneshot::channel();
let unreg_name = name.clone();
tokio::spawn(async move {
let _ = drop_rx.await;
log::trace!("Unregistering advertisement at {}", &unreg_name);
let _: std::result::Result<(), dbus::Error> =
proxy.method_call(MANAGER_INTERFACE, "UnregisterAdvertisement", (unreg_name.clone(),)).await;
log::trace!("Unpublishing advertisement at {}", &unreg_name);
let mut cr = inner.crossroads.lock().await;
let _: Option<Self> = cr.remove(&unreg_name);
});
Ok(AdvertisementHandle { name, _drop_tx: drop_tx })
}
}
#[must_use = "AdvertisementHandle must be held for advertisement to be broadcasted"]
pub struct AdvertisementHandle {
name: dbus::Path<'static>,
_drop_tx: oneshot::Sender<()>,
}
impl Drop for AdvertisementHandle {
fn drop(&mut self) {
}
}
impl fmt::Debug for AdvertisementHandle {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "AdvertisementHandle {{ {} }}", &self.name)
}
}