bluer/
monitor.rs

1//! Bluetooth advertisement monitor.
2//!
3//! This API allows an client to specify a job of monitoring advertisements by
4//! exposing advertisement monitors with filtering conditions, thresholds of RSSI and timers
5//! of RSSI thresholds.
6
7use dbus::nonblock::Proxy;
8use dbus_crossroads::{Crossroads, IfaceBuilder, IfaceToken};
9use futures::{Stream, StreamExt};
10use std::{
11    fmt,
12    pin::Pin,
13    sync::Arc,
14    task::{Context, Poll},
15    time::Duration,
16};
17use strum::{Display, EnumString};
18use tokio::sync::{mpsc, oneshot, Mutex};
19use tokio_stream::wrappers::ReceiverStream;
20use uuid::Uuid;
21
22use crate::{
23    method_call, Address, DbusResult, Device, Error, ErrorKind, Result, SessionInner, SERVICE_NAME, TIMEOUT,
24};
25
26pub(crate) const INTERFACE: &str = "org.bluez.AdvertisementMonitor1";
27pub(crate) const MANAGER_INTERFACE: &str = "org.bluez.AdvertisementMonitorManager1";
28pub(crate) const MANAGER_PATH: &str = "/org/bluez";
29pub(crate) const MONITOR_PREFIX: &str = publish_path!("monitor");
30
31/// Determines the type of advertisement monitor.
32#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Display, EnumString)]
33#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
34#[non_exhaustive]
35pub enum Type {
36    /// Patterns with logic OR applied.
37    #[strum(serialize = "or_patterns")]
38    OrPatterns,
39}
40
41impl Default for Type {
42    fn default() -> Self {
43        Self::OrPatterns
44    }
45}
46
47/// Common advertising data types for [`Pattern::data_type`].
48///
49/// See [the GATT specification](https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile/)
50/// for a complete list.
51pub mod data_type {
52    /// Flags: Contains important settings for the device such as BR/EDR and LE modes.
53    pub const FLAGS: u8 = 0x01;
54
55    /// Incomplete List of 16-bit Service Class UUIDs:
56    /// Contains a list of 16-bit UUIDs as defined by the Bluetooth SIG that the device advertises, but the list is not complete.
57    pub const INCOMPLETE_LIST_16_BIT_SERVICE_CLASS_UUIDS: u8 = 0x02;
58
59    /// Complete List of 16-bit Service Class UUIDs:
60    /// Contains a complete list of 16-bit UUIDs as defined by the Bluetooth SIG that the device advertises.
61    pub const COMPLETE_LIST_16_BIT_SERVICE_CLASS_UUIDS: u8 = 0x03;
62
63    /// Incomplete List of 32-bit Service Class UUIDs:
64    /// Contains a list of 32-bit UUIDs as defined by the Bluetooth SIG that the device advertises, but the list is not complete.
65    pub const INCOMPLETE_LIST_32_BIT_SERVICE_CLASS_UUIDS: u8 = 0x04;
66
67    /// Complete List of 32-bit Service Class UUIDs:
68    /// Contains a complete list of 32-bit UUIDs as defined by the Bluetooth SIG that the device advertises.
69    pub const COMPLETE_LIST_32_BIT_SERVICE_CLASS_UUIDS: u8 = 0x05;
70
71    /// Incomplete List of 128-bit Service Class UUIDs:
72    /// Contains a list of 128-bit UUIDs that the device advertises, but the list is not complete.
73    pub const INCOMPLETE_LIST_128_BIT_SERVICE_CLASS_UUIDS: u8 = 0x06;
74
75    /// Complete List of 128-bit Service Class UUIDs:
76    /// Contains a complete list of 128-bit UUIDs that the device advertises.
77    pub const COMPLETE_LIST_128_BIT_SERVICE_CLASS_UUIDS: u8 = 0x07;
78
79    /// Shortened Local Name: Contains a shortened version of the local device name.
80    pub const SHORTENED_LOCAL_NAME: u8 = 0x08;
81
82    /// Complete Local Name: Contains the complete local device name.
83    pub const COMPLETE_LOCAL_NAME: u8 = 0x09;
84
85    /// TX Power Level: Contains the device's transmit power level.
86    pub const TX_POWER_LEVEL: u8 = 0x0A;
87
88    /// Manufacturer Specific Data: Contains data specific to the manufacturer.
89    pub const MANUFACTURER_SPECIFIC_DATA: u8 = 0xFF;
90}
91
92/// An advertisement data pattern, used to filter devices in the advertisement monitor.
93#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
94#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
95pub struct Pattern {
96    /// Advertising data type to match.
97    ///
98    /// See [data_type] for common values.
99    pub data_type: u8,
100    /// The index in an AD data field where the search should start.
101    ///
102    /// The beginning of an AD data field is index 0.
103    pub start_position: u8,
104    /// The value of the pattern.
105    ///
106    /// The maximum length of the bytes is 31.
107    pub content: Vec<u8>,
108}
109
110impl Pattern {
111    /// Creates a new advertisement data pattern.
112    ///
113    /// See the field documentation for more information about the arguments.
114    pub fn new(data_type: u8, start_position: u8, content: &[u8]) -> Self {
115        Self { data_type, start_position, content: content.to_vec() }
116    }
117}
118
119/// Grouping rules on how to propagate the received
120/// advertisement packets to the client.
121#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
122#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
123pub enum RssiSamplingPeriod {
124    /// All advertisement packets from in-range devices
125    /// would be propagated.
126    All,
127    /// Only the first advertisement packet of in-range
128    /// devices would be propagated. If the device
129    /// becomes lost, then the first packet when it is
130    /// found again will also be propagated.
131    First,
132    /// Advertisement packets would be grouped into
133    /// the specified time period rounded to 100ms.
134    /// Packets in the same group will only be reported once,
135    /// with the RSSI value being averaged out.
136    Period(Duration),
137}
138
139impl RssiSamplingPeriod {
140    fn to_value(self) -> u16 {
141        match self {
142            Self::All => 0,
143            Self::First => 255,
144            Self::Period(period) => (period.as_millis() / 100).clamp(1, 254) as u16,
145        }
146    }
147}
148
149/// Advertisement monitor specification.
150///
151/// Specifies an advertisement monitor target.
152///
153/// Use [`MonitorManager::register`] to add a monitor target.
154#[derive(Default)]
155pub struct Monitor {
156    /// The type of the monitor.
157    pub monitor_type: Type,
158
159    /// Used in conjunction with RSSILowTimeout to determine
160    /// whether a device becomes out-of-range.
161    ///
162    /// Valid range is -127 to 20 (dBm).
163    pub rssi_low_threshold: Option<i16>,
164
165    /// Used in conjunction with RSSIHighTimeout to determine
166    /// whether a device becomes in-range.
167    ///
168    /// Valid range is -127 to 20 (dBm).
169    pub rssi_high_threshold: Option<i16>,
170
171    /// The time it takes to consider a device as out-of-range.
172    ///
173    /// If this many seconds elapses without receiving any
174    /// signal at least as strong as RSSILowThreshold, a
175    /// currently in-range device will be considered as
176    /// out-of-range (lost).
177    ///
178    /// Valid range is 1 to 300 (seconds).
179    pub rssi_low_timeout: Option<Duration>,
180
181    /// The time it takes to consider a device as in-range.
182    ///
183    /// If this many seconds elapses while we continuously
184    /// receive signals at least as strong as RSSIHighThreshold,
185    /// a currently out-of-range device will be considered as
186    /// in-range (found).
187    ///
188    /// Valid range is 1 to 300 (seconds).
189    pub rssi_high_timeout: Option<Duration>,
190
191    /// Grouping rules on how to propagate the received
192    /// advertisement packets to the client.
193    pub rssi_sampling_period: Option<RssiSamplingPeriod>,
194
195    /// Patterns to match.
196    ///
197    /// Required if [`monitor_type`](Self::monitor_type) is
198    /// [`Type::OrPatterns`].
199    pub patterns: Option<Vec<Pattern>>,
200
201    #[doc(hidden)]
202    pub _non_exhaustive: (),
203}
204
205/// Information identifying a found or lost device.
206#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
207#[non_exhaustive]
208pub struct DeviceId {
209    /// Bluetooth adapter that found or lost the device.
210    pub adapter: String,
211    /// Device address.
212    pub device: Address,
213}
214
215/// An advertisement monitor event.
216#[derive(Debug, Clone, PartialEq, Eq)]
217#[non_exhaustive]
218pub enum MonitorEvent {
219    /// This event notifies the client of finding the
220    /// targeted device.
221    ///
222    /// Once receiving the event, the client
223    /// should start to monitor the corresponding device to
224    /// retrieve the changes on RSSI and advertisement content.
225    DeviceFound(DeviceId),
226
227    /// This event notifies the client of losing the
228    /// targeted device.
229    ///
230    /// Once receiving this event, the client
231    /// should stop monitoring the corresponding device.
232    DeviceLost(DeviceId),
233}
234
235pub(crate) struct RegisteredMonitor {
236    am: Monitor,
237    activate_tx: mpsc::Sender<()>,
238    release_tx: mpsc::Sender<()>,
239    event_tx: Mutex<Option<mpsc::Sender<MonitorEvent>>>,
240}
241
242impl RegisteredMonitor {
243    fn parse_device_path(device: &dbus::Path<'static>) -> DbusResult<(String, Address)> {
244        match Device::parse_dbus_path(device) {
245            Some((adapter, addr)) => Ok((adapter.to_string(), addr)),
246            None => {
247                log::error!("Cannot parse device path {}", &device);
248                Err(dbus::MethodErr::invalid_arg("cannot parse device path"))
249            }
250        }
251    }
252
253    pub(crate) fn register_interface(cr: &mut Crossroads) -> IfaceToken<Arc<RegisteredMonitor>> {
254        cr.register(INTERFACE, |ib: &mut IfaceBuilder<Arc<RegisteredMonitor>>| {
255            ib.method_with_cr_async("Release", (), (), |ctx, cr, ()| {
256                method_call(ctx, cr, |reg: Arc<RegisteredMonitor>| async move {
257                    *reg.event_tx.lock().await = None;
258                    let _ = reg.release_tx.send(()).await;
259                    Ok(())
260                })
261            });
262
263            ib.method_with_cr_async("Activate", (), (), |ctx, cr, ()| {
264                method_call(ctx, cr, |reg: Arc<RegisteredMonitor>| async move {
265                    let _ = reg.activate_tx.send(()).await;
266                    Ok(())
267                })
268            });
269
270            ib.method_with_cr_async(
271                "DeviceFound",
272                ("device",),
273                (),
274                |ctx, cr, (addr,): (dbus::Path<'static>,)| {
275                    method_call(ctx, cr, |reg: Arc<RegisteredMonitor>| async move {
276                        let (adapter, device) = Self::parse_device_path(&addr)?;
277                        if let Some(event_tx) = reg.event_tx.lock().await.as_ref() {
278                            let _ = event_tx.send(MonitorEvent::DeviceFound(DeviceId { adapter, device })).await;
279                        }
280                        Ok(())
281                    })
282                },
283            );
284
285            ib.method_with_cr_async("DeviceLost", ("device",), (), |ctx, cr, (addr,): (dbus::Path<'static>,)| {
286                method_call(ctx, cr, move |reg: Arc<RegisteredMonitor>| async move {
287                    let (adapter, device) = Self::parse_device_path(&addr)?;
288                    if let Some(event_tx) = reg.event_tx.lock().await.as_ref() {
289                        let _ = event_tx.send(MonitorEvent::DeviceLost(DeviceId { adapter, device })).await;
290                    }
291                    Ok(())
292                })
293            });
294
295            cr_property!(ib, "Type", r => {
296                Some(r.am.monitor_type.to_string())
297            });
298
299            cr_property!(ib, "RSSILowThreshold", r => {
300                r.am.rssi_low_threshold
301            });
302
303            cr_property!(ib, "RSSIHighThreshold", r => {
304                r.am.rssi_high_threshold
305            });
306
307            cr_property!(ib, "RSSILowTimeout", r => {
308                r.am.rssi_low_timeout.map(|t| t.as_secs().clamp(1, 300) as u16)
309            });
310
311            cr_property!(ib, "RSSIHighTimeout", r => {
312                r.am.rssi_high_timeout.map(|t| t.as_secs().clamp(1, 300) as u16)
313            });
314
315            cr_property!(ib, "RSSISamplingPeriod", r => {
316                r.am.rssi_sampling_period.map(|v| v.to_value())
317            });
318
319            cr_property!(ib, "Patterns", r => {
320                r.am.patterns.as_ref().map(|patterns: &Vec<Pattern>| {
321                    patterns
322                        .iter()
323                        .map(|p| (p.start_position, p.data_type, p.content.clone()))
324                        .collect::<Vec<_>>()
325                })
326            });
327        })
328    }
329}
330
331/// A registered advertisement monitor target.
332///
333/// Use this to receive a stream of [advertisement monitor events](MonitorEvent)
334/// for the registered monitor.
335///
336/// While a [`MonitorHandle`] is being held, its events *must* be consumed regularly.
337/// Otherwise it will use an unbounded amount of memory for buffering the unconsumed events.
338///
339/// Drop to unregister the advertisement monitor target.
340#[must_use = "the MonitorHandle must be held for the monitor to be active and its events must be consumed regularly"]
341pub struct MonitorHandle {
342    name: dbus::Path<'static>,
343    event_rx: ReceiverStream<MonitorEvent>,
344    _drop_tx: oneshot::Sender<()>,
345}
346
347impl fmt::Debug for MonitorHandle {
348    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
349        write!(f, "MonitorHandle {{ {} }}", &self.name)
350    }
351}
352
353impl Stream for MonitorHandle {
354    type Item = MonitorEvent;
355
356    fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
357        Pin::into_inner(self).event_rx.poll_next_unpin(cx)
358    }
359}
360
361impl Drop for MonitorHandle {
362    fn drop(&mut self) {
363        // required for drop order
364    }
365}
366
367/// Advertisement monitor manager.
368///
369/// Use [`Adapter::monitor`](crate::adapter::Adapter::monitor) to obtain an instance.
370///
371/// Once a monitoring job is activated by BlueZ, the client can expect to get
372/// notified on the targeted advertisements no matter if there is an ongoing
373/// discovery session.
374///
375/// Use this to target advertisements and drop it to stop monitoring advertisements.
376pub struct MonitorManager {
377    inner: Arc<SessionInner>,
378    root: dbus::Path<'static>,
379    _drop_tx: oneshot::Sender<()>,
380}
381
382impl fmt::Debug for MonitorManager {
383    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
384        f.debug_struct("MonitorManager").finish()
385    }
386}
387
388impl MonitorManager {
389    pub(crate) async fn new(inner: Arc<SessionInner>, adapter_name: &str) -> Result<Self> {
390        let manager_path = dbus::Path::new(format!("{}/{}", MANAGER_PATH, adapter_name)).unwrap();
391        let root = dbus::Path::new(format!("{}/{}", MONITOR_PREFIX, Uuid::new_v4().as_simple())).unwrap();
392
393        log::trace!("Publishing advertisement monitor root at {}", &root);
394
395        {
396            let mut cr = inner.crossroads.lock().await;
397            let object_manager_token = cr.object_manager();
398            let introspectable_token = cr.introspectable();
399            let properties_token = cr.properties();
400            cr.insert(root.clone(), [&object_manager_token, &introspectable_token, &properties_token], ());
401        }
402
403        log::trace!("Registering advertisement monitor root at {}", &root);
404        let proxy = Proxy::new(SERVICE_NAME, manager_path, TIMEOUT, inner.connection.clone());
405        let () = proxy.method_call(MANAGER_INTERFACE, "RegisterMonitor", (root.clone(),)).await?;
406
407        let (_drop_tx, drop_rx) = oneshot::channel();
408        let unreg_root = root.clone();
409        let unreg_inner = inner.clone();
410        tokio::spawn(async move {
411            let _ = drop_rx.await;
412
413            log::trace!("Unregistering advertisement monitor root at {}", &unreg_root);
414            let _: std::result::Result<(), dbus::Error> =
415                proxy.method_call(MANAGER_INTERFACE, "UnregisterMonitor", (unreg_root.clone(),)).await;
416
417            log::trace!("Unpublishing advertisement monitor root at {}", &unreg_root);
418            let mut cr = unreg_inner.crossroads.lock().await;
419            cr.remove::<()>(&unreg_root);
420        });
421
422        Ok(Self { inner, root, _drop_tx })
423    }
424
425    /// Registers an advertisement monitor target.
426    ///
427    /// Returns a handle to receive events.
428    pub async fn register(&self, advertisement_monitor: Monitor) -> Result<MonitorHandle> {
429        let name = dbus::Path::new(format!("{}/{}", &self.root, Uuid::new_v4().as_simple())).unwrap();
430
431        log::trace!("Publishing advertisement monitor target at {}", &name);
432
433        let (activate_tx, mut activate_rx) = mpsc::channel(1);
434        let (release_tx, mut release_rx) = mpsc::channel(1);
435        let (event_tx, event_rx) = mpsc::channel(1024);
436        let (_drop_tx, drop_rx) = oneshot::channel();
437
438        let reg = RegisteredMonitor {
439            am: advertisement_monitor,
440            activate_tx,
441            release_tx,
442            event_tx: Mutex::new(Some(event_tx)),
443        };
444
445        {
446            let mut cr = self.inner.crossroads.lock().await;
447            cr.insert(name.clone(), [&self.inner.monitor_token], Arc::new(reg));
448        }
449
450        let inner = self.inner.clone();
451        let unreg_name = name.clone();
452        tokio::spawn(async move {
453            let _ = drop_rx.await;
454
455            log::trace!("Unpublishing advertisement monitor target at {}", &unreg_name);
456            let mut cr = inner.crossroads.lock().await;
457            cr.remove::<Arc<RegisteredMonitor>>(&unreg_name);
458        });
459
460        tokio::select! {
461            biased;
462            _ = release_rx.recv() => return Err(Error::new(ErrorKind::AdvertisementMonitorRejected)),
463            res = activate_rx.recv() => {
464                if res.is_none() {
465                    return Err(Error::new(ErrorKind::AdvertisementMonitorRejected))
466                }
467            },
468        }
469
470        Ok(MonitorHandle { name, event_rx: event_rx.into(), _drop_tx })
471    }
472}
473
474impl Drop for MonitorManager {
475    fn drop(&mut self) {
476        // required for drop order
477    }
478}