From c51f91f1e0d721962c1e3e5802eea386386a4b96 Mon Sep 17 00:00:00 2001 From: Lucas Silva Shepard Date: Mon, 26 Jun 2023 18:11:11 -0700 Subject: [PATCH 1/7] [fp-rs] Adding Windows build to Fast Pair Rust validator in Github Actions. --- .github/workflows/validate.yaml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml index 10485ac3..62890323 100644 --- a/.github/workflows/validate.yaml +++ b/.github/workflows/validate.yaml @@ -47,6 +47,16 @@ jobs: submodules: recursive - name: Build FPP run: cargo build --manifest-path presence/fpp/fpp/Cargo.toml - - name: Build Fairpair + - name: Build Fast Pair run: cargo build --manifest-path fastpair/rust/Cargo.toml - + + build-rust-windows: + name: Build Rust on Windows + runs-on: windows-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + - name: Build Fast Pair + run: cargo build --manifest-path fastpair/rust/Cargo.toml + \ No newline at end of file From 5306df92514727b05a7194155705f58d523a9344 Mon Sep 17 00:00:00 2001 From: Lucas Silva Shepard Date: Thu, 8 Jun 2023 14:16:54 -0700 Subject: [PATCH 2/7] [fp-rs] Adding Rust and IntelliJ to .gitignore --- .gitignore | 6 ++++++ fastpair/rust/.gitignore | 2 ++ 2 files changed, 8 insertions(+) create mode 100644 fastpair/rust/.gitignore diff --git a/.gitignore b/.gitignore index 39b82caf..b1c8f94a 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,9 @@ Carthage/Build .UlyssesRoot .Ulysses-Settings.plist .Ulysses-Group.plist + +# IntelliJ +.idea + +# Rust +Cargo.lock \ No newline at end of file diff --git a/fastpair/rust/.gitignore b/fastpair/rust/.gitignore new file mode 100644 index 00000000..c1bf028b --- /dev/null +++ b/fastpair/rust/.gitignore @@ -0,0 +1,2 @@ +# Build files +target From 4e8c66e88b692bc88f9ad53ff890ce297a554028 Mon Sep 17 00:00:00 2001 From: Lucas Silva Shepard Date: Wed, 28 Jun 2023 18:34:49 -0700 Subject: [PATCH 3/7] [fp-rs] Setting up boilerplate for multiplatform FP Rust. --- fastpair/rust/Cargo.toml | 12 +++++ fastpair/rust/src/bluetooth/common.rs | 31 ++++++++++++ fastpair/rust/src/bluetooth/mod.rs | 29 +++++++++++ .../rust/src/bluetooth/unsupported/adapter.rs | 38 ++++++++++++++ .../rust/src/bluetooth/unsupported/device.rs | 31 ++++++++++++ .../rust/src/bluetooth/unsupported/mod.rs | 20 ++++++++ .../rust/src/bluetooth/windows_ble/adapter.rs | 49 +++++++++++++++++++ .../rust/src/bluetooth/windows_ble/device.rs | 33 +++++++++++++ .../rust/src/bluetooth/windows_ble/mod.rs | 20 ++++++++ fastpair/rust/src/main.rs | 19 +++++-- fastpair/rust/tests/integration_test.rs | 21 ++++++++ 11 files changed, 298 insertions(+), 5 deletions(-) create mode 100644 fastpair/rust/src/bluetooth/common.rs create mode 100644 fastpair/rust/src/bluetooth/mod.rs create mode 100644 fastpair/rust/src/bluetooth/unsupported/adapter.rs create mode 100644 fastpair/rust/src/bluetooth/unsupported/device.rs create mode 100644 fastpair/rust/src/bluetooth/unsupported/mod.rs create mode 100644 fastpair/rust/src/bluetooth/windows_ble/adapter.rs create mode 100644 fastpair/rust/src/bluetooth/windows_ble/device.rs create mode 100644 fastpair/rust/src/bluetooth/windows_ble/mod.rs create mode 100644 fastpair/rust/tests/integration_test.rs diff --git a/fastpair/rust/Cargo.toml b/fastpair/rust/Cargo.toml index be7a0a1c..20a09107 100644 --- a/fastpair/rust/Cargo.toml +++ b/fastpair/rust/Cargo.toml @@ -20,3 +20,15 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = "1.0" +futures = { version = "0.3", features = ["executor"] } +tracing = "0.1.37" +cfg-if = "1.0.0" +async-trait = "0.1" + +[target.'cfg(windows)'.dependencies] +windows = { version = "0.48", features = [ + "Devices_Bluetooth", + "Devices_Bluetooth_Advertisement", + "Foundation", +] } diff --git a/fastpair/rust/src/bluetooth/common.rs b/fastpair/rust/src/bluetooth/common.rs new file mode 100644 index 00000000..0e11f8b3 --- /dev/null +++ b/fastpair/rust/src/bluetooth/common.rs @@ -0,0 +1,31 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use async_trait::async_trait; + +/// Concrete types implementing this trait are Bluetooth Central devices. +/// They provide methods for retrieving nearby connections and device info. +#[async_trait] +pub trait Adapter: Sized { + /// Retrieve the system-default Bluetooth adapter. + async fn default() -> Result; +} + +/// Concrete types implementing this trait represent Bluetooth Peripheral devices. +/// They provide methods for retrieving device info and running device actions, +/// such as pairing. +pub trait Device { + /// Retrieve the name advertised by this device. + fn name(&self) -> Result; +} diff --git a/fastpair/rust/src/bluetooth/mod.rs b/fastpair/rust/src/bluetooth/mod.rs new file mode 100644 index 00000000..ae8a64e6 --- /dev/null +++ b/fastpair/rust/src/bluetooth/mod.rs @@ -0,0 +1,29 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Split into separate crate once demo is finished, providing custom error types +// instead of using anyhow. +// b/290070686 + +pub mod common; + +cfg_if::cfg_if! { + if #[cfg(windows)] { + mod windows_ble; + pub use windows_ble::*; + } else { + mod unsupported; + pub use unsupported::*; + } +} diff --git a/fastpair/rust/src/bluetooth/unsupported/adapter.rs b/fastpair/rust/src/bluetooth/unsupported/adapter.rs new file mode 100644 index 00000000..f9c191b4 --- /dev/null +++ b/fastpair/rust/src/bluetooth/unsupported/adapter.rs @@ -0,0 +1,38 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::pin::Pin; + +use async_trait::async_trait; +use futures::stream::Stream; + +use super::BleDevice; +use crate::bluetooth::common::Adapter; + +/// Concrete type implementing `Adapter`, used for unsupported devices. +/// Every method should panic. +pub struct BleAdapter; + +#[async_trait] +impl Adapter for BleAdapter { + async fn default() -> Result { + panic!("Unsupported target platform."); + } +} + +mod tests { + use super::*; + + // TODO b/288592509 unit tests +} diff --git a/fastpair/rust/src/bluetooth/unsupported/device.rs b/fastpair/rust/src/bluetooth/unsupported/device.rs new file mode 100644 index 00000000..6c8bc520 --- /dev/null +++ b/fastpair/rust/src/bluetooth/unsupported/device.rs @@ -0,0 +1,31 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::bluetooth::common::Device; + +/// Concrete type implementing `Device`, used for unsupported devices. +/// Every method should panic. +pub struct BleDevice; + +impl Device for BleDevice { + fn name(&self) -> Result { + panic!("Unsupported target platform.") + } +} + +mod tests { + use super::*; + + // TODO b/288592509 unit tests +} diff --git a/fastpair/rust/src/bluetooth/unsupported/mod.rs b/fastpair/rust/src/bluetooth/unsupported/mod.rs new file mode 100644 index 00000000..a1af0e2d --- /dev/null +++ b/fastpair/rust/src/bluetooth/unsupported/mod.rs @@ -0,0 +1,20 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// Bluetooth LE module for unsupported devices. Every method panics. +mod adapter; +mod device; + +pub use adapter::*; +pub use device::*; diff --git a/fastpair/rust/src/bluetooth/windows_ble/adapter.rs b/fastpair/rust/src/bluetooth/windows_ble/adapter.rs new file mode 100644 index 00000000..df21e16f --- /dev/null +++ b/fastpair/rust/src/bluetooth/windows_ble/adapter.rs @@ -0,0 +1,49 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use async_trait::async_trait; +use windows::Devices::Bluetooth::BluetoothAdapter; + +use crate::bluetooth::common::Adapter; + +/// Concrete type implementing `Adapter`, used for Windows BLE. +pub struct BleAdapter { + inner: BluetoothAdapter, +} + +#[async_trait] +impl Adapter for BleAdapter { + async fn default() -> Result { + let inner = BluetoothAdapter::GetDefaultAsync()?.await?; + + if !inner.IsLowEnergySupported()? { + return Err(anyhow::anyhow!( + "This device's Bluetooth Adapter doesn't support Bluetooth LE Transport type." + )); + } + if !inner.IsCentralRoleSupported()? { + return Err(anyhow::anyhow!( + "This device's Bluetooth Adapter doesn't support Bluetooth LE central role." + )); + } + + Ok(BleAdapter { inner }) + } +} + +mod tests { + use super::*; + + // TODO b/288592509 unit tests +} diff --git a/fastpair/rust/src/bluetooth/windows_ble/device.rs b/fastpair/rust/src/bluetooth/windows_ble/device.rs new file mode 100644 index 00000000..581d5299 --- /dev/null +++ b/fastpair/rust/src/bluetooth/windows_ble/device.rs @@ -0,0 +1,33 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use windows::Devices::Bluetooth::BluetoothLEDevice; + +use crate::bluetooth::common::Device; + +/// Concrete type implementing `Device`, used for Windows BLE. +pub struct BleDevice { + inner: BluetoothLEDevice, +} + +impl Device for BleDevice { + fn name(&self) -> Result { + Ok(self.inner.Name()?.to_string_lossy()) + } +} + +mod tests { + + // TODO b/288592509 unit tests +} diff --git a/fastpair/rust/src/bluetooth/windows_ble/mod.rs b/fastpair/rust/src/bluetooth/windows_ble/mod.rs new file mode 100644 index 00000000..7d54d4ef --- /dev/null +++ b/fastpair/rust/src/bluetooth/windows_ble/mod.rs @@ -0,0 +1,20 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// Bluetooth LE module for Windows devices. +mod adapter; +mod device; + +pub use adapter::*; +pub use device::*; diff --git a/fastpair/rust/src/main.rs b/fastpair/rust/src/main.rs index 548089bc..95cd96d9 100644 --- a/fastpair/rust/src/main.rs +++ b/fastpair/rust/src/main.rs @@ -1,4 +1,4 @@ -// Copyright 2020 Google LLC +// Copyright 2023 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,9 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -mod message_stream; -mod types; +use futures::executor; -fn main() { - println!("Fastpair Rust!"); +mod bluetooth; + +use bluetooth::common::Adapter; + +fn main() -> Result<(), anyhow::Error> { + let run = async { + let _adapter = bluetooth::BleAdapter::default().await?; + + Ok(()) + }; + + executor::block_on(run) } diff --git a/fastpair/rust/tests/integration_test.rs b/fastpair/rust/tests/integration_test.rs new file mode 100644 index 00000000..08213171 --- /dev/null +++ b/fastpair/rust/tests/integration_test.rs @@ -0,0 +1,21 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + + +mod tests { + + + // TODO b/288592509 write integration tests +} From 07b5750f07573e25a32a90b0d47e5f7585e3d4d8 Mon Sep 17 00:00:00 2001 From: Lucas Silva Shepard Date: Thu, 22 Jun 2023 15:57:06 -0700 Subject: [PATCH 4/7] [fp-rs] Implemented device discovery for FP Windows in Rust. --- fastpair/rust/src/bluetooth/common.rs | 14 ++ .../rust/src/bluetooth/unsupported/adapter.rs | 9 ++ .../rust/src/bluetooth/windows_ble/adapter.rs | 139 +++++++++++++++++- .../rust/src/bluetooth/windows_ble/device.rs | 27 +++- fastpair/rust/src/lib.rs | 16 ++ fastpair/rust/src/main.rs | 13 +- fastpair/rust/tests/integration_test.rs | 4 +- 7 files changed, 212 insertions(+), 10 deletions(-) create mode 100644 fastpair/rust/src/lib.rs diff --git a/fastpair/rust/src/bluetooth/common.rs b/fastpair/rust/src/bluetooth/common.rs index 0e11f8b3..bdcefa40 100644 --- a/fastpair/rust/src/bluetooth/common.rs +++ b/fastpair/rust/src/bluetooth/common.rs @@ -12,14 +12,28 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::pin::Pin; + use async_trait::async_trait; +use futures::stream::Stream; /// Concrete types implementing this trait are Bluetooth Central devices. /// They provide methods for retrieving nearby connections and device info. #[async_trait] pub trait Adapter: Sized { + type Device: Device; + /// Retrieve the system-default Bluetooth adapter. async fn default() -> Result; + + /// Scan for nearby devices, returning a `Stream` of futures that can be + /// iterated over and polled to retrieve `BleDevice`. + // + // NOTE: Using Boxed dyn here is silly because in cross-platform code there + // should only ever be one concrete type implementing Adapter. Change this + // to `impl Stream` once impl trait return types are stabilized in traits. + // b/289224233. + fn scan_devices(&self) -> Result>>, anyhow::Error>; } /// Concrete types implementing this trait represent Bluetooth Peripheral devices. diff --git a/fastpair/rust/src/bluetooth/unsupported/adapter.rs b/fastpair/rust/src/bluetooth/unsupported/adapter.rs index f9c191b4..d07fd118 100644 --- a/fastpair/rust/src/bluetooth/unsupported/adapter.rs +++ b/fastpair/rust/src/bluetooth/unsupported/adapter.rs @@ -26,9 +26,18 @@ pub struct BleAdapter; #[async_trait] impl Adapter for BleAdapter { + type Device = BleDevice; + async fn default() -> Result { panic!("Unsupported target platform."); } + + fn scan_devices(&self) -> Result>>, anyhow::Error> { + panic!("Unsupported target platform."); + #[allow(unreachable_code)] + // Sad satisfying trait bounds github.com/rust-lang/rust/issues/55022. + Ok(Box::pin(futures::stream::iter(vec![BleDevice {}]))) + } } mod tests { diff --git a/fastpair/rust/src/bluetooth/windows_ble/adapter.rs b/fastpair/rust/src/bluetooth/windows_ble/adapter.rs index df21e16f..d2fc3c7c 100644 --- a/fastpair/rust/src/bluetooth/windows_ble/adapter.rs +++ b/fastpair/rust/src/bluetooth/windows_ble/adapter.rs @@ -12,10 +12,49 @@ // See the License for the specific language governing permissions and // limitations under the License. -use async_trait::async_trait; -use windows::Devices::Bluetooth::BluetoothAdapter; +use std::pin::Pin; +use std::sync::Arc; -use crate::bluetooth::common::Adapter; +use async_trait::async_trait; +use futures::{stream::Stream, StreamExt}; +use tracing::{error, warn}; +use windows::{ + Devices::Bluetooth::{ + Advertisement::{ + // Struct that receives Bluetooth Low Energy (LE) advertisements. + // https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.advertisement.bluetoothleadvertisementwatcher?view=winrt-22621 + BluetoothLEAdvertisementReceivedEventArgs, + + // Enum describing the type of advertisement (connectable, directed, etc). + // https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.bluetoothaddresstype?view=winrt-22621 + BluetoothLEAdvertisementType, + + // Provides data for a Received event on a `BluetoothLEAdvertisementWatcher`. + // Instance is created when the Received event occurs in the watcher struct. + // https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.advertisement.bluetoothleadvertisementreceivedeventargs?view=winrt-22621 + BluetoothLEAdvertisementWatcher, + + // Provides data for a Stopped event on a `BluetoothLEAdvertisementWatcher`. + // Instance is created when the Stopped event occurs on a watcher struct. + // https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.advertisement.bluetoothleadvertisementwatcherstoppedeventargs?view=winrt-22621 + BluetoothLEAdvertisementWatcherStoppedEventArgs, + + // Defines constants that specify a Bluetooth LE scanning mode. + // https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.advertisement.bluetoothlescanningmode?view=winrt-22621 + BluetoothLEScanningMode, + }, + // Struct for obtaining global constant information about a computer's + // Bluetooth adapter. + // https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.bluetoothadapter?view=winrt-22621 + BluetoothAdapter, + }, + // Wraps a closure for handling events associated with a struct + // (e.g. Received and Stopped events in BluetoothLEAdvertisementWatcher). + // https://learn.microsoft.com/en-us/uwp/api/windows.foundation.typedeventhandler-2?view=winrt-22621 + Foundation::TypedEventHandler, +}; + +use crate::bluetooth::{common::Adapter, BleDevice}; /// Concrete type implementing `Adapter`, used for Windows BLE. pub struct BleAdapter { @@ -24,6 +63,8 @@ pub struct BleAdapter { #[async_trait] impl Adapter for BleAdapter { + type Device = BleDevice; + async fn default() -> Result { let inner = BluetoothAdapter::GetDefaultAsync()?.await?; @@ -40,6 +81,98 @@ impl Adapter for BleAdapter { Ok(BleAdapter { inner }) } + + fn scan_devices(&self) -> Result>>, anyhow::Error> { + let watcher = BluetoothLEAdvertisementWatcher::new()?; + match watcher.SetScanningMode(BluetoothLEScanningMode::Active) { + Ok(_) => (), + Err(err) => { + warn!("Failed to turn on active scanning. Error: {}", err) + } + }; + + if self.inner.IsExtendedAdvertisingSupported()? { + watcher.SetAllowExtendedAdvertisements(true)?; + } + + // `futures::channel::mpsc` is like `std::sync::mpsc` but `impl Stream`. + let (sender, receiver) = futures::channel::mpsc::channel(16); + let sender = Arc::new(std::sync::Mutex::new(sender)); + + // `received_handler` closure holds non-owning channel reference, to + // ensure `stopped_handler` can close the channel when + // `received_handler` is done. + let weak_sender = Arc::downgrade(&sender); + let received_handler = TypedEventHandler::new( + // Move `weak_sender` into closure. + move |watcher: &Option, + event_args: &Option| { + if watcher.is_some() { + if let Some(event_args) = event_args { + if let Some(sender) = weak_sender.upgrade() { + match sender.lock().unwrap().try_send(event_args.clone()) { + Ok(_) => (), + Err(err) => { + error!("Error while handling Received event: {:?}", err) + } + } + } + } + } + + Ok(()) + }, + ); + + // `stopped_handler` closure owns channel reference, can close channel. + let mut sender = Some(sender); + let stopped_handler = TypedEventHandler::new( + // Move `sender` into closure. + move |_watcher, + _event_args: &Option< + BluetoothLEAdvertisementWatcherStoppedEventArgs, + >| { + // Drop `sender`, closing the channel. + let _sender = sender.take(); + println!("Watcher stopped receiving BLE advertisements."); + Ok(()) + }, + ); + + watcher.Received(&received_handler)?; + 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`. + Ok(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 addr = event_args.BluetoothAddress().ok()?; + let kind = event_args.BluetoothAddressType().ok()?; + + match BleDevice::from_addr(addr, kind).await { + Ok(device) => Some(device), + Err(err) => { + warn!("Error creating device: {:?}", err); + None + } + } + } + } + } + }))) + } } mod tests { diff --git a/fastpair/rust/src/bluetooth/windows_ble/device.rs b/fastpair/rust/src/bluetooth/windows_ble/device.rs index 581d5299..08c3d123 100644 --- a/fastpair/rust/src/bluetooth/windows_ble/device.rs +++ b/fastpair/rust/src/bluetooth/windows_ble/device.rs @@ -12,7 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -use windows::Devices::Bluetooth::BluetoothLEDevice; +use async_trait::async_trait; +use windows::Devices::Bluetooth::{ + // Enum describing the type of address (public, random, unspecified). + // https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.bluetoothaddresstype?view=winrt-22621 + BluetoothAddressType, + + // Struct for interacting with and pairing to a discovered BLE device. + // https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.bluetoothledevice?view=winrt-22621 + BluetoothLEDevice, +}; use crate::bluetooth::common::Device; @@ -21,6 +30,21 @@ pub struct BleDevice { inner: BluetoothLEDevice, } +impl BleDevice { + /// Create a `BleDevice` instance from the raw bluetooth address information. + pub(super) async fn from_addr( + addr: u64, + kind: BluetoothAddressType, + ) -> Result { + let inner = + BluetoothLEDevice::FromBluetoothAddressWithBluetoothAddressTypeAsync(addr, kind)? + .await?; + + Ok(BleDevice { inner }) + } +} + +#[async_trait] impl Device for BleDevice { fn name(&self) -> Result { Ok(self.inner.Name()?.to_string_lossy()) @@ -28,6 +52,7 @@ impl Device for BleDevice { } mod tests { + use super::*; // TODO b/288592509 unit tests } diff --git a/fastpair/rust/src/lib.rs b/fastpair/rust/src/lib.rs new file mode 100644 index 00000000..c9a294f5 --- /dev/null +++ b/fastpair/rust/src/lib.rs @@ -0,0 +1,16 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// Library file, exports modules for use in integration tests and external crates. +pub mod bluetooth; diff --git a/fastpair/rust/src/main.rs b/fastpair/rust/src/main.rs index 95cd96d9..0846dc34 100644 --- a/fastpair/rust/src/main.rs +++ b/fastpair/rust/src/main.rs @@ -12,17 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -use futures::executor; +use futures::{executor, StreamExt}; mod bluetooth; -use bluetooth::common::Adapter; +use bluetooth::common::{Adapter, Device}; fn main() -> Result<(), anyhow::Error> { let run = async { - let _adapter = bluetooth::BleAdapter::default().await?; + let adapter = bluetooth::BleAdapter::default().await?; + let mut scanner = adapter.scan_devices()?; - Ok(()) + while let Some(device) = scanner.next().await { + println!("found {}", device.name()?) + } + + unreachable!("Done scanning"); }; executor::block_on(run) diff --git a/fastpair/rust/tests/integration_test.rs b/fastpair/rust/tests/integration_test.rs index 08213171..18021531 100644 --- a/fastpair/rust/tests/integration_test.rs +++ b/fastpair/rust/tests/integration_test.rs @@ -12,10 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. - +use fastpair::*; mod tests { - + use super::*; // TODO b/288592509 write integration tests } From 1375066f746197f76a929ef86e5dbcd0badfd69d Mon Sep 17 00:00:00 2001 From: Lucas Silva Shepard Date: Mon, 26 Jun 2023 18:04:35 -0700 Subject: [PATCH 5/7] [fp-rs] Adding line width formatting to Fast Pair Rust. --- fastpair/rust/rustfmt.toml | 1 + 1 file changed, 1 insertion(+) create mode 100644 fastpair/rust/rustfmt.toml diff --git a/fastpair/rust/rustfmt.toml b/fastpair/rust/rustfmt.toml new file mode 100644 index 00000000..5c8d9318 --- /dev/null +++ b/fastpair/rust/rustfmt.toml @@ -0,0 +1 @@ +max_width = 80 \ No newline at end of file From aeed049d5854a415aaf595cdadad5a2d8977cbff Mon Sep 17 00:00:00 2001 From: Lucas Silva Shepard Date: Mon, 10 Jul 2023 13:35:55 -0700 Subject: [PATCH 6/7] [fp-rs] Updating device scanning interface, split into start/stop/next. --- fastpair/rust/src/bluetooth/common.rs | 19 +++--- .../rust/src/bluetooth/unsupported/adapter.rs | 16 +++-- .../rust/src/bluetooth/windows_ble/adapter.rs | 67 +++++++++++++++---- fastpair/rust/src/main.rs | 8 +-- 4 files changed, 74 insertions(+), 36 deletions(-) diff --git a/fastpair/rust/src/bluetooth/common.rs b/fastpair/rust/src/bluetooth/common.rs index bdcefa40..33ba2c4c 100644 --- a/fastpair/rust/src/bluetooth/common.rs +++ b/fastpair/rust/src/bluetooth/common.rs @@ -12,10 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::pin::Pin; - use async_trait::async_trait; -use futures::stream::Stream; /// Concrete types implementing this trait are Bluetooth Central devices. /// They provide methods for retrieving nearby connections and device info. @@ -26,14 +23,14 @@ pub trait Adapter: Sized { /// Retrieve the system-default Bluetooth adapter. async fn default() -> Result; - /// Scan for nearby devices, returning a `Stream` of futures that can be - /// iterated over and polled to retrieve `BleDevice`. - // - // NOTE: Using Boxed dyn here is silly because in cross-platform code there - // should only ever be one concrete type implementing Adapter. Change this - // to `impl Stream` once impl trait return types are stabilized in traits. - // b/289224233. - fn scan_devices(&self) -> Result>>, anyhow::Error>; + /// Begin scanning for nearby devices. + fn start_scan_devices(&mut self) -> Result<(), anyhow::Error>; + + /// Stop scanning for nearby devices. + fn stop_scan_devices(&mut self) -> Result<(), anyhow::Error>; + + /// Poll next discovered device. + async fn next_device(&mut self) -> Result; } /// Concrete types implementing this trait represent Bluetooth Peripheral devices. diff --git a/fastpair/rust/src/bluetooth/unsupported/adapter.rs b/fastpair/rust/src/bluetooth/unsupported/adapter.rs index d07fd118..f7395ea5 100644 --- a/fastpair/rust/src/bluetooth/unsupported/adapter.rs +++ b/fastpair/rust/src/bluetooth/unsupported/adapter.rs @@ -12,10 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::pin::Pin; - use async_trait::async_trait; -use futures::stream::Stream; use super::BleDevice; use crate::bluetooth::common::Adapter; @@ -32,11 +29,16 @@ impl Adapter for BleAdapter { panic!("Unsupported target platform."); } - fn scan_devices(&self) -> Result>>, anyhow::Error> { + fn start_scan_devices(&mut self) -> Result<(), anyhow::Error> { + panic!("Unsupported target platform."); + } + + fn stop_scan_devices(&mut self) -> Result<(), anyhow::Error> { + panic!("Unsupported target platform."); + } + + async fn next_device(&mut self) -> Result { panic!("Unsupported target platform."); - #[allow(unreachable_code)] - // Sad satisfying trait bounds github.com/rust-lang/rust/issues/55022. - Ok(Box::pin(futures::stream::iter(vec![BleDevice {}]))) } } diff --git a/fastpair/rust/src/bluetooth/windows_ble/adapter.rs b/fastpair/rust/src/bluetooth/windows_ble/adapter.rs index d2fc3c7c..82929e52 100644 --- a/fastpair/rust/src/bluetooth/windows_ble/adapter.rs +++ b/fastpair/rust/src/bluetooth/windows_ble/adapter.rs @@ -59,6 +59,11 @@ use crate::bluetooth::{common::Adapter, BleDevice}; /// 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>>>, } #[async_trait] @@ -79,10 +84,13 @@ impl Adapter for BleAdapter { )); } - Ok(BleAdapter { inner }) + Ok(BleAdapter { + inner, + device_stream: None, + }) } - fn scan_devices(&self) -> Result>>, anyhow::Error> { + fn start_scan_devices(&mut self) -> Result<(), anyhow::Error> { let watcher = BluetoothLEAdvertisementWatcher::new()?; match watcher.SetScanningMode(BluetoothLEScanningMode::Active) { Ok(_) => (), @@ -106,11 +114,17 @@ impl Adapter for BleAdapter { let received_handler = TypedEventHandler::new( // Move `weak_sender` into closure. move |watcher: &Option, - event_args: &Option| { + event_args: &Option< + BluetoothLEAdvertisementReceivedEventArgs, + >| { if watcher.is_some() { if let Some(event_args) = event_args { if let Some(sender) = weak_sender.upgrade() { - match sender.lock().unwrap().try_send(event_args.clone()) { + match sender + .lock() + .unwrap() + .try_send(event_args.clone()) + { Ok(_) => (), Err(err) => { error!("Error while handling Received event: {:?}", err) @@ -148,15 +162,18 @@ impl Adapter for BleAdapter { // 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`. - Ok(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; + 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, + // Move `event_args` into async block. + async move { + match event_args.AdvertisementType().ok()? { + BluetoothLEAdvertisementType::NonConnectableUndirected => { + None + } _ => { let addr = event_args.BluetoothAddress().ok()?; let kind = event_args.BluetoothAddressType().ok()?; @@ -170,8 +187,30 @@ impl Adapter for BleAdapter { } } } - } - }))) + } + }))); + + Ok(()) + } + + fn stop_scan_devices(&mut self) -> Result<(), anyhow::Error> { + if let Some(_) = &self.device_stream { + self.device_stream.take(); + Ok(()) + } else { + Err(anyhow::anyhow!("Device scanning hasn't started.")) + } + } + + async fn next_device(&mut self) -> Result { + if let Some(stream) = &mut self.device_stream { + stream + .next() + .await + .ok_or(anyhow::anyhow!("Device returned from stream is None.")) + } else { + Err(anyhow::anyhow!("Device scanning hasn't started.")) + } } } diff --git a/fastpair/rust/src/main.rs b/fastpair/rust/src/main.rs index 0846dc34..d50996d4 100644 --- a/fastpair/rust/src/main.rs +++ b/fastpair/rust/src/main.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use futures::{executor, StreamExt}; +use futures::executor; mod bluetooth; @@ -20,10 +20,10 @@ use bluetooth::common::{Adapter, Device}; fn main() -> Result<(), anyhow::Error> { let run = async { - let adapter = bluetooth::BleAdapter::default().await?; - let mut scanner = adapter.scan_devices()?; + let mut adapter = bluetooth::BleAdapter::default().await?; + adapter.start_scan_devices()?; - while let Some(device) = scanner.next().await { + while let Ok(device) = adapter.next_device().await { println!("found {}", device.name()?) } From a855dd19e1dc30dbf9d42deccd55e675be59b082 Mon Sep 17 00:00:00 2001 From: Lucas Silva Shepard Date: Thu, 13 Jul 2023 15:34:11 -0700 Subject: [PATCH 7/7] [fp-rs] Cross-platform Bluetooth now interfaces with impl trait rather than concrete per-platform type. --- fastpair/rust/src/bluetooth/mod.rs | 10 ++++++++-- fastpair/rust/src/bluetooth/windows_ble/adapter.rs | 5 +++-- fastpair/rust/src/main.rs | 4 ++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/fastpair/rust/src/bluetooth/mod.rs b/fastpair/rust/src/bluetooth/mod.rs index ae8a64e6..2faa2576 100644 --- a/fastpair/rust/src/bluetooth/mod.rs +++ b/fastpair/rust/src/bluetooth/mod.rs @@ -18,12 +18,18 @@ pub mod common; +pub use common::{Adapter, Device}; + cfg_if::cfg_if! { if #[cfg(windows)] { mod windows_ble; - pub use windows_ble::*; + use windows_ble::BleAdapter; } else { mod unsupported; - pub use unsupported::*; + use unsupported::BleAdapter; } } + +pub async fn default_adapter() -> Result { + BleAdapter::default().await +} diff --git a/fastpair/rust/src/bluetooth/windows_ble/adapter.rs b/fastpair/rust/src/bluetooth/windows_ble/adapter.rs index 82929e52..6d7af121 100644 --- a/fastpair/rust/src/bluetooth/windows_ble/adapter.rs +++ b/fastpair/rust/src/bluetooth/windows_ble/adapter.rs @@ -54,7 +54,8 @@ use windows::{ Foundation::TypedEventHandler, }; -use crate::bluetooth::{common::Adapter, BleDevice}; +use super::BleDevice; +use crate::bluetooth::common::Adapter; /// Concrete type implementing `Adapter`, used for Windows BLE. pub struct BleAdapter { @@ -202,7 +203,7 @@ impl Adapter for BleAdapter { } } - async fn next_device(&mut self) -> Result { + async fn next_device(&mut self) -> Result { if let Some(stream) = &mut self.device_stream { stream .next() diff --git a/fastpair/rust/src/main.rs b/fastpair/rust/src/main.rs index d50996d4..5905e135 100644 --- a/fastpair/rust/src/main.rs +++ b/fastpair/rust/src/main.rs @@ -16,11 +16,11 @@ use futures::executor; mod bluetooth; -use bluetooth::common::{Adapter, Device}; +use bluetooth::{Adapter, Device}; fn main() -> Result<(), anyhow::Error> { let run = async { - let mut adapter = bluetooth::BleAdapter::default().await?; + let mut adapter = bluetooth::default_adapter().await?; adapter.start_scan_devices()?; while let Ok(device) = adapter.next_device().await {