diff --git a/fastpair/rust/src/bluetooth/common/adapter.rs b/fastpair/rust/src/bluetooth/common/adapter.rs index 40a504ee..1b07e250 100644 --- a/fastpair/rust/src/bluetooth/common/adapter.rs +++ b/fastpair/rust/src/bluetooth/common/adapter.rs @@ -25,11 +25,11 @@ pub trait Adapter: Sized { /// Retrieve the system-default Bluetooth adapter. async fn default() -> Result; - /// Begin scanning for nearby devices. - fn start_scan_devices(&mut self) -> Result<(), BluetoothError>; + /// Begin scanning for nearby advertisements. + fn start_scan(&mut self) -> Result<(), BluetoothError>; - /// Stop scanning for nearby devices. - fn stop_scan_devices(&mut self) -> Result<(), BluetoothError>; + /// Stop scanning for nearby advertisements. + fn stop_scan(&mut self) -> Result<(), BluetoothError>; /// Poll next discovered device. async fn next_device(&mut self) -> Result; diff --git a/fastpair/rust/src/bluetooth/common/address.rs b/fastpair/rust/src/bluetooth/common/address.rs index dc54e067..9cbf2367 100644 --- a/fastpair/rust/src/bluetooth/common/address.rs +++ b/fastpair/rust/src/bluetooth/common/address.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::bluetooth::common::BluetoothError; + /// BLE Addresses can either be the peripheral's public MAC address, or various /// types of random addresses. #[derive(PartialEq, Eq, Clone, Copy, Debug)] @@ -68,15 +70,14 @@ impl From for ClassicAddress { } impl TryFrom for ClassicAddress { - // TODO proper error handling b/291931475 - type Error = anyhow::Error; + type Error = BluetoothError; fn try_from(addr: BleAddress) -> Result { match addr.kind { BleAddressKind::Public => Ok(ClassicAddress(addr.val)), - BleAddressKind::Random => Err(anyhow::anyhow!( - "Can't convert BLE Random address to Bluetooth Classic address." - )), + BleAddressKind::Random => Err(BluetoothError::BadTypeConversion(String::from( + "can't convert BLE Random address to Bluetooth Classic address." + ))), } } } diff --git a/fastpair/rust/src/bluetooth/common/error.rs b/fastpair/rust/src/bluetooth/common/error.rs index 5c9e49e9..8b20b36e 100644 --- a/fastpair/rust/src/bluetooth/common/error.rs +++ b/fastpair/rust/src/bluetooth/common/error.rs @@ -18,6 +18,10 @@ use thiserror::Error; #[non_exhaustive] #[derive(Error, Debug)] pub enum BluetoothError { + /// Reported when the user attempts a bad type conversion, e.g. converting + /// a BLE random address to a BT Classic address. + #[error("bad type conversion: {0}")] + BadTypeConversion(String), /// Reported when Bluetooth device pairing fails. #[error("pairing error: {0}")] PairingFailed(String), diff --git a/fastpair/rust/src/bluetooth/unsupported/adapter.rs b/fastpair/rust/src/bluetooth/unsupported/adapter.rs index 8baa3b81..2794b172 100644 --- a/fastpair/rust/src/bluetooth/unsupported/adapter.rs +++ b/fastpair/rust/src/bluetooth/unsupported/adapter.rs @@ -29,11 +29,11 @@ impl Adapter for BleAdapter { panic!("Unsupported target platform."); } - fn start_scan_devices(&mut self) -> Result<(), BluetoothError> { + fn start_scan(&mut self) -> Result<(), BluetoothError> { panic!("Unsupported target platform."); } - fn stop_scan_devices(&mut self) -> Result<(), BluetoothError> { + fn stop_scan(&mut self) -> Result<(), BluetoothError> { panic!("Unsupported target platform."); } diff --git a/fastpair/rust/src/bluetooth/windows/adapter.rs b/fastpair/rust/src/bluetooth/windows/adapter.rs index 57fb533d..3ffcfb33 100644 --- a/fastpair/rust/src/bluetooth/windows/adapter.rs +++ b/fastpair/rust/src/bluetooth/windows/adapter.rs @@ -12,12 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::pin::Pin; use std::sync::Arc; use async_trait::async_trait; -use futures::{stream::Stream, StreamExt}; -use tracing::{error, warn}; +use futures::{channel::mpsc::Receiver, StreamExt}; +use tracing::{error, info, warn}; use windows::{ Devices::Bluetooth::{ Advertisement::{ @@ -55,16 +54,23 @@ use windows::{ }; use super::BleDevice; -use crate::bluetooth::common::{Adapter, BleAddress, BleAddressKind, BluetoothError}; +use crate::bluetooth::common::{ + Adapter, BleAddress, BleAddressKind, BluetoothError, +}; + +/// Struct holding the necessary fields for listening to and handling incoming +/// BLE advertisements. +struct AdvListener { + /// Holds callback for sending received advertisement events to `receiver`. + watcher: BluetoothLEAdvertisementWatcher, + /// Can be polled to consume incoming advertisement events. + receiver: Receiver, +} /// Concrete type implementing `Adapter`, used for Windows BLE. pub struct BleAdapter { inner: BluetoothAdapter, - // NOTE: Using Boxed dyn here is silly because only one concrete type ever - // used. Change this to `impl Stream` once impl trait return types - // stabilized for existential types. - // b/289224233. - device_stream: Option + Send + Sync>>>, + listener: Option, } #[async_trait] @@ -87,11 +93,11 @@ impl Adapter for BleAdapter { Ok(BleAdapter { inner, - device_stream: None, + listener: None, }) } - fn start_scan_devices(&mut self) -> Result<(), BluetoothError> { + fn start_scan(&mut self) -> Result<(), BluetoothError> { let watcher = BluetoothLEAdvertisementWatcher::new()?; match watcher.SetScanningMode(BluetoothLEScanningMode::Active) { Ok(_) => (), @@ -149,7 +155,7 @@ impl Adapter for BleAdapter { >| { // Drop `sender`, closing the channel. let _sender = sender.take(); - println!("Watcher stopped receiving BLE advertisements."); + info!("Watcher stopped receiving BLE advertisements."); Ok(()) }, ); @@ -158,48 +164,14 @@ impl Adapter for BleAdapter { watcher.Stopped(&stopped_handler)?; watcher.Start()?; - // `receiver` is a `futures::channel::mpsc::Receiver`, which implements - // `futures::stream::Stream`. This is essentially an async Iterator. - // We apply a FilterMap to map from advertisement packet to a future - // returning `BleDevice` and filter out undesired connections. We need a - // pinned box to satisfy trait bounds for `Stream`. - self.device_stream = - Some(Box::pin(receiver.filter_map(move |event_args| { - // Move `watcher` into `FilterMap` closure. This ensures `watcher` - // is only dropped when the stream is closed. - let _watcher = &watcher; - - // Move `event_args` into async block. - async move { - match event_args.AdvertisementType().ok()? { - BluetoothLEAdvertisementType::NonConnectableUndirected => { - None - } - _ => { - let kind = event_args.BluetoothAddressType().ok()?; - let addr = event_args.BluetoothAddress().ok()?; - - let kind = BleAddressKind::try_from(kind).ok()?; - let addr = BleAddress::new(addr, kind); - - match BleDevice::new(addr).await { - Ok(device) => Some(device), - Err(err) => { - warn!("Error creating device: {:?}", err); - None - } - } - } - } - } - }))); + self.listener = Some(AdvListener { watcher, receiver }); Ok(()) } - fn stop_scan_devices(&mut self) -> Result<(), BluetoothError> { - if let Some(_) = &self.device_stream { - self.device_stream.take(); + fn stop_scan(&mut self) -> Result<(), BluetoothError> { + if let Some(listener) = self.listener.take() { + listener.watcher.Stop()?; Ok(()) } else { Err(BluetoothError::FailedPrecondition(String::from( @@ -209,13 +181,37 @@ impl Adapter for BleAdapter { } async fn next_device(&mut self) -> Result { - if let Some(stream) = &mut self.device_stream { - stream - .next() - .await - .ok_or(BluetoothError::Internal(String::from( - "device returned from Stream is None", - ))) + if let Some(listener) = &mut self.listener { + let stream = &mut listener.receiver; + // We don't want the end-user to receive empty devices, so this is a + // loop to catch and skip trivial errors from advertisements that + // can't be turned into devices. + loop { + let event_args = + stream.next().await.ok_or(BluetoothError::Internal( + String::from("Event returned from stream is None."), + ))?; + + match event_args.AdvertisementType()? { + BluetoothLEAdvertisementType::NonConnectableUndirected => { + () + } + _ => { + let kind = event_args.BluetoothAddressType()?; + let addr = event_args.BluetoothAddress()?; + + let kind = BleAddressKind::try_from(kind)?; + let addr = BleAddress::new(addr, kind); + + match BleDevice::new(addr).await { + Ok(device) => break Ok(device), + Err(err) => { + warn!("Error creating device: {:?}", err); + } + } + } + } + } } else { Err(BluetoothError::FailedPrecondition(String::from( "device scanning hasn't started, please call `start_scan()`", diff --git a/fastpair/rust/src/bluetooth/windows/address.rs b/fastpair/rust/src/bluetooth/windows/address.rs index 6f587237..d08ae4f3 100644 --- a/fastpair/rust/src/bluetooth/windows/address.rs +++ b/fastpair/rust/src/bluetooth/windows/address.rs @@ -16,21 +16,23 @@ //https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.bluetoothaddresstype?view=winrt-22621 use windows::Devices::Bluetooth::BluetoothAddressType; -use crate::bluetooth::common::BleAddressKind; +use crate::bluetooth::common::{BleAddressKind, BluetoothError}; // Convenience for converting from Windows API to crate API. impl TryFrom for BleAddressKind { - type Error = anyhow::Error; + type Error = BluetoothError; fn try_from(kind: BluetoothAddressType) -> Result { match kind { BluetoothAddressType::Public => Ok(BleAddressKind::Public), BluetoothAddressType::Random => Ok(BleAddressKind::Random), - BluetoothAddressType::Unspecified => Err(anyhow::anyhow!( - "Attempting to construct `BleAddressKind` with device \ - advertising Unspecified address type." - )), - _ => Err(anyhow::anyhow!(format!( + BluetoothAddressType::Unspecified => { + Err(BluetoothError::BadTypeConversion(String::from( + "Attempting to construct `BleAddressKind` with device \ + advertising Unspecified address type.", + ))) + } + _ => Err(BluetoothError::BadTypeConversion(format!( "Attempting to construct `BleAddressKind` with device \ advertising invalid address type {}.", kind.0, diff --git a/fastpair/rust/src/main.rs b/fastpair/rust/src/main.rs index 7b796f5a..95c665c2 100644 --- a/fastpair/rust/src/main.rs +++ b/fastpair/rust/src/main.rs @@ -23,7 +23,7 @@ use bluetooth::{Adapter, Address, ClassicAddress, Device}; fn main() -> Result<(), Box> { let run = async { let mut adapter = bluetooth::default_adapter().await?; - adapter.start_scan_devices()?; + adapter.start_scan()?; while let Ok(ble_device) = adapter.next_device().await { let name = ble_device.name()?;