From 251740d010a43966180404461d627aa4be635df0 Mon Sep 17 00:00:00 2001 From: Lucas Silva Shepard Date: Wed, 12 Jul 2023 12:37:01 -0700 Subject: [PATCH 01/11] [fp-rs] Renaming folder windows_ble to windows, moving common to folder --- .../{common.rs => common/adapter.rs} | 10 ++------- fastpair/rust/src/bluetooth/common/device.rs | 21 +++++++++++++++++++ fastpair/rust/src/bluetooth/common/mod.rs | 20 ++++++++++++++++++ fastpair/rust/src/bluetooth/mod.rs | 4 ++-- .../{windows_ble => windows}/adapter.rs | 0 .../{windows_ble => windows}/device.rs | 0 .../bluetooth/{windows_ble => windows}/mod.rs | 0 7 files changed, 45 insertions(+), 10 deletions(-) rename fastpair/rust/src/bluetooth/{common.rs => common/adapter.rs} (80%) create mode 100644 fastpair/rust/src/bluetooth/common/device.rs create mode 100644 fastpair/rust/src/bluetooth/common/mod.rs rename fastpair/rust/src/bluetooth/{windows_ble => windows}/adapter.rs (100%) rename fastpair/rust/src/bluetooth/{windows_ble => windows}/device.rs (100%) rename fastpair/rust/src/bluetooth/{windows_ble => windows}/mod.rs (100%) diff --git a/fastpair/rust/src/bluetooth/common.rs b/fastpair/rust/src/bluetooth/common/adapter.rs similarity index 80% rename from fastpair/rust/src/bluetooth/common.rs rename to fastpair/rust/src/bluetooth/common/adapter.rs index 33ba2c4c..5e8be0ae 100644 --- a/fastpair/rust/src/bluetooth/common.rs +++ b/fastpair/rust/src/bluetooth/common/adapter.rs @@ -14,6 +14,8 @@ use async_trait::async_trait; +use super::Device; + /// Concrete types implementing this trait are Bluetooth Central devices. /// They provide methods for retrieving nearby connections and device info. #[async_trait] @@ -32,11 +34,3 @@ pub trait Adapter: Sized { /// Poll next discovered device. async fn next_device(&mut self) -> 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/common/device.rs b/fastpair/rust/src/bluetooth/common/device.rs new file mode 100644 index 00000000..1849fcef --- /dev/null +++ b/fastpair/rust/src/bluetooth/common/device.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. + +/// 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/common/mod.rs b/fastpair/rust/src/bluetooth/common/mod.rs new file mode 100644 index 00000000..ea2fa401 --- /dev/null +++ b/fastpair/rust/src/bluetooth/common/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. + +/// Module for shared functionality between all Bluetooth platforms. +mod adapter; +mod device; + +pub use adapter::*; +pub use device::*; diff --git a/fastpair/rust/src/bluetooth/mod.rs b/fastpair/rust/src/bluetooth/mod.rs index 2faa2576..b49b95df 100644 --- a/fastpair/rust/src/bluetooth/mod.rs +++ b/fastpair/rust/src/bluetooth/mod.rs @@ -22,8 +22,8 @@ pub use common::{Adapter, Device}; cfg_if::cfg_if! { if #[cfg(windows)] { - mod windows_ble; - use windows_ble::BleAdapter; + mod windows; + use self::windows::BleAdapter; } else { mod unsupported; use unsupported::BleAdapter; diff --git a/fastpair/rust/src/bluetooth/windows_ble/adapter.rs b/fastpair/rust/src/bluetooth/windows/adapter.rs similarity index 100% rename from fastpair/rust/src/bluetooth/windows_ble/adapter.rs rename to fastpair/rust/src/bluetooth/windows/adapter.rs diff --git a/fastpair/rust/src/bluetooth/windows_ble/device.rs b/fastpair/rust/src/bluetooth/windows/device.rs similarity index 100% rename from fastpair/rust/src/bluetooth/windows_ble/device.rs rename to fastpair/rust/src/bluetooth/windows/device.rs diff --git a/fastpair/rust/src/bluetooth/windows_ble/mod.rs b/fastpair/rust/src/bluetooth/windows/mod.rs similarity index 100% rename from fastpair/rust/src/bluetooth/windows_ble/mod.rs rename to fastpair/rust/src/bluetooth/windows/mod.rs From 494eb660271f050b31f17e67940b341e7d3e5290 Mon Sep 17 00:00:00 2001 From: Lucas Silva Shepard Date: Tue, 18 Jul 2023 15:18:53 -0700 Subject: [PATCH 02/11] [fp-rs] Implemented custom error types for Bluetooth library. --- fastpair/rust/Cargo.toml | 2 +- fastpair/rust/src/bluetooth/common/adapter.rs | 10 ++--- fastpair/rust/src/bluetooth/common/device.rs | 4 +- fastpair/rust/src/bluetooth/common/error.rs | 42 +++++++++++++++++++ fastpair/rust/src/bluetooth/common/mod.rs | 2 + fastpair/rust/src/bluetooth/mod.rs | 4 +- .../rust/src/bluetooth/unsupported/adapter.rs | 10 ++--- .../rust/src/bluetooth/unsupported/device.rs | 4 +- .../rust/src/bluetooth/windows/adapter.rs | 34 ++++++++------- fastpair/rust/src/bluetooth/windows/device.rs | 6 +-- fastpair/rust/src/bluetooth/windows/error.rs | 21 ++++++++++ fastpair/rust/src/bluetooth/windows/mod.rs | 1 + fastpair/rust/src/main.rs | 4 +- 13 files changed, 110 insertions(+), 34 deletions(-) create mode 100644 fastpair/rust/src/bluetooth/common/error.rs create mode 100644 fastpair/rust/src/bluetooth/windows/error.rs diff --git a/fastpair/rust/Cargo.toml b/fastpair/rust/Cargo.toml index 20a09107..02a7b908 100644 --- a/fastpair/rust/Cargo.toml +++ b/fastpair/rust/Cargo.toml @@ -20,11 +20,11 @@ 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" +thiserror = "1.0.43" [target.'cfg(windows)'.dependencies] windows = { version = "0.48", features = [ diff --git a/fastpair/rust/src/bluetooth/common/adapter.rs b/fastpair/rust/src/bluetooth/common/adapter.rs index 5e8be0ae..40a504ee 100644 --- a/fastpair/rust/src/bluetooth/common/adapter.rs +++ b/fastpair/rust/src/bluetooth/common/adapter.rs @@ -14,7 +14,7 @@ use async_trait::async_trait; -use super::Device; +use super::{BluetoothError, Device}; /// Concrete types implementing this trait are Bluetooth Central devices. /// They provide methods for retrieving nearby connections and device info. @@ -23,14 +23,14 @@ pub trait Adapter: Sized { type Device: Device; /// Retrieve the system-default Bluetooth adapter. - async fn default() -> Result; + async fn default() -> Result; /// Begin scanning for nearby devices. - fn start_scan_devices(&mut self) -> Result<(), anyhow::Error>; + fn start_scan_devices(&mut self) -> Result<(), BluetoothError>; /// Stop scanning for nearby devices. - fn stop_scan_devices(&mut self) -> Result<(), anyhow::Error>; + fn stop_scan_devices(&mut self) -> Result<(), BluetoothError>; /// Poll next discovered device. - async fn next_device(&mut self) -> Result; + async fn next_device(&mut self) -> Result; } diff --git a/fastpair/rust/src/bluetooth/common/device.rs b/fastpair/rust/src/bluetooth/common/device.rs index 1849fcef..33ef11c2 100644 --- a/fastpair/rust/src/bluetooth/common/device.rs +++ b/fastpair/rust/src/bluetooth/common/device.rs @@ -15,7 +15,9 @@ /// Concrete types implementing this trait represent Bluetooth Peripheral devices. /// They provide methods for retrieving device info and running device actions, /// such as pairing. +use super::BluetoothError; + pub trait Device { /// Retrieve the name advertised by this device. - fn name(&self) -> Result; + fn name(&self) -> Result; } diff --git a/fastpair/rust/src/bluetooth/common/error.rs b/fastpair/rust/src/bluetooth/common/error.rs new file mode 100644 index 00000000..3e7ac640 --- /dev/null +++ b/fastpair/rust/src/bluetooth/common/error.rs @@ -0,0 +1,42 @@ +// 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 thiserror::Error; + +/// Library error type. +#[non_exhaustive] +#[derive(Error, Debug)] +pub enum BluetoothError { + /// Indicates that the operation was rejected because the system is not in + /// a state required for the operation's execution. + /// E.g. The user calls `stop_scan()` or polls the advertisement stream + /// before calling `start_scan()`. + #[error("failed precondition: {0}")] + FailedPrecondition(String), + /// Reported when the user calls an operation that is supported by their + /// Operating System, but is not supported by their device. + /// E.g. a Windows machine with an old BT Classic adapter that + /// doesn't support BLE). + #[error("bluetooth operation not supported by system: {0}")] + NotSupported(String), + /// Wrapper around OS-level errors, e.g. `windows::core::Error` for Windows. + /// These typically mean something is very wrong with the system (e.g. OOM). + #[error("bluetooth system-level error: {0}")] + System(String), + /// Reported when a bug occurs inside the library. Whenever a seemingly + /// impossible error condition arises where you could call `expect()`, + /// return this error instead. + #[error("internal error: {0}")] + Internal(String), +} diff --git a/fastpair/rust/src/bluetooth/common/mod.rs b/fastpair/rust/src/bluetooth/common/mod.rs index ea2fa401..d0fce7c4 100644 --- a/fastpair/rust/src/bluetooth/common/mod.rs +++ b/fastpair/rust/src/bluetooth/common/mod.rs @@ -15,6 +15,8 @@ /// Module for shared functionality between all Bluetooth platforms. mod adapter; mod device; +mod error; pub use adapter::*; pub use device::*; +pub use error::*; diff --git a/fastpair/rust/src/bluetooth/mod.rs b/fastpair/rust/src/bluetooth/mod.rs index b49b95df..765086f2 100644 --- a/fastpair/rust/src/bluetooth/mod.rs +++ b/fastpair/rust/src/bluetooth/mod.rs @@ -18,7 +18,7 @@ pub mod common; -pub use common::{Adapter, Device}; +pub use common::{Adapter, BluetoothError, Device}; cfg_if::cfg_if! { if #[cfg(windows)] { @@ -30,6 +30,6 @@ cfg_if::cfg_if! { } } -pub async fn default_adapter() -> Result { +pub async fn default_adapter() -> Result { BleAdapter::default().await } diff --git a/fastpair/rust/src/bluetooth/unsupported/adapter.rs b/fastpair/rust/src/bluetooth/unsupported/adapter.rs index f7395ea5..8baa3b81 100644 --- a/fastpair/rust/src/bluetooth/unsupported/adapter.rs +++ b/fastpair/rust/src/bluetooth/unsupported/adapter.rs @@ -15,7 +15,7 @@ use async_trait::async_trait; use super::BleDevice; -use crate::bluetooth::common::Adapter; +use crate::bluetooth::common::{Adapter, BluetoothError}; /// Concrete type implementing `Adapter`, used for unsupported devices. /// Every method should panic. @@ -25,19 +25,19 @@ pub struct BleAdapter; impl Adapter for BleAdapter { type Device = BleDevice; - async fn default() -> Result { + async fn default() -> Result { panic!("Unsupported target platform."); } - fn start_scan_devices(&mut self) -> Result<(), anyhow::Error> { + fn start_scan_devices(&mut self) -> Result<(), BluetoothError> { panic!("Unsupported target platform."); } - fn stop_scan_devices(&mut self) -> Result<(), anyhow::Error> { + fn stop_scan_devices(&mut self) -> Result<(), BluetoothError> { panic!("Unsupported target platform."); } - async fn next_device(&mut self) -> Result { + async fn next_device(&mut self) -> Result { panic!("Unsupported target platform."); } } diff --git a/fastpair/rust/src/bluetooth/unsupported/device.rs b/fastpair/rust/src/bluetooth/unsupported/device.rs index 6c8bc520..d18d1e34 100644 --- a/fastpair/rust/src/bluetooth/unsupported/device.rs +++ b/fastpair/rust/src/bluetooth/unsupported/device.rs @@ -12,14 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::bluetooth::common::Device; +use crate::bluetooth::common::{BluetoothError, Device}; /// Concrete type implementing `Device`, used for unsupported devices. /// Every method should panic. pub struct BleDevice; impl Device for BleDevice { - fn name(&self) -> Result { + fn name(&self) -> Result { panic!("Unsupported target platform.") } } diff --git a/fastpair/rust/src/bluetooth/windows/adapter.rs b/fastpair/rust/src/bluetooth/windows/adapter.rs index 6d7af121..0cabf752 100644 --- a/fastpair/rust/src/bluetooth/windows/adapter.rs +++ b/fastpair/rust/src/bluetooth/windows/adapter.rs @@ -55,7 +55,7 @@ use windows::{ }; use super::BleDevice; -use crate::bluetooth::common::Adapter; +use crate::bluetooth::common::{Adapter, BluetoothError}; /// Concrete type implementing `Adapter`, used for Windows BLE. pub struct BleAdapter { @@ -71,18 +71,18 @@ pub struct BleAdapter { impl Adapter for BleAdapter { type Device = BleDevice; - async fn default() -> Result { + 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." - )); + return Err(BluetoothError::NotSupported(String::from( + "LE transport type", + ))); } if !inner.IsCentralRoleSupported()? { - return Err(anyhow::anyhow!( - "This device's Bluetooth Adapter doesn't support Bluetooth LE central role." - )); + return Err(BluetoothError::NotSupported(String::from( + "central role", + ))); } Ok(BleAdapter { @@ -91,7 +91,7 @@ impl Adapter for BleAdapter { }) } - fn start_scan_devices(&mut self) -> Result<(), anyhow::Error> { + fn start_scan_devices(&mut self) -> Result<(), BluetoothError> { let watcher = BluetoothLEAdvertisementWatcher::new()?; match watcher.SetScanningMode(BluetoothLEScanningMode::Active) { Ok(_) => (), @@ -194,23 +194,29 @@ impl Adapter for BleAdapter { Ok(()) } - fn stop_scan_devices(&mut self) -> Result<(), anyhow::Error> { + fn stop_scan_devices(&mut self) -> Result<(), BluetoothError> { if let Some(_) = &self.device_stream { self.device_stream.take(); Ok(()) } else { - Err(anyhow::anyhow!("Device scanning hasn't started.")) + Err(BluetoothError::FailedPrecondition(String::from( + "device scanning hasn't started, please call `start_scan()`", + ))) } } - async fn next_device(&mut self) -> Result { + 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.")) + .ok_or(BluetoothError::Internal(String::from( + "device returned from Stream is None", + ))) } else { - Err(anyhow::anyhow!("Device scanning hasn't started.")) + Err(BluetoothError::FailedPrecondition(String::from( + "device scanning hasn't started, please call `start_scan()`", + ))) } } } diff --git a/fastpair/rust/src/bluetooth/windows/device.rs b/fastpair/rust/src/bluetooth/windows/device.rs index 08c3d123..60bd872b 100644 --- a/fastpair/rust/src/bluetooth/windows/device.rs +++ b/fastpair/rust/src/bluetooth/windows/device.rs @@ -23,7 +23,7 @@ use windows::Devices::Bluetooth::{ BluetoothLEDevice, }; -use crate::bluetooth::common::Device; +use crate::bluetooth::common::{BluetoothError, Device}; /// Concrete type implementing `Device`, used for Windows BLE. pub struct BleDevice { @@ -35,7 +35,7 @@ impl BleDevice { pub(super) async fn from_addr( addr: u64, kind: BluetoothAddressType, - ) -> Result { + ) -> Result { let inner = BluetoothLEDevice::FromBluetoothAddressWithBluetoothAddressTypeAsync(addr, kind)? .await?; @@ -46,7 +46,7 @@ impl BleDevice { #[async_trait] impl Device for BleDevice { - fn name(&self) -> Result { + fn name(&self) -> Result { Ok(self.inner.Name()?.to_string_lossy()) } } diff --git a/fastpair/rust/src/bluetooth/windows/error.rs b/fastpair/rust/src/bluetooth/windows/error.rs new file mode 100644 index 00000000..04c6bce3 --- /dev/null +++ b/fastpair/rust/src/bluetooth/windows/error.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. + +use crate::bluetooth::common::BluetoothError; + +impl From for BluetoothError { + fn from(err: windows::core::Error) -> Self { + BluetoothError::System(err.to_string()) + } +} diff --git a/fastpair/rust/src/bluetooth/windows/mod.rs b/fastpair/rust/src/bluetooth/windows/mod.rs index 7d54d4ef..4835654d 100644 --- a/fastpair/rust/src/bluetooth/windows/mod.rs +++ b/fastpair/rust/src/bluetooth/windows/mod.rs @@ -15,6 +15,7 @@ /// Bluetooth LE module for Windows devices. mod adapter; mod device; +mod error; pub use adapter::*; pub use device::*; diff --git a/fastpair/rust/src/main.rs b/fastpair/rust/src/main.rs index 5905e135..c0326ba6 100644 --- a/fastpair/rust/src/main.rs +++ b/fastpair/rust/src/main.rs @@ -12,13 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::error::Error; + use futures::executor; mod bluetooth; use bluetooth::{Adapter, Device}; -fn main() -> Result<(), anyhow::Error> { +fn main() -> Result<(), Box> { let run = async { let mut adapter = bluetooth::default_adapter().await?; adapter.start_scan_devices()?; From 9da0bf2987b0307641221bd35ce099a967864b92 Mon Sep 17 00:00:00 2001 From: Lucas Silva Shepard Date: Wed, 12 Jul 2023 15:40:11 -0700 Subject: [PATCH 03/11] [fp-rs] Implemented address API --- fastpair/rust/src/bluetooth/common/address.rs | 100 ++++++++++++++++++ fastpair/rust/src/bluetooth/common/mod.rs | 2 + .../rust/src/bluetooth/windows/adapter.rs | 9 +- .../rust/src/bluetooth/windows/address.rs | 50 +++++++++ fastpair/rust/src/bluetooth/windows/device.rs | 18 ++-- fastpair/rust/src/bluetooth/windows/mod.rs | 2 + 6 files changed, 169 insertions(+), 12 deletions(-) create mode 100644 fastpair/rust/src/bluetooth/common/address.rs create mode 100644 fastpair/rust/src/bluetooth/windows/address.rs diff --git a/fastpair/rust/src/bluetooth/common/address.rs b/fastpair/rust/src/bluetooth/common/address.rs new file mode 100644 index 00000000..87c9139d --- /dev/null +++ b/fastpair/rust/src/bluetooth/common/address.rs @@ -0,0 +1,100 @@ +// 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. + +/// BLE Addresses can either be the peripheral's public MAC address, or various +/// types of random addresses. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum BleAddressKind { + Public, + Random, +} + +/// Struct representing a 48-bit BLE Address and its type. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct BleAddress { + val: [u8; 6], + kind: BleAddressKind, +} + +/// Struct representing a 48-bit BT Classic address. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct ClassicAddress([u8; 6]); + +/// Enum for interfacing with Bluetooth Addresses. +pub enum Address { + Ble(BleAddress), + Classic(ClassicAddress), +} + +impl BleAddress { + /// `BleAddress` constructor. + pub fn new(addr: u64, kind: BleAddressKind) -> Self { + let addr = u64_to_6lsb(addr); + + BleAddress { val: addr, kind } + } + + /// Retrieve the type of BLE Address (public or random). + pub fn get_kind(&self) -> BleAddressKind { + self.kind + } +} + +/// Function for converting the six LSB of a u64 into a 6-byte array. +#[inline] +fn u64_to_6lsb(num: u64) -> [u8; 6] { + num.to_le_bytes()[..6] + .try_into() + .expect("Sanity check, slice length matches array length") +} + +impl From for ClassicAddress { + fn from(addr: u64) -> Self { + let addr = u64_to_6lsb(addr); + + ClassicAddress(addr) + } +} + +impl TryFrom for ClassicAddress { + // TODO proper error handling b/291931475 + type Error = anyhow::Error; + + 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." + )), + } + } +} + +impl From for u64 { + fn from(addr: BleAddress) -> Self { + let mut bytes = [0u8; 8]; + bytes[..6].copy_from_slice(&addr.val); + + u64::from_le_bytes(bytes) + } +} + +impl From for u64 { + fn from(addr: ClassicAddress) -> Self { + let mut bytes = [0u8; 8]; + bytes.copy_from_slice(&addr.0); + + u64::from_le_bytes(bytes) + } +} diff --git a/fastpair/rust/src/bluetooth/common/mod.rs b/fastpair/rust/src/bluetooth/common/mod.rs index d0fce7c4..f604ede6 100644 --- a/fastpair/rust/src/bluetooth/common/mod.rs +++ b/fastpair/rust/src/bluetooth/common/mod.rs @@ -14,9 +14,11 @@ /// Module for shared functionality between all Bluetooth platforms. mod adapter; +mod address; mod device; mod error; pub use adapter::*; +pub use address::*; pub use device::*; pub use error::*; diff --git a/fastpair/rust/src/bluetooth/windows/adapter.rs b/fastpair/rust/src/bluetooth/windows/adapter.rs index 0cabf752..57fb533d 100644 --- a/fastpair/rust/src/bluetooth/windows/adapter.rs +++ b/fastpair/rust/src/bluetooth/windows/adapter.rs @@ -55,7 +55,7 @@ use windows::{ }; use super::BleDevice; -use crate::bluetooth::common::{Adapter, BluetoothError}; +use crate::bluetooth::common::{Adapter, BleAddress, BleAddressKind, BluetoothError}; /// Concrete type implementing `Adapter`, used for Windows BLE. pub struct BleAdapter { @@ -176,10 +176,13 @@ impl Adapter for BleAdapter { None } _ => { - let addr = event_args.BluetoothAddress().ok()?; let kind = event_args.BluetoothAddressType().ok()?; + let addr = event_args.BluetoothAddress().ok()?; - match BleDevice::from_addr(addr, kind).await { + 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); diff --git a/fastpair/rust/src/bluetooth/windows/address.rs b/fastpair/rust/src/bluetooth/windows/address.rs new file mode 100644 index 00000000..6f587237 --- /dev/null +++ b/fastpair/rust/src/bluetooth/windows/address.rs @@ -0,0 +1,50 @@ +// 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. + +// Whether the Bluetooth advertisement is Public, Random or Unspecified. +//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; + +// Convenience for converting from Windows API to crate API. +impl TryFrom for BleAddressKind { + type Error = anyhow::Error; + + 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!( + "Attempting to construct `BleAddressKind` with device \ + advertising invalid address type {}.", + kind.0, + ))), + } + } +} + +// Convenience for converting from crate API to Windows API. +impl From for BluetoothAddressType { + fn from(kind: BleAddressKind) -> Self { + match kind { + BleAddressKind::Public => BluetoothAddressType::Public, + BleAddressKind::Random => BluetoothAddressType::Random, + } + } +} diff --git a/fastpair/rust/src/bluetooth/windows/device.rs b/fastpair/rust/src/bluetooth/windows/device.rs index 60bd872b..6d1a4a29 100644 --- a/fastpair/rust/src/bluetooth/windows/device.rs +++ b/fastpair/rust/src/bluetooth/windows/device.rs @@ -23,7 +23,7 @@ use windows::Devices::Bluetooth::{ BluetoothLEDevice, }; -use crate::bluetooth::common::{BluetoothError, Device}; +use crate::bluetooth::common::{BleAddress, BluetoothError, Device}; /// Concrete type implementing `Device`, used for Windows BLE. pub struct BleDevice { @@ -31,14 +31,14 @@ pub struct BleDevice { } 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?; + pub async fn new(addr: BleAddress) -> Result { + let kind = BluetoothAddressType::from(addr.get_kind()); + let addr = u64::from(addr); + + let inner = BluetoothLEDevice::FromBluetoothAddressWithBluetoothAddressTypeAsync( + addr, kind, + )? + .await?; Ok(BleDevice { inner }) } diff --git a/fastpair/rust/src/bluetooth/windows/mod.rs b/fastpair/rust/src/bluetooth/windows/mod.rs index 4835654d..f23035c5 100644 --- a/fastpair/rust/src/bluetooth/windows/mod.rs +++ b/fastpair/rust/src/bluetooth/windows/mod.rs @@ -14,8 +14,10 @@ /// Bluetooth LE module for Windows devices. mod adapter; +mod address; mod device; mod error; pub use adapter::*; +pub use address::*; pub use device::*; From fc07423925dfd3ad8dc98b6e34f9653108b6e2c5 Mon Sep 17 00:00:00 2001 From: Lucas Silva Shepard Date: Wed, 19 Jul 2023 11:29:53 -0700 Subject: [PATCH 04/11] [fp-rs] Implemented classic device pairing --- fastpair/rust/Cargo.toml | 1 + fastpair/rust/src/bluetooth/common/address.rs | 2 +- fastpair/rust/src/bluetooth/common/device.rs | 15 +- fastpair/rust/src/bluetooth/common/error.rs | 16 +- fastpair/rust/src/bluetooth/mod.rs | 12 +- fastpair/rust/src/bluetooth/windows/device.rs | 144 ++++++++++++++++-- fastpair/rust/src/bluetooth/windows/error.rs | 38 ++++- fastpair/rust/src/main.rs | 40 ++++- 8 files changed, 242 insertions(+), 26 deletions(-) diff --git a/fastpair/rust/Cargo.toml b/fastpair/rust/Cargo.toml index 02a7b908..a8bc4428 100644 --- a/fastpair/rust/Cargo.toml +++ b/fastpair/rust/Cargo.toml @@ -29,6 +29,7 @@ thiserror = "1.0.43" [target.'cfg(windows)'.dependencies] windows = { version = "0.48", features = [ "Devices_Bluetooth", + "Devices_Enumeration", "Devices_Bluetooth_Advertisement", "Foundation", ] } diff --git a/fastpair/rust/src/bluetooth/common/address.rs b/fastpair/rust/src/bluetooth/common/address.rs index 87c9139d..dc54e067 100644 --- a/fastpair/rust/src/bluetooth/common/address.rs +++ b/fastpair/rust/src/bluetooth/common/address.rs @@ -93,7 +93,7 @@ impl From for u64 { impl From for u64 { fn from(addr: ClassicAddress) -> Self { let mut bytes = [0u8; 8]; - bytes.copy_from_slice(&addr.0); + bytes[..6].copy_from_slice(&addr.0); u64::from_le_bytes(bytes) } diff --git a/fastpair/rust/src/bluetooth/common/device.rs b/fastpair/rust/src/bluetooth/common/device.rs index 33ef11c2..a4c5756c 100644 --- a/fastpair/rust/src/bluetooth/common/device.rs +++ b/fastpair/rust/src/bluetooth/common/device.rs @@ -12,12 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. +use async_trait::async_trait; + +use super::{Address, BluetoothError, PairingResult}; + /// Concrete types implementing this trait represent Bluetooth Peripheral devices. /// They provide methods for retrieving device info and running device actions, /// such as pairing. -use super::BluetoothError; - -pub trait Device { +#[async_trait] +pub trait Device: Sized { /// Retrieve the name advertised by this device. fn name(&self) -> Result; + + /// Retrieve this device's Bluetooth address information. + fn address(&self) -> Address; + + /// Attempt pairing with the peripheral device. + async fn pair(&self) -> Result; } diff --git a/fastpair/rust/src/bluetooth/common/error.rs b/fastpair/rust/src/bluetooth/common/error.rs index 3e7ac640..5c9e49e9 100644 --- a/fastpair/rust/src/bluetooth/common/error.rs +++ b/fastpair/rust/src/bluetooth/common/error.rs @@ -18,10 +18,12 @@ use thiserror::Error; #[non_exhaustive] #[derive(Error, Debug)] pub enum BluetoothError { + /// Reported when Bluetooth device pairing fails. + #[error("pairing error: {0}")] + PairingFailed(String), /// Indicates that the operation was rejected because the system is not in /// a state required for the operation's execution. /// E.g. The user calls `stop_scan()` or polls the advertisement stream - /// before calling `start_scan()`. #[error("failed precondition: {0}")] FailedPrecondition(String), /// Reported when the user calls an operation that is supported by their @@ -40,3 +42,15 @@ pub enum BluetoothError { #[error("internal error: {0}")] Internal(String), } + +/// Abstraction around platform-specific pairing status enums. +/// `PairingResult::Failure` should eventually be converted to +/// `BluetoothError::PairingFailed`. +#[non_exhaustive] +#[derive(Debug)] +pub enum PairingResult { + Success, + AlreadyPaired, + AlreadyInProgress, + Failure(String), +} diff --git a/fastpair/rust/src/bluetooth/mod.rs b/fastpair/rust/src/bluetooth/mod.rs index 765086f2..fb374de6 100644 --- a/fastpair/rust/src/bluetooth/mod.rs +++ b/fastpair/rust/src/bluetooth/mod.rs @@ -18,18 +18,24 @@ pub mod common; -pub use common::{Adapter, BluetoothError, Device}; +pub use common::{Adapter, Address, BluetoothError, ClassicAddress, Device}; cfg_if::cfg_if! { if #[cfg(windows)] { mod windows; - use self::windows::BleAdapter; + use self::windows::{ClassicDevice, BleAdapter}; } else { mod unsupported; - use unsupported::BleAdapter; + use unsupported::{ClassicDevice, BleAdapter}; } } pub async fn default_adapter() -> Result { BleAdapter::default().await } + +pub async fn new_classic_device( + addr: ClassicAddress, +) -> Result { + ClassicDevice::new(addr).await +} diff --git a/fastpair/rust/src/bluetooth/windows/device.rs b/fastpair/rust/src/bluetooth/windows/device.rs index 6d1a4a29..7e9f9cf0 100644 --- a/fastpair/rust/src/bluetooth/windows/device.rs +++ b/fastpair/rust/src/bluetooth/windows/device.rs @@ -13,42 +13,162 @@ // limitations under the License. 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 tracing::{info, warn}; +use windows::{ + Devices::{ + Bluetooth::{ + // Tuple struct 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 a discovered BT Classic device. + // https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.bluetoothdevice?view=winrt-22621 + BluetoothDevice, + + // Struct for interacting with a discovered BLE device. + // https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.bluetoothledevice?view=winrt-22621 + BluetoothLEDevice, + }, + Enumeration::{ + // Struct for custom pairing with a device. + // https://learn.microsoft.com/en-us/uwp/api/windows.devices.enumeration.deviceinformationcustompairing?view=winrt-22621 + DeviceInformationCustomPairing, + + // Tuple struct to indicate the kinds of pairing supported by the application. + // https://learn.microsoft.com/en-us/uwp/api/windows.devices.enumeration.devicepairingkinds?view=winrt-22621 + DevicePairingKinds, + + // Struct for retrieving data about a PairingRequested event. + // https://learn.microsoft.com/en-us/uwp/api/windows.devices.enumeration.devicepairingrequestedeventargs?view=winrt-22621 + DevicePairingRequestedEventArgs, + }, + }, + // Wraps a closure for handling events associated with a struct + // (e.g. PairingRequested event in `DeviceInformationCustomPairing`). + // https://learn.microsoft.com/en-us/uwp/api/windows.foundation.typedeventhandler-2?view=winrt-22621 + Foundation::TypedEventHandler, }; -use crate::bluetooth::common::{BleAddress, BluetoothError, Device}; +use crate::bluetooth::common::{Address, BleAddress, ClassicAddress, Device, BluetoothError, PairingResult}; /// Concrete type implementing `Device`, used for Windows BLE. pub struct BleDevice { inner: BluetoothLEDevice, + addr: BleAddress, +} + +/// Concrete type implementing `Device`, used for Windows Bluetooth Classic. +pub struct ClassicDevice { + inner: BluetoothDevice, + addr: ClassicAddress, } impl BleDevice { + /// `BleDevice` constructor. pub async fn new(addr: BleAddress) -> Result { let kind = BluetoothAddressType::from(addr.get_kind()); - let addr = u64::from(addr); + let raw_addr = u64::from(addr); let inner = BluetoothLEDevice::FromBluetoothAddressWithBluetoothAddressTypeAsync( - addr, kind, + raw_addr, kind, )? .await?; - Ok(BleDevice { inner }) + Ok(BleDevice { inner, addr }) } } #[async_trait] impl Device for BleDevice { + fn name(&self) -> Result { + Ok(self.inner.Name()?.to_string()) + } + + fn address(&self) -> Address { + Address::Ble(self.addr) + } + + async fn pair(&self) -> Result { + // BLE Audio isn't supported on Windows natively, so devices can pair + // but don't playback. Might possibly work with UWP. Since the Classic + // and BLE APIs are very similar, it might be possible to copy-paste + // `ClassicDevice::pair` directly. + unimplemented!("BLE Pairing is currently unsupported.") + } +} + + +impl ClassicDevice { + /// `ClassicDevice` constructor. + pub async fn new(addr: ClassicAddress) -> Result { + let raw_addr = u64::from(addr); + + let inner = BluetoothDevice::FromBluetoothAddressAsync( + raw_addr, + )? + .await?; + + Ok(ClassicDevice { inner, addr }) + } +} + +#[async_trait] +impl Device for ClassicDevice { fn name(&self) -> Result { Ok(self.inner.Name()?.to_string_lossy()) } + + fn address(&self) -> Address { + Address::Classic(self.addr) + } + + async fn pair(&self) -> Result { + let pair_info = self.inner.DeviceInformation()?.Pairing()?; + if pair_info.IsPaired()? { + info!("Device already paired"); + Ok(PairingResult::AlreadyPaired) + } else if !pair_info.CanPair()? { + info!("Device can't pair"); + Err(BluetoothError::PairingFailed(String::from("device can't pair"))) + } else { + let custom = pair_info.Custom()?; + custom.PairingRequested(&TypedEventHandler::new( + |_custom: &Option, + event_args: &Option, + | { + if let Some(event_args) = event_args { + match event_args.PairingKind()? { + DevicePairingKinds::ConfirmOnly => { + event_args.Accept() + } + _ => { + warn!("Unsupported pairing kind {:?}", event_args.PairingKind()); + Ok(()) + } + } + } else { + warn!("Empty pairing event arguments"); + Ok(()) + } + + }, + ))?; + let res = custom + .PairAsync( + DevicePairingKinds::ConfirmOnly + | DevicePairingKinds::ProvidePin + | DevicePairingKinds::ConfirmPinMatch + | DevicePairingKinds::DisplayPin, + )? + .await?; + let status = PairingResult::from(res.Status()?); + + match status { + PairingResult::Failure(msg) => Err(BluetoothError::PairingFailed(msg)), + _ => Ok(status), + } + } + } } mod tests { diff --git a/fastpair/rust/src/bluetooth/windows/error.rs b/fastpair/rust/src/bluetooth/windows/error.rs index 04c6bce3..59e12a31 100644 --- a/fastpair/rust/src/bluetooth/windows/error.rs +++ b/fastpair/rust/src/bluetooth/windows/error.rs @@ -12,10 +12,46 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::bluetooth::common::BluetoothError; +use windows::Devices::Enumeration::DevicePairingResultStatus; + +use crate::bluetooth::common::{BluetoothError, PairingResult}; impl From for BluetoothError { fn from(err: windows::core::Error) -> Self { BluetoothError::System(err.to_string()) } } + +// https://learn.microsoft.com/en-us/uwp/api/windows.devices.enumeration.devicepairingresultstatus?view=winrt-22621 +impl From for PairingResult { + fn from(status: DevicePairingResultStatus) -> Self { + match status { + DevicePairingResultStatus::Paired => PairingResult::Success, + DevicePairingResultStatus::AlreadyPaired => { + PairingResult::AlreadyPaired + } + DevicePairingResultStatus::OperationAlreadyInProgress => { + PairingResult::AlreadyInProgress + } + DevicePairingResultStatus::NotReadyToPair => PairingResult::Failure( + String::from("the device object is not in a state where it can be paired"), + ), + DevicePairingResultStatus::NotPaired => PairingResult::Failure(String::from("the device object is not currently paired.")), + DevicePairingResultStatus::ConnectionRejected => PairingResult::Failure(String::from("the device object rejected the connection.")), + DevicePairingResultStatus::TooManyConnections => PairingResult::Failure(String::from("the device object indicated it cannot accept any more incoming connections.")), + DevicePairingResultStatus::HardwareFailure => PairingResult::Failure(String::from("the device object indicated there was a hardware failure.")), + DevicePairingResultStatus::AuthenticationTimeout => PairingResult::Failure(String::from("the authentication process timed out before it could complete.")), + DevicePairingResultStatus::AuthenticationNotAllowed => PairingResult::Failure(String::from("the authentication protocol is not supported, so the device is not paired.")), + DevicePairingResultStatus::AuthenticationFailure => PairingResult::Failure(String::from("authentication failed, so the device is not paired. Either the device object or the application rejected the authentication.")), + DevicePairingResultStatus::NoSupportedProfiles => PairingResult::Failure(String::from("there are no network profiles for this device object to use.")), + DevicePairingResultStatus::ProtectionLevelCouldNotBeMet => PairingResult::Failure(String::from("the minimum level of protection is not supported by the device object or the application.")), + DevicePairingResultStatus::AccessDenied => PairingResult::Failure(String::from("your application does not have the appropriate permissions level to pair the device object.")), + DevicePairingResultStatus::InvalidCeremonyData => PairingResult::Failure(String::from("the ceremony data was incorrect.")), + DevicePairingResultStatus::PairingCanceled => PairingResult::Failure(String::from("the pairing action was canceled before completion.")), + DevicePairingResultStatus::RequiredHandlerNotRegistered => PairingResult::Failure(String::from("either the event handler wasn't registered or a required DevicePairingKinds was not supported.",)), + DevicePairingResultStatus::RejectedByHandler => PairingResult::Failure(String::from("the application handler rejected the pairing.")), + DevicePairingResultStatus::RemoteDeviceHasAssociation => PairingResult::Failure(String::from("the remote device already has an association.")), + DevicePairingResultStatus::Failed | _ => PairingResult::Failure(String::from("an unknown failure occurred.")), + } + } +} diff --git a/fastpair/rust/src/main.rs b/fastpair/rust/src/main.rs index c0326ba6..7b796f5a 100644 --- a/fastpair/rust/src/main.rs +++ b/fastpair/rust/src/main.rs @@ -18,18 +18,48 @@ use futures::executor; mod bluetooth; -use bluetooth::{Adapter, Device}; +use bluetooth::{Adapter, Address, ClassicAddress, Device}; fn main() -> Result<(), Box> { let run = async { let mut adapter = bluetooth::default_adapter().await?; adapter.start_scan_devices()?; - while let Ok(device) = adapter.next_device().await { - println!("found {}", device.name()?) - } + while let Ok(ble_device) = adapter.next_device().await { + let name = ble_device.name()?; - unreachable!("Done scanning"); + if name.contains("LE_WF-1000XM3") { + println!("FOUND {} ", name); + + let addr: Address = ble_device.address(); + + // Dynamic dispatch is necessary here because `BleDevice` and + // `ClassicDevice` share the `Device` trait (and thus must have + // the same return type for `address()` method). This can be + // changed if `Device` trait should exclusively define + // cross-platform behavior. + let classic_addr = match addr { + Address::Ble(ble) => ClassicAddress::try_from(ble), + Address::Classic(_) => unreachable!( + "Address should come from BLE Device, therefore \ + shouldn't be Classic." + ), + }?; + + let classic_device = + bluetooth::new_classic_device(classic_addr).await?; + + match classic_device.pair().await { + Ok(_) => { + println!("Pairing success!"); + } + Err(err) => println!("Error {}", err), + } + break; + } + } + println!("Done scanning"); + Ok(()) }; executor::block_on(run) From 4be9872f6dfc9b2699be0cf2ea1eaf83568dca9b Mon Sep 17 00:00:00 2001 From: Lucas Silva Shepard Date: Fri, 14 Jul 2023 23:25:57 -0700 Subject: [PATCH 05/11] [fp-rs] Updated device scanning interface to facilitate adding more functionality to device constructor. --- fastpair/rust/src/bluetooth/common/adapter.rs | 8 +- fastpair/rust/src/bluetooth/common/address.rs | 11 +- fastpair/rust/src/bluetooth/common/error.rs | 4 + .../rust/src/bluetooth/unsupported/adapter.rs | 4 +- .../rust/src/bluetooth/windows/adapter.rs | 110 +++++++++--------- .../rust/src/bluetooth/windows/address.rs | 16 +-- fastpair/rust/src/main.rs | 2 +- 7 files changed, 79 insertions(+), 76 deletions(-) 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()?; From 81592cc4c5ee302c365c4b020eafa6436385eb07 Mon Sep 17 00:00:00 2001 From: Lucas Silva Shepard Date: Fri, 14 Jul 2023 17:24:47 -0700 Subject: [PATCH 06/11] [fp-rs] Device now holds 16bit UUID service data, collected by Adapter. --- fastpair/rust/Cargo.toml | 2 + fastpair/rust/src/bluetooth/common/data.rs | 36 +++++++++++++ fastpair/rust/src/bluetooth/common/device.rs | 4 +- fastpair/rust/src/bluetooth/common/mod.rs | 2 + .../rust/src/bluetooth/windows/adapter.rs | 51 ++++++++++++++++--- fastpair/rust/src/bluetooth/windows/device.rs | 15 ++++-- fastpair/rust/src/main.rs | 4 +- 7 files changed, 101 insertions(+), 13 deletions(-) create mode 100644 fastpair/rust/src/bluetooth/common/data.rs diff --git a/fastpair/rust/Cargo.toml b/fastpair/rust/Cargo.toml index a8bc4428..02b48ebc 100644 --- a/fastpair/rust/Cargo.toml +++ b/fastpair/rust/Cargo.toml @@ -32,4 +32,6 @@ windows = { version = "0.48", features = [ "Devices_Enumeration", "Devices_Bluetooth_Advertisement", "Foundation", + "Foundation_Collections", + "Storage_Streams", ] } diff --git a/fastpair/rust/src/bluetooth/common/data.rs b/fastpair/rust/src/bluetooth/common/data.rs new file mode 100644 index 00000000..7ab7d57e --- /dev/null +++ b/fastpair/rust/src/bluetooth/common/data.rs @@ -0,0 +1,36 @@ +// 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. + +pub enum BleDataSection { + ServiceData16BitUUid = 0x16, +} + +pub struct ServiceData { + uuid: U, + data: Vec, +} + +impl ServiceData { + pub fn new(uuid: U, data: Vec) -> Self { + ServiceData { uuid, data } + } + + pub fn uuid(&self) -> U { + self.uuid + } + + pub fn data(&self) -> &Vec { + &self.data + } +} diff --git a/fastpair/rust/src/bluetooth/common/device.rs b/fastpair/rust/src/bluetooth/common/device.rs index a4c5756c..9c745df3 100644 --- a/fastpair/rust/src/bluetooth/common/device.rs +++ b/fastpair/rust/src/bluetooth/common/device.rs @@ -14,7 +14,7 @@ use async_trait::async_trait; -use super::{Address, BluetoothError, PairingResult}; +use super::{Address, BluetoothError, PairingResult, ServiceData}; /// Concrete types implementing this trait represent Bluetooth Peripheral devices. /// They provide methods for retrieving device info and running device actions, @@ -29,4 +29,6 @@ pub trait Device: Sized { /// Attempt pairing with the peripheral device. async fn pair(&self) -> Result; + + fn service_data(&self) -> &Vec>; } diff --git a/fastpair/rust/src/bluetooth/common/mod.rs b/fastpair/rust/src/bluetooth/common/mod.rs index f604ede6..5a03978a 100644 --- a/fastpair/rust/src/bluetooth/common/mod.rs +++ b/fastpair/rust/src/bluetooth/common/mod.rs @@ -15,10 +15,12 @@ /// Module for shared functionality between all Bluetooth platforms. mod adapter; mod address; +mod data; mod device; mod error; pub use adapter::*; pub use address::*; +pub use data::*; pub use device::*; pub use error::*; diff --git a/fastpair/rust/src/bluetooth/windows/adapter.rs b/fastpair/rust/src/bluetooth/windows/adapter.rs index 3ffcfb33..9d7c9ab8 100644 --- a/fastpair/rust/src/bluetooth/windows/adapter.rs +++ b/fastpair/rust/src/bluetooth/windows/adapter.rs @@ -51,11 +51,16 @@ use windows::{ // (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, + + // Struct for reading data from a Windows stream, like an IVectorView. + // https://learn.microsoft.com/en-us/uwp/api/windows.storage.streams.datareader?view=winrt-22621 + Storage::Streams::DataReader, }; use super::BleDevice; use crate::bluetooth::common::{ - Adapter, BleAddress, BleAddressKind, BluetoothError, + Adapter, BleAddress, BleAddressKind, BleDataSection, BluetoothError, + ServiceData, }; /// Struct holding the necessary fields for listening to and handling incoming @@ -73,6 +78,39 @@ pub struct BleAdapter { listener: Option, } +/// Parse the advertisement's service data. +/// Further Reading: +/// * `BleMedium::AdvertisementReceivedHandler` under +/// github.com/google/nearby/internal/platform/implementation/windows_ble/ble_medium.cc. +/// * Bluetooth Core Specification Supplement, Part A, Section 1.11. +/// * go/fast_pair_windows_data_parse. +#[inline] +fn get_service_data_16bit_uuid( + event_args: &BluetoothLEAdvertisementReceivedEventArgs, +) -> Result>, BluetoothError> { + let advertisement = event_args.Advertisement()?; + let mut service_data_vec = Vec::new(); + + // Note `service_data` is `!Send` and `!Sync`. This means processing must + // occur in a synchronous environment (namely, this function's scope). + // The compiler will complain if similar code is written between awaits + // in an async function. + for service_data in advertisement + .GetSectionsByType(BleDataSection::ServiceData16BitUUid as u8)? + { + let data_reader = DataReader::FromBuffer(&service_data.Data()?)?; + let uuid = data_reader.ReadUInt16()?; + + let unconsumed_buffer_len = + data_reader.UnconsumedBufferLength()? as usize; + let mut data = vec![0u8; unconsumed_buffer_len]; + data_reader.ReadBytes(&mut data)?; + + service_data_vec.push(ServiceData::new(uuid, data)); + } + Ok(service_data_vec) +} + #[async_trait] impl Adapter for BleAdapter { type Device = BleDevice; @@ -202,13 +240,12 @@ impl Adapter for BleAdapter { let kind = BleAddressKind::try_from(kind)?; let addr = BleAddress::new(addr, kind); + let service_data = + get_service_data_16bit_uuid(&event_args)?; - match BleDevice::new(addr).await { - Ok(device) => break Ok(device), - Err(err) => { - warn!("Error creating device: {:?}", err); - } - } + let device = BleDevice::new(addr, service_data).await?; + + break Ok(device); } } } diff --git a/fastpair/rust/src/bluetooth/windows/device.rs b/fastpair/rust/src/bluetooth/windows/device.rs index 7e9f9cf0..30f1fae7 100644 --- a/fastpair/rust/src/bluetooth/windows/device.rs +++ b/fastpair/rust/src/bluetooth/windows/device.rs @@ -49,12 +49,13 @@ use windows::{ Foundation::TypedEventHandler, }; -use crate::bluetooth::common::{Address, BleAddress, ClassicAddress, Device, BluetoothError, PairingResult}; +use crate::bluetooth::common::{Address, BleAddress, ClassicAddress, Device, ServiceData, BluetoothError, PairingResult}; /// Concrete type implementing `Device`, used for Windows BLE. pub struct BleDevice { inner: BluetoothLEDevice, addr: BleAddress, + service_data: Vec> } /// Concrete type implementing `Device`, used for Windows Bluetooth Classic. @@ -65,7 +66,7 @@ pub struct ClassicDevice { impl BleDevice { /// `BleDevice` constructor. - pub async fn new(addr: BleAddress) -> Result { + pub async fn new(addr: BleAddress, service_data: Vec>) -> Result { let kind = BluetoothAddressType::from(addr.get_kind()); let raw_addr = u64::from(addr); @@ -74,7 +75,7 @@ impl BleDevice { )? .await?; - Ok(BleDevice { inner, addr }) + Ok(BleDevice { inner, addr, service_data }) } } @@ -95,6 +96,10 @@ impl Device for BleDevice { // `ClassicDevice::pair` directly. unimplemented!("BLE Pairing is currently unsupported.") } + + fn service_data(&self) -> &Vec> { + &self.service_data + } } @@ -169,6 +174,10 @@ impl Device for ClassicDevice { } } } + + fn service_data(&self) -> &Vec> { + unimplemented!("Service data is currently unsupported for Classic devices.") + } } mod tests { diff --git a/fastpair/rust/src/main.rs b/fastpair/rust/src/main.rs index 95c665c2..56b290a3 100644 --- a/fastpair/rust/src/main.rs +++ b/fastpair/rust/src/main.rs @@ -36,8 +36,8 @@ fn main() -> Result<(), Box> { // Dynamic dispatch is necessary here because `BleDevice` and // `ClassicDevice` share the `Device` trait (and thus must have // the same return type for `address()` method). This can be - // changed if `Device` trait should exclusively define - // cross-platform behavior. + // changed later if `Device` trait should exclusively define + // shared cross-platform behavior. let classic_addr = match addr { Address::Ble(ble) => ClassicAddress::try_from(ble), Address::Classic(_) => unreachable!( From 7730a0f1db3ef3590eff1e617888fc7be2d393ca Mon Sep 17 00:00:00 2001 From: Lucas Silva Shepard Date: Mon, 17 Jul 2023 17:02:55 -0700 Subject: [PATCH 07/11] [fp-rs] Updating Fast Pair Seeker to filter out non-FP advertisements. --- fastpair/rust/src/main.rs | 45 ++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/fastpair/rust/src/main.rs b/fastpair/rust/src/main.rs index 56b290a3..b7cb0937 100644 --- a/fastpair/rust/src/main.rs +++ b/fastpair/rust/src/main.rs @@ -28,34 +28,41 @@ fn main() -> Result<(), Box> { while let Ok(ble_device) = adapter.next_device().await { let name = ble_device.name()?; - if name.contains("LE_WF-1000XM3") { - println!("FOUND {} ", name); + for service_data in ble_device.service_data() { + let uuid = service_data.uuid(); - let addr: Address = ble_device.address(); + // This is a Fast Pair device. + if uuid == 0x2cfe { + if name.contains("LE_WF-1000XM3") { + println!("FOUND {} ", name); - // Dynamic dispatch is necessary here because `BleDevice` and - // `ClassicDevice` share the `Device` trait (and thus must have - // the same return type for `address()` method). This can be - // changed later if `Device` trait should exclusively define - // shared cross-platform behavior. - let classic_addr = match addr { - Address::Ble(ble) => ClassicAddress::try_from(ble), - Address::Classic(_) => unreachable!( + let addr: Address = ble_device.address(); + + // Dynamic dispatch is necessary here because `BleDevice` and + // `ClassicDevice` share the `Device` trait (and thus must have + // the same return type for `address()` method). This can be + // changed later if `Device` trait should exclusively define + // shared cross-platform behavior. + let classic_addr = match addr { + Address::Ble(ble) => ClassicAddress::try_from(ble), + Address::Classic(_) => panic!( "Address should come from BLE Device, therefore \ shouldn't be Classic." ), - }?; + }?; - let classic_device = - bluetooth::new_classic_device(classic_addr).await?; + let classic_device = + bluetooth::new_classic_device(classic_addr).await?; - match classic_device.pair().await { - Ok(_) => { - println!("Pairing success!"); + match classic_device.pair().await { + Ok(_) => { + println!("Pairing success!"); + } + Err(err) => println!("Error {}", err), + } + break; } - Err(err) => println!("Error {}", err), } - break; } } println!("Done scanning"); From ba945aa360dc90a643b49c911a3725273371891d Mon Sep 17 00:00:00 2001 From: Lucas Silva Shepard Date: Sun, 16 Jul 2023 02:26:36 -0700 Subject: [PATCH 08/11] [fp-rs] Implemented device selection CLI. --- fastpair/rust/src/bluetooth/common/address.rs | 7 +- fastpair/rust/src/bluetooth/mod.rs | 4 +- fastpair/rust/src/main.rs | 115 +++++++++++++----- 3 files changed, 89 insertions(+), 37 deletions(-) diff --git a/fastpair/rust/src/bluetooth/common/address.rs b/fastpair/rust/src/bluetooth/common/address.rs index 9cbf2367..7071e8f8 100644 --- a/fastpair/rust/src/bluetooth/common/address.rs +++ b/fastpair/rust/src/bluetooth/common/address.rs @@ -16,24 +16,25 @@ 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)] +#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)] pub enum BleAddressKind { Public, Random, } /// Struct representing a 48-bit BLE Address and its type. -#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)] pub struct BleAddress { val: [u8; 6], kind: BleAddressKind, } /// Struct representing a 48-bit BT Classic address. -#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)] pub struct ClassicAddress([u8; 6]); /// Enum for interfacing with Bluetooth Addresses. +#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)] pub enum Address { Ble(BleAddress), Classic(ClassicAddress), diff --git a/fastpair/rust/src/bluetooth/mod.rs b/fastpair/rust/src/bluetooth/mod.rs index fb374de6..cd1a7247 100644 --- a/fastpair/rust/src/bluetooth/mod.rs +++ b/fastpair/rust/src/bluetooth/mod.rs @@ -23,14 +23,14 @@ pub use common::{Adapter, Address, BluetoothError, ClassicAddress, Device}; cfg_if::cfg_if! { if #[cfg(windows)] { mod windows; - use self::windows::{ClassicDevice, BleAdapter}; + pub use self::windows::{ClassicDevice, BleAdapter}; } else { mod unsupported; use unsupported::{ClassicDevice, BleAdapter}; } } -pub async fn default_adapter() -> Result { +pub async fn default_adapter() -> Result { BleAdapter::default().await } diff --git a/fastpair/rust/src/main.rs b/fastpair/rust/src/main.rs index b7cb0937..d9627525 100644 --- a/fastpair/rust/src/main.rs +++ b/fastpair/rust/src/main.rs @@ -12,56 +12,107 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::error::Error; +use std::{ + collections::HashSet, + error::Error, + io::{self, Write}, + sync::Arc, + thread, +}; -use futures::executor; +use futures::{ + executor::{self, block_on}, + lock::Mutex, +}; mod bluetooth; -use bluetooth::{Adapter, Address, ClassicAddress, Device}; +use bluetooth::{Adapter, Address, BleAdapter, ClassicAddress, Device}; + +async fn get_user_input( + device_vec: Arc::Device>>>, +) -> Result<(), Box> { + let mut buffer = String::new(); + loop { + io::stdout().flush()?; + buffer.clear(); + io::stdin().read_line(&mut buffer)?; + + let val = match buffer.trim().parse::() { + Ok(val) => val, + Err(_) => { + println!("Please enter a valid digit."); + continue; + } + }; + + let index_to_device = device_vec.lock().await; + match index_to_device.get(val) { + Some(device) => { + let addr: Address = device.address(); + + // Dynamic dispatch is necessary here because `BleDevice` and + // `ClassicDevice` share the `Device` trait (and thus must have + // the same return type for `address()` method). This can be + // changed later if `Device` trait should exclusively define + // shared cross-platform behavior. + let classic_addr = match addr { + Address::Ble(ble) => ClassicAddress::try_from(ble), + Address::Classic(_) => panic!( + "Address should come from BLE Device, therefore \ + shouldn't be Classic." + ), + }?; + + let classic_device = + bluetooth::new_classic_device(classic_addr).await?; + + match classic_device.pair().await { + Ok(_) => { + println!("Pairing success!"); + } + Err(err) => println!("Error {}", err), + } + break Ok(()); + } + None => println!("Please enter a valid digit."), + } + } +} fn main() -> Result<(), Box> { let run = async { let mut adapter = bluetooth::default_adapter().await?; adapter.start_scan()?; - while let Ok(ble_device) = adapter.next_device().await { - let name = ble_device.name()?; + let mut addr_set = HashSet::new(); + let device_vec = Arc::new(Mutex::new(Vec::new())); + { + // Process user input in a separate thread. + let device_vec = device_vec.clone(); + thread::spawn(|| block_on(get_user_input(device_vec)).unwrap()); + } + + let mut counter: u32 = 0; + + // Retrieve incoming device advertisements. + while let Ok(ble_device) = adapter.next_device().await { for service_data in ble_device.service_data() { let uuid = service_data.uuid(); // This is a Fast Pair device. if uuid == 0x2cfe { - if name.contains("LE_WF-1000XM3") { - println!("FOUND {} ", name); + let addr: Address = ble_device.address(); + let name = ble_device.name()?; - let addr: Address = ble_device.address(); - - // Dynamic dispatch is necessary here because `BleDevice` and - // `ClassicDevice` share the `Device` trait (and thus must have - // the same return type for `address()` method). This can be - // changed later if `Device` trait should exclusively define - // shared cross-platform behavior. - let classic_addr = match addr { - Address::Ble(ble) => ClassicAddress::try_from(ble), - Address::Classic(_) => panic!( - "Address should come from BLE Device, therefore \ - shouldn't be Classic." - ), - }?; - - let classic_device = - bluetooth::new_classic_device(classic_addr).await?; - - match classic_device.pair().await { - Ok(_) => { - println!("Pairing success!"); - } - Err(err) => println!("Error {}", err), - } - break; + if addr_set.insert(addr) { + // New FP device discovered. + println!("{}: {}", counter, name); + device_vec.lock().await.push(ble_device); + counter += 1; } + break; } } } From f42fdb418641965b555ef28ec1fc6caaa986f2f6 Mon Sep 17 00:00:00 2001 From: Lucas Silva Shepard Date: Thu, 20 Jul 2023 11:00:05 -0700 Subject: [PATCH 09/11] [fp-rs] Temporarily removing bluetooth lib abstraction layer, applications now talk directly to platform code. Rust's type system is causing issues with notating associated types. For now, concrete types will be used, but this will be fixed when the API is updated with splitting Device into ClassicDevice and BleDevice. --- fastpair/rust/src/bluetooth/common/address.rs | 7 ------ fastpair/rust/src/bluetooth/common/device.rs | 6 +++-- fastpair/rust/src/bluetooth/mod.rs | 17 ++++--------- fastpair/rust/src/bluetooth/windows/device.rs | 14 +++++++---- fastpair/rust/src/main.rs | 24 +++++-------------- 5 files changed, 24 insertions(+), 44 deletions(-) diff --git a/fastpair/rust/src/bluetooth/common/address.rs b/fastpair/rust/src/bluetooth/common/address.rs index 7071e8f8..485ff731 100644 --- a/fastpair/rust/src/bluetooth/common/address.rs +++ b/fastpair/rust/src/bluetooth/common/address.rs @@ -33,13 +33,6 @@ pub struct BleAddress { #[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)] pub struct ClassicAddress([u8; 6]); -/// Enum for interfacing with Bluetooth Addresses. -#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)] -pub enum Address { - Ble(BleAddress), - Classic(ClassicAddress), -} - impl BleAddress { /// `BleAddress` constructor. pub fn new(addr: u64, kind: BleAddressKind) -> Self { diff --git a/fastpair/rust/src/bluetooth/common/device.rs b/fastpair/rust/src/bluetooth/common/device.rs index 9c745df3..30e277ae 100644 --- a/fastpair/rust/src/bluetooth/common/device.rs +++ b/fastpair/rust/src/bluetooth/common/device.rs @@ -14,18 +14,20 @@ use async_trait::async_trait; -use super::{Address, BluetoothError, PairingResult, ServiceData}; +use super::{BluetoothError, PairingResult, ServiceData}; /// Concrete types implementing this trait represent Bluetooth Peripheral devices. /// They provide methods for retrieving device info and running device actions, /// such as pairing. #[async_trait] pub trait Device: Sized { + type Address; + /// Retrieve the name advertised by this device. fn name(&self) -> Result; /// Retrieve this device's Bluetooth address information. - fn address(&self) -> Address; + fn address(&self) -> Self::Address; /// Attempt pairing with the peripheral device. async fn pair(&self) -> Result; diff --git a/fastpair/rust/src/bluetooth/mod.rs b/fastpair/rust/src/bluetooth/mod.rs index cd1a7247..c996bcd6 100644 --- a/fastpair/rust/src/bluetooth/mod.rs +++ b/fastpair/rust/src/bluetooth/mod.rs @@ -18,24 +18,17 @@ pub mod common; -pub use common::{Adapter, Address, BluetoothError, ClassicAddress, Device}; +pub use common::{Adapter, BluetoothError, ClassicAddress, Device}; cfg_if::cfg_if! { if #[cfg(windows)] { mod windows; - pub use self::windows::{ClassicDevice, BleAdapter}; + use self::windows as platform; } else { mod unsupported; - use unsupported::{ClassicDevice, BleAdapter}; + use unsupported as platform; } } -pub async fn default_adapter() -> Result { - BleAdapter::default().await -} - -pub async fn new_classic_device( - addr: ClassicAddress, -) -> Result { - ClassicDevice::new(addr).await -} +pub type BleAdapter = platform::BleAdapter; +pub type ClassicDevice = platform::ClassicDevice; diff --git a/fastpair/rust/src/bluetooth/windows/device.rs b/fastpair/rust/src/bluetooth/windows/device.rs index 30f1fae7..a1551211 100644 --- a/fastpair/rust/src/bluetooth/windows/device.rs +++ b/fastpair/rust/src/bluetooth/windows/device.rs @@ -49,7 +49,7 @@ use windows::{ Foundation::TypedEventHandler, }; -use crate::bluetooth::common::{Address, BleAddress, ClassicAddress, Device, ServiceData, BluetoothError, PairingResult}; +use crate::bluetooth::{common::{BleAddress, ClassicAddress, Device, ServiceData, BluetoothError, PairingResult}, BleAdapter}; /// Concrete type implementing `Device`, used for Windows BLE. pub struct BleDevice { @@ -81,12 +81,14 @@ impl BleDevice { #[async_trait] impl Device for BleDevice { + type Address = BleAddress; + fn name(&self) -> Result { Ok(self.inner.Name()?.to_string()) } - fn address(&self) -> Address { - Address::Ble(self.addr) + fn address(&self) -> Self::Address { + self.addr } async fn pair(&self) -> Result { @@ -119,12 +121,14 @@ impl ClassicDevice { #[async_trait] impl Device for ClassicDevice { + type Address = ClassicAddress; + fn name(&self) -> Result { Ok(self.inner.Name()?.to_string_lossy()) } - fn address(&self) -> Address { - Address::Classic(self.addr) + fn address(&self) -> Self::Address { + self.addr } async fn pair(&self) -> Result { diff --git a/fastpair/rust/src/main.rs b/fastpair/rust/src/main.rs index d9627525..ab41a698 100644 --- a/fastpair/rust/src/main.rs +++ b/fastpair/rust/src/main.rs @@ -27,7 +27,7 @@ use futures::{ mod bluetooth; -use bluetooth::{Adapter, Address, BleAdapter, ClassicAddress, Device}; +use bluetooth::{Adapter, BleAdapter, ClassicAddress, Device}; async fn get_user_input( device_vec: Arc::Device>>>, @@ -49,23 +49,11 @@ async fn get_user_input( let index_to_device = device_vec.lock().await; match index_to_device.get(val) { Some(device) => { - let addr: Address = device.address(); - - // Dynamic dispatch is necessary here because `BleDevice` and - // `ClassicDevice` share the `Device` trait (and thus must have - // the same return type for `address()` method). This can be - // changed later if `Device` trait should exclusively define - // shared cross-platform behavior. - let classic_addr = match addr { - Address::Ble(ble) => ClassicAddress::try_from(ble), - Address::Classic(_) => panic!( - "Address should come from BLE Device, therefore \ - shouldn't be Classic." - ), - }?; + let addr = device.address(); + let classic_addr = ClassicAddress::try_from(addr)?; let classic_device = - bluetooth::new_classic_device(classic_addr).await?; + bluetooth::ClassicDevice::new(classic_addr).await?; match classic_device.pair().await { Ok(_) => { @@ -82,7 +70,7 @@ async fn get_user_input( fn main() -> Result<(), Box> { let run = async { - let mut adapter = bluetooth::default_adapter().await?; + let mut adapter = bluetooth::BleAdapter::default().await?; adapter.start_scan()?; let mut addr_set = HashSet::new(); @@ -103,7 +91,7 @@ fn main() -> Result<(), Box> { // This is a Fast Pair device. if uuid == 0x2cfe { - let addr: Address = ble_device.address(); + let addr = ble_device.address(); let name = ble_device.name()?; if addr_set.insert(addr) { From 25a3257880314ed589dd533b10289a99eeaed963 Mon Sep 17 00:00:00 2001 From: Lucas Silva Shepard Date: Mon, 24 Jul 2023 10:41:46 -0700 Subject: [PATCH 10/11] [fp-rs] Moving service data and advertisement information into separate BleAdvertisement struct. --- fastpair/rust/src/bluetooth/common/adapter.rs | 9 +- .../src/bluetooth/common/advertisement.rs | 89 +++++++++++++++ fastpair/rust/src/bluetooth/common/data.rs | 36 ------ fastpair/rust/src/bluetooth/common/device.rs | 4 +- fastpair/rust/src/bluetooth/common/mod.rs | 4 +- fastpair/rust/src/bluetooth/mod.rs | 6 +- .../rust/src/bluetooth/windows/adapter.rs | 64 ++--------- .../src/bluetooth/windows/advertisement.rs | 108 ++++++++++++++++++ fastpair/rust/src/bluetooth/windows/device.rs | 15 +-- fastpair/rust/src/bluetooth/windows/mod.rs | 2 + fastpair/rust/src/main.rs | 17 ++- 11 files changed, 238 insertions(+), 116 deletions(-) create mode 100644 fastpair/rust/src/bluetooth/common/advertisement.rs delete mode 100644 fastpair/rust/src/bluetooth/common/data.rs create mode 100644 fastpair/rust/src/bluetooth/windows/advertisement.rs diff --git a/fastpair/rust/src/bluetooth/common/adapter.rs b/fastpair/rust/src/bluetooth/common/adapter.rs index 1b07e250..f6dda1b9 100644 --- a/fastpair/rust/src/bluetooth/common/adapter.rs +++ b/fastpair/rust/src/bluetooth/common/adapter.rs @@ -14,14 +14,12 @@ use async_trait::async_trait; -use super::{BluetoothError, Device}; +use super::{BleAdvertisement, BleDataTypeId, BluetoothError}; /// 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; @@ -32,5 +30,8 @@ pub trait Adapter: Sized { fn stop_scan(&mut self) -> Result<(), BluetoothError>; /// Poll next discovered device. - async fn next_device(&mut self) -> Result; + async fn next_advertisement( + &mut self, + data_selector: Option<&Vec>, + ) -> Result; } diff --git a/fastpair/rust/src/bluetooth/common/advertisement.rs b/fastpair/rust/src/bluetooth/common/advertisement.rs new file mode 100644 index 00000000..8260936e --- /dev/null +++ b/fastpair/rust/src/bluetooth/common/advertisement.rs @@ -0,0 +1,89 @@ +// 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 super::{BleAddress, BluetoothError}; + +/// Holds data related to an incoming BLE Advertisement. This includes +/// information about the advertisement (e.g. address of sender) as well as +/// data sections extracted from the advertisement. Platform-specific methods +/// should be written to load in data sections from incoming advertisements. +pub struct BleAdvertisement { + address: BleAddress, + service_data_16bit_uuid: Option>>, +} + +impl BleAdvertisement { + /// Construct a new `BleAdvertisement` instance. + pub(crate) fn new(address: BleAddress) -> Self { + BleAdvertisement { + address, + service_data_16bit_uuid: None, + } + } + + /// Retrieve the `BleAddress` that emitted this advertisement. + pub fn address(&self) -> BleAddress { + self.address + } + + /// Setter for `ServiceData` field with 16bit UUID. + pub(crate) fn set_service_data_16bit_uuid( + &mut self, + data_sections: Vec>, + ) { + self.service_data_16bit_uuid = Some(data_sections); + } + + /// Getter for `ServiceData` field with 16bit UUID. + pub fn service_data_16bit_uuid( + &self, + ) -> Result<&Vec>, BluetoothError> { + match &self.service_data_16bit_uuid { + Some(service_data) => Ok(&service_data), + None => Err(BluetoothError::FailedPrecondition(String::from( + "No service data has been loaded into this advertisement.", + ))), + } + } +} + +/// Enum denoting the assigned number of Bluetooth common data types. Used for +/// fetching specific data sections from a Bluetooth advertisement. +/// Bluetooth Assigned Numbers, Section 2.3 +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub enum BleDataTypeId { + ServiceData16BitUuid = 0x16, +} + +/// Struct representing the Bluetooth Service Data common data type. `U` should +/// be one of the valid uuid sizes, specified in: +/// Bluetooth Supplement to the Core Specification, Part A, Section 1.11. +pub struct ServiceData { + uuid: U, + data: Vec, +} + +impl ServiceData { + pub fn new(uuid: U, data: Vec) -> Self { + ServiceData { uuid, data } + } + + pub fn uuid(&self) -> U { + self.uuid + } + + pub fn data(&self) -> &Vec { + &self.data + } +} diff --git a/fastpair/rust/src/bluetooth/common/data.rs b/fastpair/rust/src/bluetooth/common/data.rs deleted file mode 100644 index 7ab7d57e..00000000 --- a/fastpair/rust/src/bluetooth/common/data.rs +++ /dev/null @@ -1,36 +0,0 @@ -// 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. - -pub enum BleDataSection { - ServiceData16BitUUid = 0x16, -} - -pub struct ServiceData { - uuid: U, - data: Vec, -} - -impl ServiceData { - pub fn new(uuid: U, data: Vec) -> Self { - ServiceData { uuid, data } - } - - pub fn uuid(&self) -> U { - self.uuid - } - - pub fn data(&self) -> &Vec { - &self.data - } -} diff --git a/fastpair/rust/src/bluetooth/common/device.rs b/fastpair/rust/src/bluetooth/common/device.rs index 30e277ae..1f861155 100644 --- a/fastpair/rust/src/bluetooth/common/device.rs +++ b/fastpair/rust/src/bluetooth/common/device.rs @@ -14,7 +14,7 @@ use async_trait::async_trait; -use super::{BluetoothError, PairingResult, ServiceData}; +use super::{BluetoothError, PairingResult}; /// Concrete types implementing this trait represent Bluetooth Peripheral devices. /// They provide methods for retrieving device info and running device actions, @@ -31,6 +31,4 @@ pub trait Device: Sized { /// Attempt pairing with the peripheral device. async fn pair(&self) -> Result; - - fn service_data(&self) -> &Vec>; } diff --git a/fastpair/rust/src/bluetooth/common/mod.rs b/fastpair/rust/src/bluetooth/common/mod.rs index 5a03978a..13b05114 100644 --- a/fastpair/rust/src/bluetooth/common/mod.rs +++ b/fastpair/rust/src/bluetooth/common/mod.rs @@ -15,12 +15,12 @@ /// Module for shared functionality between all Bluetooth platforms. mod adapter; mod address; -mod data; +mod advertisement; mod device; mod error; pub use adapter::*; pub use address::*; -pub use data::*; +pub use advertisement::*; pub use device::*; pub use error::*; diff --git a/fastpair/rust/src/bluetooth/mod.rs b/fastpair/rust/src/bluetooth/mod.rs index c996bcd6..979a27c7 100644 --- a/fastpair/rust/src/bluetooth/mod.rs +++ b/fastpair/rust/src/bluetooth/mod.rs @@ -18,7 +18,10 @@ pub mod common; -pub use common::{Adapter, BluetoothError, ClassicAddress, Device}; +pub use common::{ + Adapter, BleAdvertisement, BleDataTypeId, BluetoothError, ClassicAddress, + Device, +}; cfg_if::cfg_if! { if #[cfg(windows)] { @@ -31,4 +34,5 @@ cfg_if::cfg_if! { } pub type BleAdapter = platform::BleAdapter; +pub type BleDevice = platform::BleDevice; pub type ClassicDevice = platform::ClassicDevice; diff --git a/fastpair/rust/src/bluetooth/windows/adapter.rs b/fastpair/rust/src/bluetooth/windows/adapter.rs index 9d7c9ab8..2eb3ded2 100644 --- a/fastpair/rust/src/bluetooth/windows/adapter.rs +++ b/fastpair/rust/src/bluetooth/windows/adapter.rs @@ -51,16 +51,10 @@ use windows::{ // (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, - - // Struct for reading data from a Windows stream, like an IVectorView. - // https://learn.microsoft.com/en-us/uwp/api/windows.storage.streams.datareader?view=winrt-22621 - Storage::Streams::DataReader, }; -use super::BleDevice; use crate::bluetooth::common::{ - Adapter, BleAddress, BleAddressKind, BleDataSection, BluetoothError, - ServiceData, + Adapter, BleAdvertisement, BleDataTypeId, BluetoothError, }; /// Struct holding the necessary fields for listening to and handling incoming @@ -78,43 +72,8 @@ pub struct BleAdapter { listener: Option, } -/// Parse the advertisement's service data. -/// Further Reading: -/// * `BleMedium::AdvertisementReceivedHandler` under -/// github.com/google/nearby/internal/platform/implementation/windows_ble/ble_medium.cc. -/// * Bluetooth Core Specification Supplement, Part A, Section 1.11. -/// * go/fast_pair_windows_data_parse. -#[inline] -fn get_service_data_16bit_uuid( - event_args: &BluetoothLEAdvertisementReceivedEventArgs, -) -> Result>, BluetoothError> { - let advertisement = event_args.Advertisement()?; - let mut service_data_vec = Vec::new(); - - // Note `service_data` is `!Send` and `!Sync`. This means processing must - // occur in a synchronous environment (namely, this function's scope). - // The compiler will complain if similar code is written between awaits - // in an async function. - for service_data in advertisement - .GetSectionsByType(BleDataSection::ServiceData16BitUUid as u8)? - { - let data_reader = DataReader::FromBuffer(&service_data.Data()?)?; - let uuid = data_reader.ReadUInt16()?; - - let unconsumed_buffer_len = - data_reader.UnconsumedBufferLength()? as usize; - let mut data = vec![0u8; unconsumed_buffer_len]; - data_reader.ReadBytes(&mut data)?; - - service_data_vec.push(ServiceData::new(uuid, data)); - } - Ok(service_data_vec) -} - #[async_trait] impl Adapter for BleAdapter { - type Device = BleDevice; - async fn default() -> Result { let inner = BluetoothAdapter::GetDefaultAsync()?.await?; @@ -218,7 +177,10 @@ impl Adapter for BleAdapter { } } - async fn next_device(&mut self) -> Result { + async fn next_advertisement( + &mut self, + datatype_selector: Option<&Vec>, + ) -> Result { 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 @@ -235,17 +197,15 @@ impl Adapter for BleAdapter { () } _ => { - let kind = event_args.BluetoothAddressType()?; - let addr = event_args.BluetoothAddress()?; + let mut advertisement = + BleAdvertisement::try_from(&event_args)?; - let kind = BleAddressKind::try_from(kind)?; - let addr = BleAddress::new(addr, kind); - let service_data = - get_service_data_16bit_uuid(&event_args)?; + if let Some(datatype_selector) = datatype_selector { + advertisement + .load_data(&event_args, datatype_selector)?; + } - let device = BleDevice::new(addr, service_data).await?; - - break Ok(device); + break Ok(advertisement); } } } diff --git a/fastpair/rust/src/bluetooth/windows/advertisement.rs b/fastpair/rust/src/bluetooth/windows/advertisement.rs new file mode 100644 index 00000000..526e2d5a --- /dev/null +++ b/fastpair/rust/src/bluetooth/windows/advertisement.rs @@ -0,0 +1,108 @@ +// 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::{ + // Struct that receives Bluetooth Low Energy (LE) advertisements. + // https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.advertisement.bluetoothleadvertisementwatcher?view=winrt-22621 + Devices::Bluetooth::Advertisement::{ + BluetoothLEAdvertisementDataSection, + BluetoothLEAdvertisementReceivedEventArgs, + }, + + // Struct representing an immutable view into a vector. + // https://learn.microsoft.com/en-us/uwp/api/windows.foundation.collections.ivectorview-1?view=winrt-22621 + Foundation::Collections::IVectorView, + + // Struct for reading data from a Windows stream, like an IVectorView. + // https://learn.microsoft.com/en-us/uwp/api/windows.storage.streams.datareader?view=winrt-22621 + Storage::Streams::DataReader, +}; + +use crate::bluetooth::common::{ + BleAddress, BleAddressKind, BleAdvertisement, BleDataTypeId, + BluetoothError, ServiceData, +}; + +impl TryFrom<&BluetoothLEAdvertisementReceivedEventArgs> for BleAdvertisement { + type Error = BluetoothError; + + fn try_from( + adv: &BluetoothLEAdvertisementReceivedEventArgs, + ) -> Result { + let addr = adv.BluetoothAddress()?; + let kind = BleAddressKind::try_from(adv.BluetoothAddressType()?)?; + + let addr = BleAddress::new(addr, kind); + + Ok(BleAdvertisement::new(addr)) + } +} + +impl BleAdvertisement { + /// Load data of selected data types into self by parsing the raw Windows + /// advertisement. + /// See: Supplement to the Bluetooth Core Specification Part A, Section 1. + pub(crate) fn load_data( + &mut self, + adv: &BluetoothLEAdvertisementReceivedEventArgs, + datatype_ids: &[BleDataTypeId], + ) -> Result<(), BluetoothError> { + let adv = adv.Advertisement()?; + + for datatype_id in datatype_ids { + // Note `raw_data_sections` is `!Send` and `!Sync`. This means + // processing must occur in a synchronous environment. The compiler + // will complain if parsing is done in an async function. + let raw_data_sections = + adv.GetSectionsByType((*datatype_id) as u8)?; + match datatype_id { + BleDataTypeId::ServiceData16BitUuid => { + let service_data = + parse_service_data_16bit_uuid(raw_data_sections)?; + self.set_service_data_16bit_uuid(service_data) + } + }; + } + + Ok(()) + } +} + +/// Parse the advertisement's service data. +/// Further Reading: +/// * `BleMedium::AdvertisementReceivedHandler` under +/// github.com/google/nearby/internal/platform/implementation/windows_ble/ble_medium.cc. +/// * Bluetooth Supplement to the Core Specification, Part A, Section 1.11. +/// * go/fast_pair_windows_data_parse. +#[inline] +fn parse_service_data_16bit_uuid( + raw_data_sections: IVectorView, +) -> Result>, BluetoothError> { + let mut data_vec = Vec::new(); + + for raw_data in raw_data_sections { + let data_reader = DataReader::FromBuffer(&raw_data.Data()?)?; + let uuid = data_reader.ReadUInt16()?; + + let unconsumed_buffer_len = + data_reader.UnconsumedBufferLength()? as usize; + + let mut data = vec![0u8; unconsumed_buffer_len]; + data_reader.ReadBytes(&mut data)?; + + data_vec.push(ServiceData::new(uuid, data)); + } + + Ok(data_vec) +} diff --git a/fastpair/rust/src/bluetooth/windows/device.rs b/fastpair/rust/src/bluetooth/windows/device.rs index a1551211..6c3cc742 100644 --- a/fastpair/rust/src/bluetooth/windows/device.rs +++ b/fastpair/rust/src/bluetooth/windows/device.rs @@ -49,13 +49,12 @@ use windows::{ Foundation::TypedEventHandler, }; -use crate::bluetooth::{common::{BleAddress, ClassicAddress, Device, ServiceData, BluetoothError, PairingResult}, BleAdapter}; +use crate::bluetooth::common::{BleAddress, ClassicAddress, Device, BluetoothError, PairingResult}; /// Concrete type implementing `Device`, used for Windows BLE. pub struct BleDevice { inner: BluetoothLEDevice, addr: BleAddress, - service_data: Vec> } /// Concrete type implementing `Device`, used for Windows Bluetooth Classic. @@ -66,7 +65,7 @@ pub struct ClassicDevice { impl BleDevice { /// `BleDevice` constructor. - pub async fn new(addr: BleAddress, service_data: Vec>) -> Result { + pub async fn new(addr: BleAddress) -> Result { let kind = BluetoothAddressType::from(addr.get_kind()); let raw_addr = u64::from(addr); @@ -75,7 +74,7 @@ impl BleDevice { )? .await?; - Ok(BleDevice { inner, addr, service_data }) + Ok(BleDevice { inner, addr }) } } @@ -98,10 +97,6 @@ impl Device for BleDevice { // `ClassicDevice::pair` directly. unimplemented!("BLE Pairing is currently unsupported.") } - - fn service_data(&self) -> &Vec> { - &self.service_data - } } @@ -178,10 +173,6 @@ impl Device for ClassicDevice { } } } - - fn service_data(&self) -> &Vec> { - unimplemented!("Service data is currently unsupported for Classic devices.") - } } mod tests { diff --git a/fastpair/rust/src/bluetooth/windows/mod.rs b/fastpair/rust/src/bluetooth/windows/mod.rs index f23035c5..8003fe00 100644 --- a/fastpair/rust/src/bluetooth/windows/mod.rs +++ b/fastpair/rust/src/bluetooth/windows/mod.rs @@ -15,9 +15,11 @@ /// Bluetooth LE module for Windows devices. mod adapter; mod address; +mod advertisement; mod device; mod error; pub use adapter::*; pub use address::*; +pub use advertisement::*; pub use device::*; diff --git a/fastpair/rust/src/main.rs b/fastpair/rust/src/main.rs index ab41a698..8c45fa9a 100644 --- a/fastpair/rust/src/main.rs +++ b/fastpair/rust/src/main.rs @@ -27,10 +27,12 @@ use futures::{ mod bluetooth; -use bluetooth::{Adapter, BleAdapter, ClassicAddress, Device}; +use bluetooth::{Adapter, BleDevice, ClassicAddress, Device}; + +use crate::bluetooth::BleDataTypeId; async fn get_user_input( - device_vec: Arc::Device>>>, + device_vec: Arc>>, ) -> Result<(), Box> { let mut buffer = String::new(); loop { @@ -83,15 +85,18 @@ fn main() -> Result<(), Box> { } let mut counter: u32 = 0; - + let datatype_selector = vec![BleDataTypeId::ServiceData16BitUuid]; // Retrieve incoming device advertisements. - while let Ok(ble_device) = adapter.next_device().await { - for service_data in ble_device.service_data() { + while let Ok(advertisement) = + adapter.next_advertisement(Some(&datatype_selector)).await + { + for service_data in advertisement.service_data_16bit_uuid()? { let uuid = service_data.uuid(); // This is a Fast Pair device. if uuid == 0x2cfe { - let addr = ble_device.address(); + let addr = advertisement.address(); + let ble_device = BleDevice::new(addr).await?; let name = ble_device.name()?; if addr_set.insert(addr) { From e73a9bb9a8796bc155342615bf7c6acb3f0739a8 Mon Sep 17 00:00:00 2001 From: Lucas Silva Shepard Date: Fri, 28 Jul 2023 14:58:15 -0700 Subject: [PATCH 11/11] [fp-rs] Updated API for accessing platform-specific structs. Previously, `Device` trait was implemented by both Classic and BLE Devices. This forced usage of associated types for choosing the type of address employed, bringing about limitations of Rust's type system. By ensuring cross-platform traits are only used for shared cross-platform behavior, there are no longer type annotation issues in the application layer. Changes: * Split the `Device` trait into `ClassicDevice` and `BleDevice`. * Migrated these to an `api/` module to avoid name clashes and clearly differentiate common structs (under `common/) from common traits (under `api/)`. * Removed shared `Address` enum in order to get rid of device's address associated type. * Application layer can now only talk to impl traits rather than the concrete platform-specific type. This ensures compile-time behavior guarantees. Constructors for cross-platform impls provided under the `Platform` unit struct. * Updated `unsupported` module for unsupported platforms. Previously, dummy inherent impls would've been necessary for calling the `new()` method. This is now part of the `ClassicDevice` and `BleDevice` API, so cross-platform behavior is guaranteed to work. --- .../src/bluetooth/{common => api}/adapter.rs | 6 +- fastpair/rust/src/bluetooth/api/device.rs | 56 +++++++++++++++++++ fastpair/rust/src/bluetooth/api/mod.rs | 5 ++ fastpair/rust/src/bluetooth/common/device.rs | 34 ----------- fastpair/rust/src/bluetooth/common/mod.rs | 4 -- fastpair/rust/src/bluetooth/mod.rs | 28 ++++++++-- .../rust/src/bluetooth/unsupported/adapter.rs | 15 +++-- .../rust/src/bluetooth/unsupported/device.rs | 45 +++++++++++++-- .../rust/src/bluetooth/windows/adapter.rs | 9 +-- fastpair/rust/src/bluetooth/windows/device.rs | 37 +++--------- fastpair/rust/src/main.rs | 15 ++--- 11 files changed, 160 insertions(+), 94 deletions(-) rename fastpair/rust/src/bluetooth/{common => api}/adapter.rs (91%) create mode 100644 fastpair/rust/src/bluetooth/api/device.rs create mode 100644 fastpair/rust/src/bluetooth/api/mod.rs delete mode 100644 fastpair/rust/src/bluetooth/common/device.rs diff --git a/fastpair/rust/src/bluetooth/common/adapter.rs b/fastpair/rust/src/bluetooth/api/adapter.rs similarity index 91% rename from fastpair/rust/src/bluetooth/common/adapter.rs rename to fastpair/rust/src/bluetooth/api/adapter.rs index f6dda1b9..39ab9310 100644 --- a/fastpair/rust/src/bluetooth/common/adapter.rs +++ b/fastpair/rust/src/bluetooth/api/adapter.rs @@ -14,12 +14,14 @@ use async_trait::async_trait; -use super::{BleAdvertisement, BleDataTypeId, BluetoothError}; +use crate::bluetooth::common::{ + BleAdvertisement, BleDataTypeId, BluetoothError, +}; /// 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 { +pub trait BleAdapter: Sized { /// Retrieve the system-default Bluetooth adapter. async fn default() -> Result; diff --git a/fastpair/rust/src/bluetooth/api/device.rs b/fastpair/rust/src/bluetooth/api/device.rs new file mode 100644 index 00000000..97cba1d8 --- /dev/null +++ b/fastpair/rust/src/bluetooth/api/device.rs @@ -0,0 +1,56 @@ +// 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 crate::bluetooth::common::{ + BleAddress, BluetoothError, ClassicAddress, PairingResult, +}; + +/// Concrete types implementing this trait represent BLE Peripheral devices. +/// They provide methods for retrieving device info and running device actions, +/// such as pairing. +#[async_trait] +pub trait BleDevice: Sized { + /// Create a new `BleDevice` instance from a `BleAddress`, typically + /// enabled through locally cached data retrieved from a Bluetooth adapter's + /// scanning functionality. + async fn new(addr: BleAddress) -> Result; + + /// Retrieve the name advertised by this device. + fn name(&self) -> Result; + + /// Retrieve this device's Bluetooth address information. + fn address(&self) -> BleAddress; +} + +/// Concrete types implementing this trait represent BT Classic Peripheral +/// devices. They provide methods for retrieving device info and running device +/// actions, such as pairing. +#[async_trait] +pub trait ClassicDevice: Sized { + /// Create a new `ClassicDevice` instance from a `ClassicAddress`, typically + /// enabled through locally cached data retrieved from a Bluetooth adapter's + /// scanning functionality. + async fn new(addr: ClassicAddress) -> Result; + + /// Retrieve the name advertised by this device. + fn name(&self) -> Result; + + /// Retrieve this device's Bluetooth address information. + fn address(&self) -> ClassicAddress; + + /// Attempt pairing with the peripheral device. + async fn pair(&self) -> Result; +} diff --git a/fastpair/rust/src/bluetooth/api/mod.rs b/fastpair/rust/src/bluetooth/api/mod.rs new file mode 100644 index 00000000..0e410749 --- /dev/null +++ b/fastpair/rust/src/bluetooth/api/mod.rs @@ -0,0 +1,5 @@ +mod adapter; +mod device; + +pub use adapter::*; +pub use device::*; diff --git a/fastpair/rust/src/bluetooth/common/device.rs b/fastpair/rust/src/bluetooth/common/device.rs deleted file mode 100644 index 1f861155..00000000 --- a/fastpair/rust/src/bluetooth/common/device.rs +++ /dev/null @@ -1,34 +0,0 @@ -// 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 super::{BluetoothError, PairingResult}; - -/// Concrete types implementing this trait represent Bluetooth Peripheral devices. -/// They provide methods for retrieving device info and running device actions, -/// such as pairing. -#[async_trait] -pub trait Device: Sized { - type Address; - - /// Retrieve the name advertised by this device. - fn name(&self) -> Result; - - /// Retrieve this device's Bluetooth address information. - fn address(&self) -> Self::Address; - - /// Attempt pairing with the peripheral device. - async fn pair(&self) -> Result; -} diff --git a/fastpair/rust/src/bluetooth/common/mod.rs b/fastpair/rust/src/bluetooth/common/mod.rs index 13b05114..1ddd9b30 100644 --- a/fastpair/rust/src/bluetooth/common/mod.rs +++ b/fastpair/rust/src/bluetooth/common/mod.rs @@ -13,14 +13,10 @@ // limitations under the License. /// Module for shared functionality between all Bluetooth platforms. -mod adapter; mod address; mod advertisement; -mod device; mod error; -pub use adapter::*; pub use address::*; pub use advertisement::*; -pub use device::*; pub use error::*; diff --git a/fastpair/rust/src/bluetooth/mod.rs b/fastpair/rust/src/bluetooth/mod.rs index 979a27c7..0acdb250 100644 --- a/fastpair/rust/src/bluetooth/mod.rs +++ b/fastpair/rust/src/bluetooth/mod.rs @@ -16,11 +16,12 @@ // instead of using anyhow. // b/290070686 +pub mod api; pub mod common; +pub use api::{BleAdapter, BleDevice, ClassicDevice}; pub use common::{ - Adapter, BleAdvertisement, BleDataTypeId, BluetoothError, ClassicAddress, - Device, + BleAddress, BleAdvertisement, BleDataTypeId, BluetoothError, ClassicAddress, }; cfg_if::cfg_if! { @@ -33,6 +34,23 @@ cfg_if::cfg_if! { } } -pub type BleAdapter = platform::BleAdapter; -pub type BleDevice = platform::BleDevice; -pub type ClassicDevice = platform::ClassicDevice; +pub struct Platform; + +impl Platform { + pub async fn default_adapter( + ) -> Result { + platform::BleAdapter::default().await + } + + pub async fn new_ble_device( + addr: BleAddress, + ) -> Result { + platform::BleDevice::new(addr).await + } + + pub async fn new_classic_device( + addr: ClassicAddress, + ) -> Result { + platform::ClassicDevice::new(addr).await + } +} diff --git a/fastpair/rust/src/bluetooth/unsupported/adapter.rs b/fastpair/rust/src/bluetooth/unsupported/adapter.rs index 2794b172..de883e3c 100644 --- a/fastpair/rust/src/bluetooth/unsupported/adapter.rs +++ b/fastpair/rust/src/bluetooth/unsupported/adapter.rs @@ -15,16 +15,16 @@ use async_trait::async_trait; use super::BleDevice; -use crate::bluetooth::common::{Adapter, BluetoothError}; +use crate::bluetooth::{ + api, common::BluetoothError, BleAdvertisement, BleDataTypeId, +}; /// Concrete type implementing `Adapter`, used for unsupported devices. /// Every method should panic. pub struct BleAdapter; #[async_trait] -impl Adapter for BleAdapter { - type Device = BleDevice; - +impl api::BleAdapter for BleAdapter { async fn default() -> Result { panic!("Unsupported target platform."); } @@ -37,8 +37,11 @@ impl Adapter for BleAdapter { panic!("Unsupported target platform."); } - async fn next_device(&mut self) -> Result { - panic!("Unsupported target platform."); + async fn next_advertisement( + &mut self, + datatype_selector: Option<&Vec>, + ) -> Result { + panic!("Unsupported target platform"); } } diff --git a/fastpair/rust/src/bluetooth/unsupported/device.rs b/fastpair/rust/src/bluetooth/unsupported/device.rs index d18d1e34..0ac68775 100644 --- a/fastpair/rust/src/bluetooth/unsupported/device.rs +++ b/fastpair/rust/src/bluetooth/unsupported/device.rs @@ -12,15 +12,52 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::bluetooth::common::{BluetoothError, Device}; +use async_trait::async_trait; -/// Concrete type implementing `Device`, used for unsupported devices. +use crate::bluetooth::{ + api, + common::{BleAddress, BluetoothError, ClassicAddress, PairingResult}, +}; + +/// Concrete type implementing `api::BleDevice` for unsupported platforms. /// Every method should panic. pub struct BleDevice; -impl Device for BleDevice { +#[async_trait] +impl api::BleDevice for BleDevice { + async fn new(addr: BleAddress) -> Result { + panic!("Unsupported target platform."); + } + fn name(&self) -> Result { - panic!("Unsupported target platform.") + panic!("Unsupported target platform."); + } + + fn address(&self) -> BleAddress { + panic!("Unsupported target platform."); + } +} + +/// Concrete type implementing `api::ClassicDevice` for unsupported platforms. +/// Every method should panic. +pub struct ClassicDevice; + +#[async_trait] +impl api::ClassicDevice for ClassicDevice { + async fn new(addr: ClassicAddress) -> Result { + panic!("Unsupported target platform."); + } + + fn name(&self) -> Result { + panic!("Unsupported target platform."); + } + + fn address(&self) -> ClassicAddress { + panic!("Unsupported target platform."); + } + + async fn pair(&self) -> Result { + panic!("Unsupported target platform."); } } diff --git a/fastpair/rust/src/bluetooth/windows/adapter.rs b/fastpair/rust/src/bluetooth/windows/adapter.rs index 2eb3ded2..cf4519f6 100644 --- a/fastpair/rust/src/bluetooth/windows/adapter.rs +++ b/fastpair/rust/src/bluetooth/windows/adapter.rs @@ -53,8 +53,9 @@ use windows::{ Foundation::TypedEventHandler, }; -use crate::bluetooth::common::{ - Adapter, BleAdvertisement, BleDataTypeId, BluetoothError, +use crate::bluetooth::{ + api, + common::{BleAdvertisement, BleDataTypeId, BluetoothError}, }; /// Struct holding the necessary fields for listening to and handling incoming @@ -66,14 +67,14 @@ struct AdvListener { receiver: Receiver, } -/// Concrete type implementing `Adapter`, used for Windows BLE. +/// Concrete type implementing `api::BleAdapter`, used for Windows BLE. pub struct BleAdapter { inner: BluetoothAdapter, listener: Option, } #[async_trait] -impl Adapter for BleAdapter { +impl api::BleAdapter for BleAdapter { async fn default() -> Result { let inner = BluetoothAdapter::GetDefaultAsync()?.await?; diff --git a/fastpair/rust/src/bluetooth/windows/device.rs b/fastpair/rust/src/bluetooth/windows/device.rs index 6c3cc742..cf0e293f 100644 --- a/fastpair/rust/src/bluetooth/windows/device.rs +++ b/fastpair/rust/src/bluetooth/windows/device.rs @@ -49,7 +49,7 @@ use windows::{ Foundation::TypedEventHandler, }; -use crate::bluetooth::common::{BleAddress, ClassicAddress, Device, BluetoothError, PairingResult}; +use crate::bluetooth::{api, common::{BleAddress, ClassicAddress, BluetoothError, PairingResult}}; /// Concrete type implementing `Device`, used for Windows BLE. pub struct BleDevice { @@ -63,9 +63,9 @@ pub struct ClassicDevice { addr: ClassicAddress, } -impl BleDevice { - /// `BleDevice` constructor. - pub async fn new(addr: BleAddress) -> Result { +#[async_trait] +impl api::BleDevice for BleDevice { + async fn new(addr: BleAddress) -> Result { let kind = BluetoothAddressType::from(addr.get_kind()); let raw_addr = u64::from(addr); @@ -76,33 +76,19 @@ impl BleDevice { Ok(BleDevice { inner, addr }) } -} - -#[async_trait] -impl Device for BleDevice { - type Address = BleAddress; fn name(&self) -> Result { Ok(self.inner.Name()?.to_string()) } - fn address(&self) -> Self::Address { + fn address(&self) -> BleAddress { self.addr } - - async fn pair(&self) -> Result { - // BLE Audio isn't supported on Windows natively, so devices can pair - // but don't playback. Might possibly work with UWP. Since the Classic - // and BLE APIs are very similar, it might be possible to copy-paste - // `ClassicDevice::pair` directly. - unimplemented!("BLE Pairing is currently unsupported.") - } } - -impl ClassicDevice { - /// `ClassicDevice` constructor. - pub async fn new(addr: ClassicAddress) -> Result { +#[async_trait] +impl api::ClassicDevice for ClassicDevice { + async fn new(addr: ClassicAddress) -> Result { let raw_addr = u64::from(addr); let inner = BluetoothDevice::FromBluetoothAddressAsync( @@ -112,17 +98,12 @@ impl ClassicDevice { Ok(ClassicDevice { inner, addr }) } -} - -#[async_trait] -impl Device for ClassicDevice { - type Address = ClassicAddress; fn name(&self) -> Result { Ok(self.inner.Name()?.to_string_lossy()) } - fn address(&self) -> Self::Address { + fn address(&self) -> ClassicAddress { self.addr } diff --git a/fastpair/rust/src/main.rs b/fastpair/rust/src/main.rs index 8c45fa9a..19d77c00 100644 --- a/fastpair/rust/src/main.rs +++ b/fastpair/rust/src/main.rs @@ -27,12 +27,13 @@ use futures::{ mod bluetooth; -use bluetooth::{Adapter, BleDevice, ClassicAddress, Device}; - -use crate::bluetooth::BleDataTypeId; +use crate::bluetooth::{ + BleAdapter, BleDataTypeId, BleDevice, ClassicAddress, ClassicDevice, + Platform, +}; async fn get_user_input( - device_vec: Arc>>, + device_vec: Arc>>, ) -> Result<(), Box> { let mut buffer = String::new(); loop { @@ -55,7 +56,7 @@ async fn get_user_input( let classic_addr = ClassicAddress::try_from(addr)?; let classic_device = - bluetooth::ClassicDevice::new(classic_addr).await?; + Platform::new_classic_device(classic_addr).await?; match classic_device.pair().await { Ok(_) => { @@ -72,7 +73,7 @@ async fn get_user_input( fn main() -> Result<(), Box> { let run = async { - let mut adapter = bluetooth::BleAdapter::default().await?; + let mut adapter = Platform::default_adapter().await?; adapter.start_scan()?; let mut addr_set = HashSet::new(); @@ -96,7 +97,7 @@ fn main() -> Result<(), Box> { // This is a Fast Pair device. if uuid == 0x2cfe { let addr = advertisement.address(); - let ble_device = BleDevice::new(addr).await?; + let ble_device = Platform::new_ble_device(addr).await?; let name = ble_device.name()?; if addr_set.insert(addr) {