Merge pull request #1957 from TheShepord:fpwinrs_pair

PiperOrigin-RevId: 552656377
This commit is contained in:
Copybara-Service
2023-07-31 19:20:22 -07:00
19 changed files with 973 additions and 171 deletions
+4 -1
View File
@@ -20,15 +20,18 @@ 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 = [
"Devices_Bluetooth",
"Devices_Enumeration",
"Devices_Bluetooth_Advertisement",
"Foundation",
"Foundation_Collections",
"Storage_Streams",
] }
@@ -14,29 +14,26 @@
use async_trait::async_trait;
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 {
type Device: Device;
pub trait BleAdapter: Sized {
/// Retrieve the system-default Bluetooth adapter.
async fn default() -> Result<Self, anyhow::Error>;
async fn default() -> Result<Self, BluetoothError>;
/// Begin scanning for nearby devices.
fn start_scan_devices(&mut self) -> Result<(), anyhow::Error>;
/// Begin scanning for nearby advertisements.
fn start_scan(&mut self) -> Result<(), BluetoothError>;
/// Stop scanning for nearby devices.
fn stop_scan_devices(&mut self) -> Result<(), anyhow::Error>;
/// Stop scanning for nearby advertisements.
fn stop_scan(&mut self) -> Result<(), BluetoothError>;
/// Poll next discovered device.
async fn next_device(&mut self) -> Result<Self::Device, anyhow::Error>;
}
/// 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<String, anyhow::Error>;
async fn next_advertisement(
&mut self,
data_selector: Option<&Vec<BleDataTypeId>>,
) -> Result<BleAdvertisement, BluetoothError>;
}
+56
View File
@@ -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<Self, BluetoothError>;
/// Retrieve the name advertised by this device.
fn name(&self) -> Result<String, BluetoothError>;
/// 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<Self, BluetoothError>;
/// Retrieve the name advertised by this device.
fn name(&self) -> Result<String, BluetoothError>;
/// Retrieve this device's Bluetooth address information.
fn address(&self) -> ClassicAddress;
/// Attempt pairing with the peripheral device.
async fn pair(&self) -> Result<PairingResult, BluetoothError>;
}
+5
View File
@@ -0,0 +1,5 @@
mod adapter;
mod device;
pub use adapter::*;
pub use device::*;
@@ -0,0 +1,95 @@
// 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;
/// BLE Addresses can either be the peripheral's public MAC address, or various
/// types of random addresses.
#[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, Hash)]
pub struct BleAddress {
val: [u8; 6],
kind: BleAddressKind,
}
/// Struct representing a 48-bit BT Classic address.
#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)]
pub struct ClassicAddress([u8; 6]);
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<u64> for ClassicAddress {
fn from(addr: u64) -> Self {
let addr = u64_to_6lsb(addr);
ClassicAddress(addr)
}
}
impl TryFrom<BleAddress> for ClassicAddress {
type Error = BluetoothError;
fn try_from(addr: BleAddress) -> Result<Self, Self::Error> {
match addr.kind {
BleAddressKind::Public => Ok(ClassicAddress(addr.val)),
BleAddressKind::Random => Err(BluetoothError::BadTypeConversion(String::from(
"can't convert BLE Random address to Bluetooth Classic address."
))),
}
}
}
impl From<BleAddress> 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<ClassicAddress> for u64 {
fn from(addr: ClassicAddress) -> Self {
let mut bytes = [0u8; 8];
bytes[..6].copy_from_slice(&addr.0);
u64::from_le_bytes(bytes)
}
}
@@ -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<Vec<ServiceData<u16>>>,
}
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<ServiceData<u16>>,
) {
self.service_data_16bit_uuid = Some(data_sections);
}
/// Getter for `ServiceData` field with 16bit UUID.
pub fn service_data_16bit_uuid(
&self,
) -> Result<&Vec<ServiceData<u16>>, 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<U: Copy> {
uuid: U,
data: Vec<u8>,
}
impl<U: Copy> ServiceData<U> {
pub fn new(uuid: U, data: Vec<u8>) -> Self {
ServiceData { uuid, data }
}
pub fn uuid(&self) -> U {
self.uuid
}
pub fn data(&self) -> &Vec<u8> {
&self.data
}
}
@@ -0,0 +1,60 @@
// 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 {
/// 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),
/// 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
#[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),
}
/// 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),
}
+22
View File
@@ -0,0 +1,22 @@
// 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 address;
mod advertisement;
mod error;
pub use address::*;
pub use advertisement::*;
pub use error::*;
+27 -6
View File
@@ -16,20 +16,41 @@
// instead of using anyhow.
// b/290070686
pub mod api;
pub mod common;
pub use common::{Adapter, Device};
pub use api::{BleAdapter, BleDevice, ClassicDevice};
pub use common::{
BleAddress, BleAdvertisement, BleDataTypeId, BluetoothError, ClassicAddress,
};
cfg_if::cfg_if! {
if #[cfg(windows)] {
mod windows_ble;
use windows_ble::BleAdapter;
mod windows;
use self::windows as platform;
} else {
mod unsupported;
use unsupported::BleAdapter;
use unsupported as platform;
}
}
pub async fn default_adapter() -> Result<impl Adapter, anyhow::Error> {
BleAdapter::default().await
pub struct Platform;
impl Platform {
pub async fn default_adapter(
) -> Result<impl api::BleAdapter, BluetoothError> {
platform::BleAdapter::default().await
}
pub async fn new_ble_device(
addr: BleAddress,
) -> Result<impl api::BleDevice, BluetoothError> {
platform::BleDevice::new(addr).await
}
pub async fn new_classic_device(
addr: ClassicAddress,
) -> Result<impl api::ClassicDevice, BluetoothError> {
platform::ClassicDevice::new(addr).await
}
}
@@ -15,30 +15,33 @@
use async_trait::async_trait;
use super::BleDevice;
use crate::bluetooth::common::Adapter;
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;
async fn default() -> Result<Self, anyhow::Error> {
impl api::BleAdapter for BleAdapter {
async fn default() -> Result<Self, BluetoothError> {
panic!("Unsupported target platform.");
}
fn start_scan_devices(&mut self) -> Result<(), anyhow::Error> {
fn start_scan(&mut self) -> Result<(), BluetoothError> {
panic!("Unsupported target platform.");
}
fn stop_scan_devices(&mut self) -> Result<(), anyhow::Error> {
fn stop_scan(&mut self) -> Result<(), BluetoothError> {
panic!("Unsupported target platform.");
}
async fn next_device(&mut self) -> Result<Self::Device, anyhow::Error> {
panic!("Unsupported target platform.");
async fn next_advertisement(
&mut self,
datatype_selector: Option<&Vec<BleDataTypeId>>,
) -> Result<BleAdvertisement, BluetoothError> {
panic!("Unsupported target platform");
}
}
@@ -12,15 +12,52 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::bluetooth::common::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 {
fn name(&self) -> Result<String, anyhow::Error> {
panic!("Unsupported target platform.")
#[async_trait]
impl api::BleDevice for BleDevice {
async fn new(addr: BleAddress) -> Result<Self, BluetoothError> {
panic!("Unsupported target platform.");
}
fn name(&self) -> Result<String, BluetoothError> {
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<Self, BluetoothError> {
panic!("Unsupported target platform.");
}
fn name(&self) -> Result<String, BluetoothError> {
panic!("Unsupported target platform.");
}
fn address(&self) -> ClassicAddress {
panic!("Unsupported target platform.");
}
async fn pair(&self) -> Result<PairingResult, BluetoothError> {
panic!("Unsupported target platform.");
}
}
@@ -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::{
@@ -54,44 +53,49 @@ use windows::{
Foundation::TypedEventHandler,
};
use super::BleDevice;
use crate::bluetooth::common::Adapter;
use crate::bluetooth::{
api,
common::{BleAdvertisement, BleDataTypeId, BluetoothError},
};
/// Concrete type implementing `Adapter`, used for Windows BLE.
/// 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<BluetoothLEAdvertisementReceivedEventArgs>,
}
/// Concrete type implementing `api::BleAdapter`, 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<Pin<Box<dyn Stream<Item = BleDevice> + Send + Sync>>>,
listener: Option<AdvListener>,
}
#[async_trait]
impl Adapter for BleAdapter {
type Device = BleDevice;
async fn default() -> Result<Self, anyhow::Error> {
impl api::BleAdapter for BleAdapter {
async fn default() -> Result<Self, BluetoothError> {
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 {
inner,
device_stream: None,
listener: None,
})
}
fn start_scan_devices(&mut self) -> Result<(), anyhow::Error> {
fn start_scan(&mut self) -> Result<(), BluetoothError> {
let watcher = BluetoothLEAdvertisementWatcher::new()?;
match watcher.SetScanningMode(BluetoothLEScanningMode::Active) {
Ok(_) => (),
@@ -149,7 +153,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,59 +162,58 @@ 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 addr = event_args.BluetoothAddress().ok()?;
let kind = event_args.BluetoothAddressType().ok()?;
match BleDevice::from_addr(addr, kind).await {
Ok(device) => Some(device),
Err(err) => {
warn!("Error creating device: {:?}", err);
None
}
}
}
}
}
})));
self.listener = Some(AdvListener { watcher, receiver });
Ok(())
}
fn stop_scan_devices(&mut self) -> Result<(), anyhow::Error> {
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(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<Self::Device, anyhow::Error> {
if let Some(stream) = &mut self.device_stream {
stream
.next()
.await
.ok_or(anyhow::anyhow!("Device returned from stream is None."))
async fn next_advertisement(
&mut self,
datatype_selector: Option<&Vec<BleDataTypeId>>,
) -> Result<BleAdvertisement, BluetoothError> {
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 mut advertisement =
BleAdvertisement::try_from(&event_args)?;
if let Some(datatype_selector) = datatype_selector {
advertisement
.load_data(&event_args, datatype_selector)?;
}
break Ok(advertisement);
}
}
}
} else {
Err(anyhow::anyhow!("Device scanning hasn't started."))
Err(BluetoothError::FailedPrecondition(String::from(
"device scanning hasn't started, please call `start_scan()`",
)))
}
}
}
@@ -0,0 +1,52 @@
// 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, BluetoothError};
// Convenience for converting from Windows API to crate API.
impl TryFrom<BluetoothAddressType> for BleAddressKind {
type Error = BluetoothError;
fn try_from(kind: BluetoothAddressType) -> Result<Self, Self::Error> {
match kind {
BluetoothAddressType::Public => Ok(BleAddressKind::Public),
BluetoothAddressType::Random => Ok(BleAddressKind::Random),
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,
))),
}
}
}
// Convenience for converting from crate API to Windows API.
impl From<BleAddressKind> for BluetoothAddressType {
fn from(kind: BleAddressKind) -> Self {
match kind {
BleAddressKind::Public => BluetoothAddressType::Public,
BleAddressKind::Random => BluetoothAddressType::Random,
}
}
}
@@ -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<Self, Self::Error> {
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<BluetoothLEAdvertisementDataSection>,
) -> Result<Vec<ServiceData<u16>>, 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)
}
@@ -0,0 +1,163 @@
// 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 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::{api, common::{BleAddress, ClassicAddress, 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,
}
#[async_trait]
impl api::BleDevice for BleDevice {
async fn new(addr: BleAddress) -> Result<Self, BluetoothError> {
let kind = BluetoothAddressType::from(addr.get_kind());
let raw_addr = u64::from(addr);
let inner = BluetoothLEDevice::FromBluetoothAddressWithBluetoothAddressTypeAsync(
raw_addr, kind,
)?
.await?;
Ok(BleDevice { inner, addr })
}
fn name(&self) -> Result<String, BluetoothError> {
Ok(self.inner.Name()?.to_string())
}
fn address(&self) -> BleAddress {
self.addr
}
}
#[async_trait]
impl api::ClassicDevice for ClassicDevice {
async fn new(addr: ClassicAddress) -> Result<Self, BluetoothError> {
let raw_addr = u64::from(addr);
let inner = BluetoothDevice::FromBluetoothAddressAsync(
raw_addr,
)?
.await?;
Ok(ClassicDevice { inner, addr })
}
fn name(&self) -> Result<String, BluetoothError> {
Ok(self.inner.Name()?.to_string_lossy())
}
fn address(&self) -> ClassicAddress {
self.addr
}
async fn pair(&self) -> Result<PairingResult, BluetoothError> {
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<DeviceInformationCustomPairing>,
event_args: &Option<DevicePairingRequestedEventArgs>,
| {
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 {
use super::*;
// TODO b/288592509 unit tests
}
@@ -0,0 +1,57 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use windows::Devices::Enumeration::DevicePairingResultStatus;
use crate::bluetooth::common::{BluetoothError, PairingResult};
impl From<windows::core::Error> 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<DevicePairingResultStatus> 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.")),
}
}
}
@@ -14,7 +14,12 @@
/// 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::*;
@@ -1,58 +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 windows::Devices::Bluetooth::{
// Enum describing the type of address (public, random, unspecified).
// https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.bluetoothaddresstype?view=winrt-22621
BluetoothAddressType,
// Struct for interacting with and pairing to a discovered BLE device.
// https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.bluetoothledevice?view=winrt-22621
BluetoothLEDevice,
};
use crate::bluetooth::common::Device;
/// Concrete type implementing `Device`, used for Windows BLE.
pub struct BleDevice {
inner: BluetoothLEDevice,
}
impl BleDevice {
/// Create a `BleDevice` instance from the raw bluetooth address information.
pub(super) async fn from_addr(
addr: u64,
kind: BluetoothAddressType,
) -> Result<Self, anyhow::Error> {
let inner =
BluetoothLEDevice::FromBluetoothAddressWithBluetoothAddressTypeAsync(addr, kind)?
.await?;
Ok(BleDevice { inner })
}
}
#[async_trait]
impl Device for BleDevice {
fn name(&self) -> Result<String, anyhow::Error> {
Ok(self.inner.Name()?.to_string_lossy())
}
}
mod tests {
use super::*;
// TODO b/288592509 unit tests
}
+92 -8
View File
@@ -12,22 +12,106 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use futures::executor;
use std::{
collections::HashSet,
error::Error,
io::{self, Write},
sync::Arc,
thread,
};
use futures::{
executor::{self, block_on},
lock::Mutex,
};
mod bluetooth;
use bluetooth::{Adapter, Device};
use crate::bluetooth::{
BleAdapter, BleDataTypeId, BleDevice, ClassicAddress, ClassicDevice,
Platform,
};
fn main() -> Result<(), anyhow::Error> {
async fn get_user_input(
device_vec: Arc<Mutex<Vec<impl BleDevice>>>,
) -> Result<(), Box<dyn Error>> {
let mut buffer = String::new();
loop {
io::stdout().flush()?;
buffer.clear();
io::stdin().read_line(&mut buffer)?;
let val = match buffer.trim().parse::<usize>() {
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 = device.address();
let classic_addr = ClassicAddress::try_from(addr)?;
let classic_device =
Platform::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<dyn Error>> {
let run = async {
let mut adapter = bluetooth::default_adapter().await?;
adapter.start_scan_devices()?;
let mut adapter = Platform::default_adapter().await?;
adapter.start_scan()?;
while let Ok(device) = adapter.next_device().await {
println!("found {}", 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());
}
unreachable!("Done scanning");
let mut counter: u32 = 0;
let datatype_selector = vec![BleDataTypeId::ServiceData16BitUuid];
// Retrieve incoming device advertisements.
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 = advertisement.address();
let ble_device = Platform::new_ble_device(addr).await?;
let name = ble_device.name()?;
if addr_set.insert(addr) {
// New FP device discovered.
println!("{}: {}", counter, name);
device_vec.lock().await.push(ble_device);
counter += 1;
}
break;
}
}
}
println!("Done scanning");
Ok(())
};
executor::block_on(run)