bluer/
adapter.rs

1//! Bluetooth adapter.
2
3use dbus::{
4    arg::{PropMap, RefArg, Variant},
5    nonblock::{Proxy, SyncConnection},
6    Path,
7};
8use futures::{
9    future,
10    stream::{self, SelectAll},
11    Stream, StreamExt,
12};
13use std::{
14    collections::{BTreeSet, HashMap, HashSet},
15    fmt::{Debug, Formatter},
16    sync::Arc,
17};
18use strum::{Display, EnumString};
19use tokio::sync::mpsc;
20use tokio_stream::wrappers::ReceiverStream;
21use uuid::Uuid;
22
23use crate::{
24    adv,
25    adv::{Advertisement, AdvertisementHandle, Capabilities, Feature, PlatformFeature, SecondaryChannel},
26    all_dbus_objects, device,
27    device::Device,
28    gatt,
29    monitor::MonitorManager,
30    Address, AddressType, Error, ErrorKind, Event, InternalErrorKind, Modalias, Result, SessionInner,
31    SingleSessionToken, SERVICE_NAME, TIMEOUT,
32};
33
34pub(crate) const INTERFACE: &str = "org.bluez.Adapter1";
35pub(crate) const PATH: &str = "/org/bluez";
36pub(crate) const PREFIX: &str = "/org/bluez/";
37
38/// Default adapter name.
39pub(crate) const DEFAULT_NAME: &str = "hci0";
40
41/// Interface to a Bluetooth adapter.
42#[cfg_attr(docsrs, doc(cfg(feature = "bluetoothd")))]
43#[derive(Clone)]
44pub struct Adapter {
45    inner: Arc<SessionInner>,
46    dbus_path: Path<'static>,
47    name: Arc<String>,
48}
49
50impl Debug for Adapter {
51    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
52        f.debug_struct("Adapter").field("name", &self.name()).finish()
53    }
54}
55
56impl Adapter {
57    /// Create Bluetooth adapter interface for adapter with specified name.
58    pub(crate) fn new(inner: Arc<SessionInner>, name: &str) -> Result<Self> {
59        Ok(Self {
60            inner,
61            dbus_path: Path::new(PREFIX.to_string() + name)
62                .map_err(|_| Error::new(ErrorKind::InvalidName(name.to_string())))?,
63            name: Arc::new(name.to_string()),
64        })
65    }
66
67    fn proxy(&self) -> Proxy<'_, &SyncConnection> {
68        Proxy::new(SERVICE_NAME, &self.dbus_path, TIMEOUT, &*self.inner.connection)
69    }
70
71    pub(crate) fn dbus_path(adapter_name: &str) -> Result<Path<'static>> {
72        Path::new(format!("{PREFIX}{adapter_name}",))
73            .map_err(|_| Error::new(ErrorKind::InvalidName((*adapter_name).to_string())))
74    }
75
76    pub(crate) fn parse_dbus_path_prefix<'a>(path: &'a Path) -> Option<(&'a str, &'a str)> {
77        match path.strip_prefix(PREFIX) {
78            Some(p) => {
79                let sep = p.find('/').unwrap_or(p.len());
80                Some((&p[0..sep], &p[sep..]))
81            }
82            None => None,
83        }
84    }
85
86    pub(crate) fn parse_dbus_path<'a>(path: &'a Path) -> Option<&'a str> {
87        match Self::parse_dbus_path_prefix(path) {
88            Some((v, "")) => Some(v),
89            _ => None,
90        }
91    }
92
93    /// The Bluetooth adapter name.
94    ///
95    /// For example `hci0`.
96    pub fn name(&self) -> &str {
97        &self.name
98    }
99
100    /// Bluetooth addresses of discovered Bluetooth devices.
101    pub async fn device_addresses(&self) -> Result<Vec<Address>> {
102        let mut addrs = Vec::new();
103        for (path, interfaces) in all_dbus_objects(&self.inner.connection).await? {
104            match Device::parse_dbus_path(&path) {
105                Some((adapter, addr)) if adapter == *self.name && interfaces.contains_key(device::INTERFACE) => {
106                    addrs.push(addr)
107                }
108                _ => (),
109            }
110        }
111        Ok(addrs)
112    }
113
114    /// Starts monitoring of advertisements.
115    ///
116    /// Once a monitoring job is activated by BlueZ, the client can expect to get
117    /// notified on the targeted advertisements no matter if there is an ongoing
118    /// discovery session.
119    ///
120    /// Use the returned [`MonitorManager`] to target advertisements
121    /// and drop it to stop monitoring advertisements.
122    pub async fn monitor(&self) -> Result<MonitorManager> {
123        MonitorManager::new(self.inner.clone(), self.name()).await
124    }
125
126    /// Get interface to Bluetooth device of specified address.
127    pub fn device(&self, address: Address) -> Result<Device> {
128        Device::new(self.inner.clone(), self.name.clone(), address)
129    }
130
131    /// Gets the filter used for device discovery.
132    pub async fn discovery_filter(&self) -> DiscoveryFilter {
133        self.inner.adapter_discovery_filter.lock().await.get(self.name()).cloned().unwrap_or_default()
134    }
135
136    /// Sets the filter used for device discovery.
137    ///
138    /// Setting a discovery filter does not guarantee that all its filters will be applied.
139    /// A discovery session from another program might be active, leading to merging of
140    /// the discovery filters by the Bluetooth daemon.
141    ///
142    /// The discovery filter can only be changed when no device discovery is currently active.
143    /// Otherwise a [DiscoveryActive error](ErrorKind::DiscoveryActive) will be returned.
144    pub async fn set_discovery_filter(&self, discovery_filter: DiscoveryFilter) -> Result<()> {
145        if self.inner.is_single_session_active(&self.dbus_path).await {
146            return Err(Error::new(ErrorKind::DiscoveryActive));
147        }
148
149        self.inner.adapter_discovery_filter.lock().await.insert(self.name().to_string(), discovery_filter);
150        Ok(())
151    }
152
153    /// This method starts the device discovery session.
154    ///
155    /// This includes an inquiry procedure and remote device name resolving.
156    ///
157    /// This process will start streaming device addresses as new devices are discovered.
158    /// A device may be discovered multiple times.
159    ///
160    /// All already known devices are also included in the device stream.
161    /// This may include devices that are currently not in range.
162    /// Check the [Device::rssi] property to see if the device is currently present.
163    ///
164    /// Device properties are queried asynchronously and may not be available
165    /// yet when a [DeviceAdded event](AdapterEvent::DeviceAdded) occurs.
166    /// Use [discover_devices_with_changes](Self::discover_devices_with_changes)
167    /// when you want to be notified when the device properties change.
168    ///
169    /// The discovery filter can be configured using [set_discovery_filter](Self::set_discovery_filter).
170    pub async fn discover_devices(&self) -> Result<impl Stream<Item = AdapterEvent>> {
171        let token = self.discovery_session().await?;
172        let change_events = self
173            .events()
174            .await?
175            .map(move |evt| {
176                let _token = &token;
177                evt
178            })
179            .take_while(|evt| {
180                future::ready(!matches!(evt, AdapterEvent::PropertyChanged(AdapterProperty::Discovering(false))))
181            });
182
183        let known = self.device_addresses().await?;
184        let known_events = stream::iter(known).map(AdapterEvent::DeviceAdded);
185
186        let all_events = known_events.chain(change_events);
187
188        Ok(all_events)
189    }
190
191    /// This method starts the device discovery session and notifies of device property changes.
192    ///
193    /// This includes an inquiry procedure and remote device name resolving.
194    ///
195    /// This process will start streaming device addresses as new devices are discovered.
196    /// Each time device properties change you will receive an additional
197    /// [DeviceAdded event](AdapterEvent::DeviceAdded) for that device.
198    ///
199    /// All already known devices are also included in the device stream.
200    /// This may include devices that are currently not in range.
201    /// Check the [Device::rssi] property to see if the device is currently present.
202    ///
203    /// The discovery filter can be configured using [set_discovery_filter](Self::set_discovery_filter).
204    pub async fn discover_devices_with_changes(&self) -> Result<impl Stream<Item = AdapterEvent>> {
205        let (tx, rx) = mpsc::channel(1);
206        let mut discovery = self.discover_devices().await?;
207        let adapter = self.clone();
208
209        tokio::spawn(async move {
210            let mut changes = SelectAll::new();
211
212            loop {
213                tokio::select! {
214                    evt = discovery.next() => {
215                        match evt {
216                            Some(AdapterEvent::DeviceAdded(addr)) => {
217                                if let Ok(dev) = adapter.device(addr) {
218                                    if let Ok(dev_evts) = dev.events().await {
219                                        changes.push(dev_evts.map(move |_| addr));
220                                    }
221                                }
222                                let _ = tx.send(AdapterEvent::DeviceAdded(addr)).await;
223                            },
224                            Some(AdapterEvent::DeviceRemoved(addr)) => {
225                                let _ = tx.send(AdapterEvent::DeviceRemoved(addr)).await;
226                            },
227                            Some(_) => (),
228                            None => break,
229                        }
230                    },
231                    Some(addr) = changes.next(), if !changes.is_empty() => {
232                        let _ = tx.send(AdapterEvent::DeviceAdded(addr)).await;
233                    },
234                    () = tx.closed() => break,
235                }
236            }
237        });
238
239        Ok(ReceiverStream::new(rx))
240    }
241
242    async fn discovery_session(&self) -> Result<SingleSessionToken> {
243        let dbus_path = self.dbus_path.clone();
244        let connection = self.inner.connection.clone();
245        let token = self
246            .inner
247            .single_session(
248                &self.dbus_path,
249                async move {
250                    let () = self
251                        .call_method("SetDiscoveryFilter", (self.discovery_filter().await.into_dict(),))
252                        .await?;
253                    let () = self.call_method("StartDiscovery", ()).await?;
254                    Ok(())
255                },
256                async move {
257                    log::trace!("{}: {}.StopDiscovery ()", &dbus_path, SERVICE_NAME);
258                    let proxy = Proxy::new(SERVICE_NAME, &dbus_path, TIMEOUT, &*connection);
259                    let result: std::result::Result<(), dbus::Error> =
260                        proxy.method_call(INTERFACE, "StopDiscovery", ()).await;
261                    log::trace!("{}: {}.StopDiscovery () -> {:?}", &dbus_path, SERVICE_NAME, &result);
262                },
263            )
264            .await?;
265        Ok(token)
266    }
267
268    dbus_interface!();
269    dbus_default_interface!(INTERFACE);
270
271    /// Streams adapter property and device changes.
272    ///
273    /// The stream ends when the adapter is removed.
274    pub async fn events(&self) -> Result<impl Stream<Item = AdapterEvent>> {
275        let name = self.name.clone();
276        let events = self.inner.events(self.dbus_path.clone(), true).await?;
277        let stream = events.flat_map(move |event| match event {
278            Event::ObjectAdded { object, .. } => match Device::parse_dbus_path(&object) {
279                Some((adapter, address)) if adapter == *name => {
280                    stream::once(async move { AdapterEvent::DeviceAdded(address) }).boxed()
281                }
282                _ => stream::empty().boxed(),
283            },
284            Event::ObjectRemoved { object, .. } => match Device::parse_dbus_path(&object) {
285                Some((adapter, address)) if adapter == *name => {
286                    stream::once(async move { AdapterEvent::DeviceRemoved(address) }).boxed()
287                }
288                _ => stream::empty().boxed(),
289            },
290            Event::PropertiesChanged { changed, .. } => stream::iter(
291                AdapterProperty::from_prop_map(changed).into_iter().map(AdapterEvent::PropertyChanged),
292            )
293            .boxed(),
294        });
295        Ok(stream)
296    }
297
298    /// Registers an advertisement object to be sent over the LE
299    /// Advertising channel.
300    ///
301    /// InvalidArguments error indicates that the object has
302    /// invalid or conflicting properties.
303    ///
304    /// InvalidLength error indicates that the data
305    /// provided generates a data packet which is too long.
306    ///
307    /// The properties of this object are parsed when it is
308    /// registered, and any changes are ignored.
309    ///
310    /// If the same object is registered twice it will result in
311    /// an AlreadyExists error.
312    ///
313    /// If the maximum number of advertisement instances is
314    /// reached it will result in NotPermitted error.
315    ///
316    /// Drop the returned [AdvertisementHandle] to unregister the advertisement.
317    pub async fn advertise(&self, le_advertisement: Advertisement) -> Result<AdvertisementHandle> {
318        le_advertisement.register(self.inner.clone(), self.name.clone()).await
319    }
320
321    /// Registers a local GATT services hierarchy (GATT Server).
322    ///
323    /// Registering a service allows applications to publish a *local* GATT service,
324    /// which then becomes available to remote devices.
325    ///
326    /// Drop the returned [ApplicationHandle](gatt::local::ApplicationHandle) to unregister the application.
327    pub async fn serve_gatt_application(
328        &self, gatt_application: gatt::local::Application,
329    ) -> Result<gatt::local::ApplicationHandle> {
330        gatt_application.register(self.inner.clone(), self.name.clone()).await
331    }
332
333    /// Registers local GATT profiles (GATT Client).
334    ///
335    /// By registering this type of object
336    /// an application effectively indicates support for a specific GATT profile
337    /// and requests automatic connections to be established to devices
338    /// supporting it.
339    ///
340    /// Drop the returned [ProfileHandle](gatt::local::ProfileHandle) to unregister the application.
341    pub async fn register_gatt_profile(
342        &self, gatt_profile: gatt::local::Profile,
343    ) -> Result<gatt::local::ProfileHandle> {
344        gatt_profile.register(self.inner.clone(), self.name.clone()).await
345    }
346
347    // ===========================================================================================
348    // Methods
349    // ===========================================================================================
350
351    /// This removes the remote device object for the given
352    /// device address.
353    ///
354    /// It will remove also the pairing information.
355    pub async fn remove_device(&self, address: Address) -> Result<()> {
356        let path = Device::dbus_path(self.name(), address)?;
357        let () = self.call_method("RemoveDevice", ((path),)).await?;
358        Ok(())
359    }
360
361    /// This method connects to device without need of
362    /// performing General Discovery.
363    ///
364    /// Connection mechanism is
365    /// similar to Connect method from Device1 interface with
366    /// exception that this method returns success when physical
367    /// connection is established. After this method returns,
368    /// services discovery will continue and any supported
369    /// profile will be connected. There is no need for calling
370    /// Connect on Device1 after this call. If connection was
371    /// successful this method returns the created
372    /// device object.
373    ///
374    /// Parameters are the following:
375    ///
376    /// * `address` -
377    ///     The Bluetooth device address of the remote
378    ///     device.
379    /// * `address_type` -
380    ///     The Bluetooth device Address Type. This is
381    ///     address type that should be used for initial
382    ///     connection.
383    ///
384    /// This method is experimental.
385    pub async fn connect_device(&self, address: Address, address_type: AddressType) -> Result<Device> {
386        let mut m = PropMap::new();
387        m.insert("Address".to_string(), Variant(address.to_string().box_clone()));
388        match address_type {
389            AddressType::LePublic | AddressType::LeRandom => {
390                m.insert("AddressType".to_string(), Variant(address_type.to_string().box_clone()));
391            }
392            AddressType::BrEdr => (),
393        }
394        let (_path,): (Path,) = self.call_method("ConnectDevice", (m,)).await?;
395
396        self.device(address)
397    }
398}
399
400define_properties!(
401    Adapter,
402    /// Bluetooth adapter property.
403    pub AdapterProperty => {
404
405        // ===========================================================================================
406        // Adapter properties
407        // ===========================================================================================
408
409        /// The Bluetooth adapter address.
410        property(
411            Address, Address,
412            dbus: (INTERFACE, "Address", String, MANDATORY),
413            get: (address, v => { v.parse()? }),
414        );
415
416        /// The Bluetooth adapter address type.
417        ///
418        /// For dual-mode and BR/EDR
419        /// only adapter this defaults to "public". Single mode LE
420        /// adapters may have either value. With privacy enabled
421        /// this contains type of Identity Address and not type of
422        /// address used for connection.
423        property(
424            AddressType, AddressType,
425            dbus: (INTERFACE, "AddressType", String, MANDATORY),
426            get: (address_type, v => {v.parse()?}),
427        );
428
429        ///	The Bluetooth system name (pretty hostname).
430        ///
431        /// This property is either a static system default
432        /// or controlled by an external daemon providing
433        /// access to the pretty hostname configuration.
434        property(
435            SystemName, String,
436            dbus: (INTERFACE, "Name", String, MANDATORY),
437            get: (system_name, v => {v.to_owned()}),
438        );
439
440        /// The Bluetooth friendly name.
441        ///
442        /// This value can be changed.
443        ///
444        /// In case no alias is set, it will return the system
445        /// provided name. Setting an empty string as alias will
446        /// convert it back to the system provided name.
447        ///
448        /// When resetting the alias with an empty string, the
449        /// property will default back to system name.
450        ///
451        /// On a well configured system, this property never
452        /// needs to be changed since it defaults to the system
453        /// name and provides the pretty hostname. Only if the
454        /// local name needs to be different from the pretty
455        /// hostname, this property should be used as last
456        /// resort.
457        property(
458            Alias, String,
459            dbus: (INTERFACE, "Alias", String, MANDATORY),
460            get: (alias, v => {v.to_owned()}),
461            set: (set_alias, v => {v}),
462        );
463
464        /// The Bluetooth class of device.
465        ///
466        ///	This property represents the value that is either
467        ///	automatically configured by DMI/ACPI information
468        ///	or provided as static configuration.
469        property(
470            Class, u32,
471            dbus: (INTERFACE, "Class", u32, MANDATORY),
472            get: (class, v => {v.to_owned()}),
473        );
474
475        /// Switch an adapter on or off. This will also set the
476        /// appropriate connectable state of the controller.
477        ///
478        /// The value of this property is not persistent. After
479        /// restart or unplugging of the adapter it will reset
480        /// back to false.
481        property(
482            Powered, bool,
483            dbus: (INTERFACE, "Powered", bool, MANDATORY),
484            get: (is_powered, v => {v.to_owned()}),
485            set: (set_powered, v => {v}),
486        );
487
488        /// Switch an adapter to discoverable or non-discoverable
489        /// to either make it visible or hide it.
490        ///
491        /// This is a global
492        /// setting and should only be used by the settings
493        /// application.
494        ///
495        /// If the DiscoverableTimeout is set to a non-zero
496        /// value then the system will set this value back to
497        /// false after the timer expired.
498        ///
499        /// In case the adapter is switched off, setting this
500        /// value will fail.
501        ///
502        /// When changing the Powered property the new state of
503        /// this property will be updated via a PropertiesChanged
504        /// signal.
505        ///
506        /// For any new adapter this settings defaults to false.
507        ///
508        /// To send Bluetooth LE advertisements use the
509        /// [advertise](Adapter::advertise) method instead.
510        property(
511            Discoverable, bool,
512            dbus: (INTERFACE, "Discoverable", bool, MANDATORY),
513            get: (is_discoverable, v => {v.to_owned()}),
514            set: (set_discoverable, v => {v}),
515        );
516
517        /// Switch an adapter to pairable or non-pairable.
518        ///
519        /// This is
520        /// a global setting and should only be used by the
521        /// settings application.
522        ///
523        /// Note that this property only affects incoming pairing
524        /// requests.
525        ///
526        /// For any new adapter this settings defaults to true.
527        property(
528            Pairable, bool,
529            dbus: (INTERFACE, "Pairable", bool, MANDATORY),
530            get: (is_pairable, v => {v.to_owned()}),
531            set: (set_pairable, v => {v}),
532        );
533
534        /// The pairable timeout in seconds.
535        ///
536        /// A value of zero
537        /// means that the timeout is disabled and it will stay in
538        /// pairable mode forever.
539        ///
540        /// The default value for pairable timeout should be
541        /// disabled (value 0).
542        property(
543            PairableTimeout, u32,
544            dbus: (INTERFACE, "PairableTimeout", u32, MANDATORY),
545            get: (pairable_timeout, v => {v.to_owned()}),
546            set: (set_pairable_timeout, v => {v}),
547        );
548
549        /// The discoverable timeout in seconds.
550        ///
551        /// A value of zero
552        /// means that the timeout is disabled and it will stay in
553        /// discoverable/limited mode forever.
554        ///
555        /// The default value for the discoverable timeout should
556        /// be 180 seconds (3 minutes).
557        property(
558            DiscoverableTimeout, u32,
559            dbus: (INTERFACE, "DiscoverableTimeout", u32, MANDATORY),
560            get: (discoverable_timeout, v => {v.to_owned()}),
561            set: (set_discoverable_timeout, v => {v}),
562        );
563
564        ///	Indicates that a device discovery procedure is active.
565        property(
566            Discovering, bool,
567            dbus: (INTERFACE, "Discovering", bool, MANDATORY),
568            get: (is_discovering, v => {v.to_owned()}),
569        );
570
571        /// List of 128-bit UUIDs that represents the available
572        /// local services.
573        property(
574            Uuids, HashSet<Uuid>,
575            dbus: (INTERFACE, "UUIDs", Vec<String>, OPTIONAL),
576            get: (uuids, v => {
577                v
578                .iter()
579                .map(|uuid| {
580                    uuid.parse()
581                        .map_err(|_| Error::new(ErrorKind::Internal(InternalErrorKind::InvalidUuid(uuid.to_string()))))
582                })
583                .collect::<Result<HashSet<Uuid>>>()?
584            }),
585        );
586
587        /// Local Device ID information in modalias format
588        /// used by the kernel and udev.
589        property(
590            Modalias, Modalias,
591            dbus: (INTERFACE, "Modalias", String, OPTIONAL),
592            get: (modalias, v => { v.parse()? }),
593        );
594
595        // ===========================================================================================
596        // LE advertising manager properties
597        // ===========================================================================================
598
599        ///	Number of active advertising instances.
600        property(
601            ActiveAdvertisingInstances, u8,
602            dbus: (adv::MANAGER_INTERFACE, "ActiveInstances", u8, MANDATORY),
603            get: (active_advertising_instances, v => {v.to_owned()}),
604        );
605
606        ///	Number of available advertising instances.
607        property(
608            SupportedAdvertisingInstances, u8,
609            dbus: (adv::MANAGER_INTERFACE, "SupportedInstances", u8, MANDATORY),
610            get: (supported_advertising_instances, v => {v.to_owned()}),
611        );
612
613        /// List of supported system includes.
614        property(
615            SupportedAdvertisingSystemIncludes, BTreeSet<Feature>,
616            dbus: (adv::MANAGER_INTERFACE, "SupportedIncludes", Vec<String>, MANDATORY),
617            get: (supported_advertising_system_includes, v => {
618                v.iter().filter_map(|s| s.parse().ok()).collect()
619            }),
620        );
621
622        /// List of supported Secondary channels.
623        ///
624        /// Secondary
625        /// channels can be used to advertise with the
626        /// corresponding PHY.
627        property(
628            SupportedAdvertisingSecondaryChannels, BTreeSet<SecondaryChannel>,
629            dbus: (adv::MANAGER_INTERFACE, "SupportedSecondaryChannels", Vec<String>, OPTIONAL),
630            get: (supported_advertising_secondary_channels, v => {
631                v.iter().filter_map(|s| s.parse().ok()).collect()
632            }),
633        );
634
635        /// Enumerates Advertising-related controller capabilities
636        /// useful to the client.
637        property(
638            SupportedAdvertisingCapabilities, Capabilities,
639            dbus: (adv::MANAGER_INTERFACE, "SupportedCapabilities", HashMap<String, Variant<Box<dyn RefArg  + 'static>>>, OPTIONAL),
640            get: (supported_advertising_capabilities, v => {
641                Capabilities::from_dict(v)?
642            }),
643        );
644
645        /// List of supported platform features.
646        ///
647        /// If no features
648        /// are available on the platform, the SupportedFeatures
649        /// array will be empty.
650        property(
651            SupportedAdvertisingFeatures, BTreeSet<PlatformFeature>,
652            dbus: (adv::MANAGER_INTERFACE, "SupportedFeatures", Vec<String>, OPTIONAL),
653            get: (supported_advertising_features, v => {
654                v.iter().filter_map(|s| s.parse().ok()).collect()
655            }),
656        );
657    }
658);
659
660/// Bluetooth adapter event.
661#[cfg_attr(docsrs, doc(cfg(feature = "bluetoothd")))]
662#[derive(Clone, Debug)]
663#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
664pub enum AdapterEvent {
665    /// Bluetooth device with specified address was added.
666    DeviceAdded(Address),
667    /// Bluetooth device with specified address was removed.
668    DeviceRemoved(Address),
669    /// Bluetooth adapter property changed.
670    PropertyChanged(AdapterProperty),
671}
672
673/// Transport parameter determines the type of scan.
674#[cfg_attr(docsrs, doc(cfg(feature = "bluetoothd")))]
675#[derive(Debug, Clone, Copy, Eq, PartialEq, Display, EnumString)]
676#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
677#[non_exhaustive]
678pub enum DiscoveryTransport {
679    /// interleaved scan
680    #[strum(serialize = "auto")]
681    Auto,
682    /// BR/EDR inquiry
683    #[strum(serialize = "bredr")]
684    BrEdr,
685    /// LE scan only
686    #[strum(serialize = "le")]
687    Le,
688}
689
690impl Default for DiscoveryTransport {
691    fn default() -> Self {
692        Self::Auto
693    }
694}
695
696/// Bluetooth device discovery filter.
697///
698/// The default discovery filter does not restrict any devices and provides
699/// [duplicate data](Self::duplicate_data).
700#[cfg_attr(docsrs, doc(cfg(feature = "bluetoothd")))]
701#[derive(Default, Clone, Debug, Eq, PartialEq)]
702#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
703pub struct DiscoveryFilter {
704    ///  Filter by service UUIDs, empty means match
705    ///  _any_ UUID.
706    ///
707    ///  When a remote device is found that advertises
708    ///  any UUID from UUIDs, it will be reported if:
709    ///  - pathloss and RSSI are both empty.
710    ///  - only pathloss param is set, device advertise
711    ///    TX power, and computed pathloss is less than
712    ///    pathloss param.
713    ///  - only RSSI param is set, and received RSSI is
714    ///    higher than RSSI param.
715    pub uuids: HashSet<Uuid>,
716    /// RSSI threshold value.
717    ///
718    /// PropertiesChanged signals will be emitted
719    /// for already existing Device objects, with
720    /// updated RSSI value. If one or more discovery
721    /// filters have been set, the RSSI delta-threshold,
722    /// that is imposed by StartDiscovery by default,
723    /// will not be applied.
724    pub rssi: Option<i16>,
725    /// Pathloss threshold value.
726    ///
727    /// PropertiesChanged signals will be emitted
728    /// for already existing Device objects, with
729    /// updated Pathloss value.
730    ///
731    /// Must not be set when [`rssi`](Self::rssi) is set.
732    pub pathloss: Option<u16>,
733    /// Transport parameter determines the type of
734    /// scan.
735    ///
736    /// Possible values:
737    ///     "auto"  - interleaved scan
738    ///     "bredr" - BR/EDR inquiry
739    ///     "le"    - LE scan only
740    ///
741    /// If "le" or "bredr" Transport is requested,
742    /// and the controller doesn't support it,
743    /// org.bluez.Error.Failed error will be returned.
744    ///
745    /// If "auto" transport is requested, scan will use
746    /// LE, BREDR, or both, depending on what's
747    /// currently enabled on the controller.
748    pub transport: DiscoveryTransport,
749    /// Disables duplicate detection of advertisement data.
750    ///
751    /// When enabled PropertiesChanged signals will be
752    /// generated for either ManufacturerData and
753    /// ServiceData every time they are discovered.
754    pub duplicate_data: bool,
755    /// Make adapter discoverable while discovering.
756    ///
757    /// If the adapter is already discoverable setting
758    /// this filter won't do anything.
759    pub discoverable: bool,
760    /// Discover devices where the pattern matches
761    /// either the prefix of the address or
762    /// device name which is convenient way to limited
763    /// the number of device objects created during a
764    /// discovery.
765    ///
766    /// When set disregards device discoverable flags.
767    ///
768    /// Note: The pattern matching is ignored if there
769    /// are other client that don't set any pattern as
770    /// it work as a logical OR, also setting empty
771    /// string "" pattern will match any device found.
772    pub pattern: Option<String>,
773    #[doc(hidden)]
774    #[cfg_attr(feature = "serde", serde(skip))]
775    pub _non_exhaustive: (),
776}
777
778impl DiscoveryFilter {
779    fn into_dict(self) -> HashMap<&'static str, Variant<Box<dyn RefArg>>> {
780        let mut hm: HashMap<&'static str, Variant<Box<dyn RefArg>>> = HashMap::new();
781        let Self { uuids, rssi, pathloss, transport, duplicate_data, discoverable, pattern, _non_exhaustive } =
782            self;
783        hm.insert("UUIDs", Variant(Box::new(uuids.into_iter().map(|uuid| uuid.to_string()).collect::<Vec<_>>())));
784        if let Some(rssi) = rssi {
785            hm.insert("RSSI", Variant(Box::new(rssi)));
786        }
787        if let Some(pathloss) = pathloss {
788            hm.insert("Pathloss", Variant(Box::new(pathloss)));
789        }
790        hm.insert("Transport", Variant(Box::new(transport.to_string())));
791
792        // WORKAROUND: bluetoothd has a bug that causes it to crash when deserializing a bool
793        //             from D-Bus on some architectures (at least 32-bit ARM). We can partly
794        //             work around this by omitting the bools from the dictionary, if they match
795        //             the default values.
796        if duplicate_data {
797            hm.insert("DuplicateData", Variant(Box::new(true)));
798        }
799        if discoverable {
800            hm.insert("Discoverable", Variant(Box::new(true)));
801        }
802
803        if let Some(pattern) = pattern {
804            hm.insert("Pattern", Variant(Box::new(pattern)));
805        }
806        hm
807    }
808}