diff options
author | Martin Robinson <mrobinson@igalia.com> | 2024-01-09 10:13:41 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-01-09 09:13:41 +0000 |
commit | 6a804cd775e3976103269ac54ae997a3accc8618 (patch) | |
tree | 1a125d1901e5aa3cc98a1f5beabebd927a3b9472 | |
parent | fddc4a430fca591152c69f0793ab946dcdc81617 (diff) | |
download | servo-6a804cd775e3976103269ac54ae997a3accc8618.tar.gz servo-6a804cd775e3976103269ac54ae997a3accc8618.zip |
Integrate the `devices` respository (#30974)
Despite the name of this dependency, it only handles bluetooth. Because
it's a separate repository. Integrating it, allows changes here to be
tested more consistently. In addition, it's likely that new bluetooth
libraries will allow removing the majority of the platform-specific code
in this directory.
This is based on the version of this dependency from:
https://github.com/servo/devices/pull/34
23 files changed, 3880 insertions, 27 deletions
diff --git a/Cargo.lock b/Cargo.lock index 4a276e5888a..f334c6d3a52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -477,7 +477,10 @@ version = "0.0.1" dependencies = [ "bitflags 2.4.1", "bluetooth_traits", - "device", + "blurdroid", + "blurmac", + "blurmock", + "blurz", "embedder_traits", "ipc-channel", "log", @@ -505,7 +508,6 @@ checksum = "19b23557dd27704797128f9db2816416bef20dad62d4a9768714eeb65f07d296" [[package]] name = "blurmac" version = "0.1.0" -source = "git+https://github.com/servo/devices?rev=cb28c4725ffbfece99dab842d17d3e8c50774778#cb28c4725ffbfece99dab842d17d3e8c50774778" dependencies = [ "log", "objc", @@ -1323,17 +1325,6 @@ dependencies = [ ] [[package]] -name = "device" -version = "0.0.1" -source = "git+https://github.com/servo/devices?rev=cb28c4725ffbfece99dab842d17d3e8c50774778#cb28c4725ffbfece99dab842d17d3e8c50774778" -dependencies = [ - "blurdroid", - "blurmac", - "blurmock", - "blurz", -] - -[[package]] name = "devtools" version = "0.0.1" dependencies = [ diff --git a/components/bluetooth/Cargo.toml b/components/bluetooth/Cargo.toml index fa2a6feb742..4046b256a1b 100644 --- a/components/bluetooth/Cargo.toml +++ b/components/bluetooth/Cargo.toml @@ -13,7 +13,7 @@ path = "lib.rs" [dependencies] bitflags = { workspace = true } bluetooth_traits = { workspace = true } -device = { git = "https://github.com/servo/devices", features = ["bluetooth-test"], rev = "cb28c4725ffbfece99dab842d17d3e8c50774778" } +blurmock = { version = "0.1.2", optional = true } embedder_traits = { workspace = true } ipc-channel = { workspace = true } log = { workspace = true } @@ -22,4 +22,14 @@ servo_rand = { path = "../rand" } uuid = { workspace = true } [features] -native-bluetooth = ["device/bluetooth"] +native-bluetooth = ["blurz", "blurdroid", "blurmac", "bluetooth-test"] +bluetooth-test = ["blurmock"] + +[target.'cfg(target_os = "linux")'.dependencies] +blurz = { version = "0.3", optional = true } + +[target.'cfg(target_os = "android")'.dependencies] +blurdroid = { version = "0.1.2", optional = true } + +[target.'cfg(target_os = "macos")'.dependencies] +blurmac = { path = "../../third_party/blurmac", optional = true } diff --git a/components/bluetooth/README.md b/components/bluetooth/README.md new file mode 100644 index 00000000000..ec6e536d66a --- /dev/null +++ b/components/bluetooth/README.md @@ -0,0 +1,55 @@ +# Bluetooth Rust lib using macOS CoreBluetooth + +[](https://travis-ci.org/akosthekiss/blurmac) +[](https://crates.io/crates/blurmac) + +The main aim of BlurMac is to enable [WebBluetooth](https://webbluetoothcg.github.io) +in [Servo](https://github.com/servo/servo) on macOS. Thus, API and implementation +decisions are affected by the encapsulating [Devices](https://github.com/servo/devices), +and the sibling [BlurZ](https://github.com/szeged/blurz) and [BlurDroid](https://github.com/szeged/blurdroid) +crates. + + +## Run Servo with WebBluetooth Enabled + +Usually, you don't want to work with BlurMac on its own but use it within Servo. +So, most probably you'll want to run Servo with WebBluetooth enabled: + +``` +RUST_LOG=blurmac \ +./mach run \ + --dev \ + --pref=dom.bluetooth.enabled \ + --pref=dom.permissions.testing.allowed_in_nonsecure_contexts \ + URL +``` + +Notes: +* The above command is actually not really BlurMac-specific (except for the `RUST_LOG` + part). It runs Servo with WBT enabled on any platform where WBT is supported. +* You don't need the `RUST_LOG=blurmac` part if you don't want to see BlurMac debug + messages on the console. +* You don't need the `--dev` part if you want to run a release build. +* You don't need the `--pref=dom.permissions.testing.allowed_in_nonsecure_contexts` + part if your `URL` is https (but you do need it if you test a local file). + + +## Known Issues + +* Device RSSI can not be retrieved yet. +* Support for included services is incomplete. +* Descriptors are not supported yet. +* Notifications on characteristics are not supported yet (the limitation comes from + Devices). + + +## Compatibility + +Tested on: + +* macOS Sierra 10.12. + + +## Copyright and Licensing + +Licensed under the BSD 3-Clause [License](LICENSE.md). diff --git a/components/bluetooth/adapter.rs b/components/bluetooth/adapter.rs new file mode 100644 index 00000000000..b5cd71b4543 --- /dev/null +++ b/components/bluetooth/adapter.rs @@ -0,0 +1,408 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::error::Error; +use std::sync::Arc; + +#[cfg(all(target_os = "android", feature = "bluetooth"))] +use blurdroid::bluetooth_adapter::Adapter as BluetoothAdapterAndroid; +#[cfg(all(target_os = "android", feature = "bluetooth"))] +use blurdroid::bluetooth_device::Device as BluetoothDeviceAndroid; +#[cfg(all(target_os = "android", feature = "bluetooth"))] +use blurdroid::bluetooth_discovery_session::DiscoverySession as BluetoothDiscoverySessionAndroid; +#[cfg(all(target_os = "macos", feature = "bluetooth"))] +use blurmac::BluetoothAdapter as BluetoothAdapterMac; +#[cfg(all(target_os = "macos", feature = "bluetooth"))] +use blurmac::BluetoothDevice as BluetoothDeviceMac; +#[cfg(all(target_os = "macos", feature = "bluetooth"))] +use blurmac::BluetoothDiscoverySession as BluetoothDiscoverySessionMac; +#[cfg(feature = "bluetooth-test")] +use blurmock::fake_adapter::FakeBluetoothAdapter; +#[cfg(feature = "bluetooth-test")] +use blurmock::fake_device::FakeBluetoothDevice; +#[cfg(feature = "bluetooth-test")] +use blurmock::fake_discovery_session::FakeBluetoothDiscoverySession; +#[cfg(all(target_os = "linux", feature = "bluetooth"))] +use blurz::bluetooth_adapter::BluetoothAdapter as BluetoothAdapterBluez; +#[cfg(all(target_os = "linux", feature = "bluetooth"))] +use blurz::bluetooth_device::BluetoothDevice as BluetoothDeviceBluez; +#[cfg(all(target_os = "linux", feature = "bluetooth"))] +use blurz::bluetooth_discovery_session::BluetoothDiscoverySession as BluetoothDiscoverySessionBluez; + +use super::bluetooth::{BluetoothDevice, BluetoothDiscoverySession}; +#[cfg(not(any( + all(target_os = "linux", feature = "bluetooth"), + all(target_os = "android", feature = "bluetooth"), + all(target_os = "macos", feature = "bluetooth") +)))] +use super::empty::BluetoothDevice as BluetoothDeviceEmpty; +#[cfg(not(any( + all(target_os = "linux", feature = "bluetooth"), + all(target_os = "android", feature = "bluetooth"), + all(target_os = "macos", feature = "bluetooth") +)))] +use super::empty::BluetoothDiscoverySession as BluetoothDiscoverySessionEmpty; +#[cfg(not(any( + all(target_os = "linux", feature = "bluetooth"), + all(target_os = "android", feature = "bluetooth"), + all(target_os = "macos", feature = "bluetooth") +)))] +use super::empty::EmptyAdapter as BluetoothAdapterEmpty; +use super::macros::get_inner_and_call; +#[cfg(feature = "bluetooth-test")] +use super::macros::get_inner_and_call_test_func; + +#[derive(Clone, Debug)] +pub enum BluetoothAdapter { + #[cfg(all(target_os = "linux", feature = "bluetooth"))] + Bluez(Arc<BluetoothAdapterBluez>), + #[cfg(all(target_os = "android", feature = "bluetooth"))] + Android(Arc<BluetoothAdapterAndroid>), + #[cfg(all(target_os = "macos", feature = "bluetooth"))] + Mac(Arc<BluetoothAdapterMac>), + #[cfg(not(any( + all(target_os = "linux", feature = "bluetooth"), + all(target_os = "android", feature = "bluetooth"), + all(target_os = "macos", feature = "bluetooth") + )))] + Empty(Arc<BluetoothAdapterEmpty>), + #[cfg(feature = "bluetooth-test")] + Mock(Arc<FakeBluetoothAdapter>), +} + +impl BluetoothAdapter { + #[cfg(all(target_os = "linux", feature = "bluetooth"))] + pub fn new() -> Result<BluetoothAdapter, Box<dyn Error>> { + let bluez_adapter = BluetoothAdapterBluez::init()?; + Ok(Self::Bluez(Arc::new(bluez_adapter))) + } + + #[cfg(all(target_os = "android", feature = "bluetooth"))] + pub fn new() -> Result<BluetoothAdapter, Box<dyn Error>> { + let blurdroid_adapter = BluetoothAdapterAndroid::get_adapter()?; + Ok(Self::Android(Arc::new(blurdroid_adapter))) + } + + #[cfg(all(target_os = "macos", feature = "bluetooth"))] + pub fn new() -> Result<BluetoothAdapter, Box<dyn Error>> { + let mac_adapter = BluetoothAdapterMac::init()?; + Ok(Self::Mac(Arc::new(mac_adapter))) + } + + #[cfg(not(any( + all(target_os = "linux", feature = "bluetooth"), + all(target_os = "android", feature = "bluetooth"), + all(target_os = "macos", feature = "bluetooth") + )))] + pub fn new() -> Result<BluetoothAdapter, Box<dyn Error>> { + let adapter = BluetoothAdapterEmpty::init()?; + Ok(Self::Empty(Arc::new(adapter))) + } + + #[cfg(feature = "bluetooth-test")] + pub fn new_mock() -> Result<BluetoothAdapter, Box<dyn Error>> { + Ok(Self::Mock(FakeBluetoothAdapter::new_empty())) + } + + pub fn get_id(&self) -> String { + get_inner_and_call!(self, BluetoothAdapter, get_id) + } + + pub fn get_devices(&self) -> Result<Vec<BluetoothDevice>, Box<dyn Error>> { + match self { + #[cfg(all(target_os = "linux", feature = "bluetooth"))] + BluetoothAdapter::Bluez(inner) => { + let device_list = inner.get_device_list()?; + Ok(device_list + .into_iter() + .map(|device| { + BluetoothDevice::Bluez(BluetoothDeviceBluez::new_empty( + self.0.clone(), + device, + )) + }) + .collect()) + }, + #[cfg(all(target_os = "android", feature = "bluetooth"))] + BluetoothAdapter::Android(inner) => { + let device_list = inner.get_device_list()?; + Ok(device_list + .into_iter() + .map(|device| { + BluetoothDevice::Android(BluetoothDeviceAndroid::new_empty( + self.0.clone(), + device, + )) + }) + .collect()) + }, + #[cfg(all(target_os = "macos", feature = "bluetooth"))] + BluetoothAdapter::Mac(inner) => { + let device_list = inner.get_device_list()?; + Ok(device_list + .into_iter() + .map(|device| { + BluetoothDevice::Mac(Arc::new(BluetoothDeviceMac::new( + inner.clone(), + device, + ))) + }) + .collect()) + }, + #[cfg(not(any( + all(target_os = "linux", feature = "bluetooth"), + all(target_os = "android", feature = "bluetooth"), + all(target_os = "macos", feature = "bluetooth") + )))] + BluetoothAdapter::Empty(inner) => { + let device_list = inner.get_device_list()?; + Ok(device_list + .into_iter() + .map(|device| { + BluetoothDevice::Empty(Arc::new(BluetoothDeviceEmpty::new(device))) + }) + .collect()) + }, + #[cfg(feature = "bluetooth-test")] + BluetoothAdapter::Mock(inner) => { + let device_list = inner.get_device_list()?; + Ok(device_list + .into_iter() + .map(|device| { + BluetoothDevice::Mock(FakeBluetoothDevice::new_empty(inner.clone(), device)) + }) + .collect()) + }, + } + } + + pub fn get_device(&self, address: String) -> Result<Option<BluetoothDevice>, Box<dyn Error>> { + let devices = self.get_devices()?; + for device in devices { + if device.get_address()? == address { + return Ok(Some(device)); + } + } + Ok(None) + } + + pub fn create_mock_device(&self, _device: String) -> Result<BluetoothDevice, Box<dyn Error>> { + match self { + #[cfg(feature = "bluetooth-test")] + BluetoothAdapter::Mock(inner) => Ok(BluetoothDevice::Mock( + FakeBluetoothDevice::new_empty(inner.clone(), _device), + )), + _ => Err(Box::from( + "Error! Test functions are not supported on real devices!", + )), + } + } + + pub fn create_discovery_session(&self) -> Result<BluetoothDiscoverySession, Box<dyn Error>> { + let discovery_session = match self { + #[cfg(all(target_os = "linux", feature = "bluetooth"))] + BluetoothAdapter::Bluez(inner) => { + BluetoothDiscoverySession::Bluez(Arc::new( + BluetoothDiscoverySessionBluez::create_session(inner.get_id())?, + )); + }, + #[cfg(all(target_os = "android", feature = "bluetooth"))] + BluetoothAdapter::Android(inner) => BluetoothDiscoverySession::Android(Arc::new( + BluetoothDiscoverySessionAndroid::create_session(inner.clone())?, + )), + #[cfg(all(target_os = "macos", feature = "bluetooth"))] + BluetoothAdapter::Mac(_) => { + BluetoothDiscoverySession::Mac(Arc::new(BluetoothDiscoverySessionMac {})) + }, + #[cfg(not(any( + all(target_os = "linux", feature = "bluetooth"), + all(target_os = "android", feature = "bluetooth"), + all(target_os = "macos", feature = "bluetooth") + )))] + BluetoothAdapter::Empty(_) => { + BluetoothDiscoverySession::Empty(Arc::new(BluetoothDiscoverySessionEmpty {})) + }, + #[cfg(feature = "bluetooth-test")] + BluetoothAdapter::Mock(inner) => BluetoothDiscoverySession::Mock(Arc::new( + FakeBluetoothDiscoverySession::create_session(inner.clone())?, + )), + }; + Ok(discovery_session) + } + + pub fn get_address(&self) -> Result<String, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothAdapter, get_address) + } + + pub fn get_name(&self) -> Result<String, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothAdapter, get_name) + } + + pub fn get_alias(&self) -> Result<String, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothAdapter, get_alias) + } + + pub fn get_class(&self) -> Result<u32, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothAdapter, get_class) + } + + pub fn is_powered(&self) -> Result<bool, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothAdapter, is_powered) + } + + pub fn is_discoverable(&self) -> Result<bool, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothAdapter, is_discoverable) + } + + pub fn is_pairable(&self) -> Result<bool, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothAdapter, is_pairable) + } + + pub fn get_pairable_timeout(&self) -> Result<u32, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothAdapter, get_pairable_timeout) + } + + pub fn get_discoverable_timeout(&self) -> Result<u32, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothAdapter, get_discoverable_timeout) + } + + pub fn is_discovering(&self) -> Result<bool, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothAdapter, is_discovering) + } + + pub fn get_uuids(&self) -> Result<Vec<String>, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothAdapter, get_uuids) + } + + pub fn get_vendor_id_source(&self) -> Result<String, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothAdapter, get_vendor_id_source) + } + + pub fn get_vendor_id(&self) -> Result<u32, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothAdapter, get_vendor_id) + } + + pub fn get_product_id(&self) -> Result<u32, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothAdapter, get_product_id) + } + + pub fn get_device_id(&self) -> Result<u32, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothAdapter, get_device_id) + } + + pub fn get_modalias(&self) -> Result<(String, u32, u32, u32), Box<dyn Error>> { + get_inner_and_call!(self, BluetoothAdapter, get_modalias) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_id(&self, id: String) -> Result<(), Box<dyn Error>> { + match self { + #[cfg(feature = "bluetooth-test")] + BluetoothAdapter::Mock(inner) => Ok(inner.set_id(id)), + _ => Err(Box::from( + "Error! Test functions are not supported on real devices!", + )), + } + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_address(&self, address: String) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothAdapter, set_address, address) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_name(&self, name: String) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothAdapter, set_name, name) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_alias(&self, alias: String) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothAdapter, set_alias, alias) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_class(&self, class: u32) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothAdapter, set_class, class) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_powered(&self, powered: bool) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothAdapter, set_powered, powered) + } + + #[cfg(feature = "bluetooth-test")] + pub fn is_present(&self) -> Result<bool, Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothAdapter, is_present) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_present(&self, present: bool) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothAdapter, set_present, present) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_discoverable(&self, discoverable: bool) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothAdapter, set_discoverable, discoverable) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_pairable(&self, pairable: bool) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothAdapter, set_pairable, pairable) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_pairable_timeout(&self, timeout: u32) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothAdapter, set_pairable_timeout, timeout) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_can_start_discovery(&self, can_start_discovery: bool) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!( + self, + BluetoothAdapter, + set_can_start_discovery, + can_start_discovery + ) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_discoverable_timeout(&self, timeout: u32) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothAdapter, set_discoverable_timeout, timeout) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_discovering(&self, discovering: bool) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothAdapter, set_discovering, discovering) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_can_stop_discovery(&self, can_stop_discovery: bool) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!( + self, + BluetoothAdapter, + set_can_stop_discovery, + can_stop_discovery + ) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_uuids(&self, uuids: Vec<String>) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothAdapter, set_uuids, uuids) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_modalias(&self, modalias: String) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothAdapter, set_modalias, modalias) + } + + #[cfg(feature = "bluetooth-test")] + pub fn get_ad_datas(&self) -> Result<Vec<String>, Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothAdapter, get_ad_datas) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_ad_datas(&self, ad_datas: Vec<String>) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothAdapter, set_ad_datas, ad_datas) + } +} diff --git a/components/bluetooth/bluetooth.rs b/components/bluetooth/bluetooth.rs new file mode 100644 index 00000000000..71766160849 --- /dev/null +++ b/components/bluetooth/bluetooth.rs @@ -0,0 +1,753 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::collections::HashMap; +use std::error::Error; +use std::sync::Arc; + +#[cfg(all(target_os = "android", feature = "bluetooth"))] +use blurdroid::bluetooth_device::Device as BluetoothDeviceAndroid; +#[cfg(all(target_os = "android", feature = "bluetooth"))] +use blurdroid::bluetooth_discovery_session::DiscoverySession as BluetoothDiscoverySessionAndroid; +#[cfg(all(target_os = "android", feature = "bluetooth"))] +use blurdroid::bluetooth_gatt_characteristic::Characteristic as BluetoothGATTCharacteristicAndroid; +#[cfg(all(target_os = "android", feature = "bluetooth"))] +use blurdroid::bluetooth_gatt_descriptor::Descriptor as BluetoothGATTDescriptorAndroid; +#[cfg(all(target_os = "android", feature = "bluetooth"))] +use blurdroid::bluetooth_gatt_service::Service as BluetoothGATTServiceAndroid; +#[cfg(all(target_os = "macos", feature = "bluetooth"))] +use blurmac::BluetoothDevice as BluetoothDeviceMac; +#[cfg(all(target_os = "macos", feature = "bluetooth"))] +use blurmac::BluetoothDiscoverySession as BluetoothDiscoverySessionMac; +#[cfg(all(target_os = "macos", feature = "bluetooth"))] +use blurmac::BluetoothGATTCharacteristic as BluetoothGATTCharacteristicMac; +#[cfg(all(target_os = "macos", feature = "bluetooth"))] +use blurmac::BluetoothGATTDescriptor as BluetoothGATTDescriptorMac; +#[cfg(all(target_os = "macos", feature = "bluetooth"))] +use blurmac::BluetoothGATTService as BluetoothGATTServiceMac; +#[cfg(feature = "bluetooth-test")] +use blurmock::fake_characteristic::FakeBluetoothGATTCharacteristic; +#[cfg(feature = "bluetooth-test")] +use blurmock::fake_descriptor::FakeBluetoothGATTDescriptor; +#[cfg(feature = "bluetooth-test")] +use blurmock::fake_device::FakeBluetoothDevice; +#[cfg(feature = "bluetooth-test")] +use blurmock::fake_discovery_session::FakeBluetoothDiscoverySession; +#[cfg(feature = "bluetooth-test")] +use blurmock::fake_service::FakeBluetoothGATTService; +#[cfg(all(target_os = "linux", feature = "bluetooth"))] +use blurz::bluetooth_device::BluetoothDevice as BluetoothDeviceBluez; +#[cfg(all(target_os = "linux", feature = "bluetooth"))] +use blurz::bluetooth_discovery_session::BluetoothDiscoverySession as BluetoothDiscoverySessionBluez; +#[cfg(all(target_os = "linux", feature = "bluetooth"))] +use blurz::bluetooth_gatt_characteristic::BluetoothGATTCharacteristic as BluetoothGATTCharacteristicBluez; +#[cfg(all(target_os = "linux", feature = "bluetooth"))] +use blurz::bluetooth_gatt_descriptor::BluetoothGATTDescriptor as BluetoothGATTDescriptorBluez; +#[cfg(all(target_os = "linux", feature = "bluetooth"))] +use blurz::bluetooth_gatt_service::BluetoothGATTService as BluetoothGATTServiceBluez; + +pub use super::adapter::BluetoothAdapter; +#[cfg(not(any( + all(target_os = "linux", feature = "bluetooth"), + all(target_os = "android", feature = "bluetooth"), + all(target_os = "macos", feature = "bluetooth") +)))] +use super::empty::BluetoothDevice as BluetoothDeviceEmpty; +#[cfg(not(any( + all(target_os = "linux", feature = "bluetooth"), + all(target_os = "android", feature = "bluetooth"), + all(target_os = "macos", feature = "bluetooth") +)))] +use super::empty::BluetoothDiscoverySession as BluetoothDiscoverySessionEmpty; +#[cfg(not(any( + all(target_os = "linux", feature = "bluetooth"), + all(target_os = "android", feature = "bluetooth"), + all(target_os = "macos", feature = "bluetooth") +)))] +use super::empty::BluetoothGATTCharacteristic as BluetoothGATTCharacteristicEmpty; +#[cfg(not(any( + all(target_os = "linux", feature = "bluetooth"), + all(target_os = "android", feature = "bluetooth"), + all(target_os = "macos", feature = "bluetooth") +)))] +use super::empty::BluetoothGATTDescriptor as BluetoothGATTDescriptorEmpty; +#[cfg(not(any( + all(target_os = "linux", feature = "bluetooth"), + all(target_os = "android", feature = "bluetooth"), + all(target_os = "macos", feature = "bluetooth") +)))] +use super::empty::BluetoothGATTService as BluetoothGATTServiceEmpty; +use super::macros::get_inner_and_call; +#[cfg(feature = "bluetooth-test")] +use super::macros::get_inner_and_call_test_func; + +#[cfg(feature = "bluetooth-test")] +const NOT_SUPPORTED_ON_MOCK_ERROR: &'static str = + "Error! The first parameter must be a mock structure!"; + +#[derive(Debug)] +pub enum BluetoothDiscoverySession { + #[cfg(all(target_os = "linux", feature = "bluetooth"))] + Bluez(Arc<BluetoothDiscoverySessionBluez>), + #[cfg(all(target_os = "android", feature = "bluetooth"))] + Android(Arc<BluetoothDiscoverySessionAndroid>), + #[cfg(all(target_os = "macos", feature = "bluetooth"))] + Mac(Arc<BluetoothDiscoverySessionMac>), + #[cfg(not(any( + all(target_os = "linux", feature = "bluetooth"), + all(target_os = "android", feature = "bluetooth"), + all(target_os = "macos", feature = "bluetooth") + )))] + Empty(Arc<BluetoothDiscoverySessionEmpty>), + #[cfg(feature = "bluetooth-test")] + Mock(Arc<FakeBluetoothDiscoverySession>), +} + +#[derive(Clone, Debug)] +pub enum BluetoothDevice { + #[cfg(all(target_os = "linux", feature = "bluetooth"))] + Bluez(Arc<BluetoothDeviceBluez>), + #[cfg(all(target_os = "android", feature = "bluetooth"))] + Android(Arc<BluetoothDeviceAndroid>), + #[cfg(all(target_os = "macos", feature = "bluetooth"))] + Mac(Arc<BluetoothDeviceMac>), + #[cfg(not(any( + all(target_os = "linux", feature = "bluetooth"), + all(target_os = "android", feature = "bluetooth"), + all(target_os = "macos", feature = "bluetooth") + )))] + Empty(Arc<BluetoothDeviceEmpty>), + #[cfg(feature = "bluetooth-test")] + Mock(Arc<FakeBluetoothDevice>), +} + +#[derive(Clone, Debug)] +pub enum BluetoothGATTService { + #[cfg(all(target_os = "linux", feature = "bluetooth"))] + Bluez(Arc<BluetoothGATTServiceBluez>), + #[cfg(all(target_os = "android", feature = "bluetooth"))] + Android(Arc<BluetoothGATTServiceAndroid>), + #[cfg(all(target_os = "macos", feature = "bluetooth"))] + Mac(Arc<BluetoothGATTServiceMac>), + #[cfg(not(any( + all(target_os = "linux", feature = "bluetooth"), + all(target_os = "android", feature = "bluetooth"), + all(target_os = "macos", feature = "bluetooth") + )))] + Empty(Arc<BluetoothGATTServiceEmpty>), + #[cfg(feature = "bluetooth-test")] + Mock(Arc<FakeBluetoothGATTService>), +} + +#[derive(Clone, Debug)] +pub enum BluetoothGATTCharacteristic { + #[cfg(all(target_os = "linux", feature = "bluetooth"))] + Bluez(Arc<BluetoothGATTCharacteristicBluez>), + #[cfg(all(target_os = "android", feature = "bluetooth"))] + Android(Arc<BluetoothGATTCharacteristicAndroid>), + #[cfg(all(target_os = "macos", feature = "bluetooth"))] + Mac(Arc<BluetoothGATTCharacteristicMac>), + #[cfg(not(any( + all(target_os = "linux", feature = "bluetooth"), + all(target_os = "android", feature = "bluetooth"), + all(target_os = "macos", feature = "bluetooth") + )))] + Empty(Arc<BluetoothGATTCharacteristicEmpty>), + #[cfg(feature = "bluetooth-test")] + Mock(Arc<FakeBluetoothGATTCharacteristic>), +} + +#[derive(Clone, Debug)] +pub enum BluetoothGATTDescriptor { + #[cfg(all(target_os = "linux", feature = "bluetooth"))] + Bluez(Arc<BluetoothGATTDescriptorBluez>), + #[cfg(all(target_os = "android", feature = "bluetooth"))] + Android(Arc<BluetoothGATTDescriptorAndroid>), + #[cfg(all(target_os = "macos", feature = "bluetooth"))] + Mac(Arc<BluetoothGATTDescriptorMac>), + #[cfg(not(any( + all(target_os = "linux", feature = "bluetooth"), + all(target_os = "android", feature = "bluetooth"), + all(target_os = "macos", feature = "bluetooth") + )))] + Empty(Arc<BluetoothGATTDescriptorEmpty>), + #[cfg(feature = "bluetooth-test")] + Mock(Arc<FakeBluetoothGATTDescriptor>), +} + +impl BluetoothDiscoverySession { + pub fn start_discovery(&self) -> Result<(), Box<dyn Error>> { + get_inner_and_call!(self, BluetoothDiscoverySession, start_discovery) + } + + pub fn stop_discovery(&self) -> Result<(), Box<dyn Error>> { + get_inner_and_call!(self, BluetoothDiscoverySession, stop_discovery) + } +} + +impl BluetoothDevice { + pub fn get_id(&self) -> String { + get_inner_and_call!(self, BluetoothDevice, get_id) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_id(&self, id: String) { + match self { + &BluetoothDevice::Mock(ref fake_adapter) => fake_adapter.set_id(id), + _ => (), + } + } + + pub fn get_address(&self) -> Result<String, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothDevice, get_address) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_address(&self, address: String) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothDevice, set_address, address) + } + + pub fn get_name(&self) -> Result<String, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothDevice, get_name) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_name(&self, name: Option<String>) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothDevice, set_name, name) + } + + pub fn get_icon(&self) -> Result<String, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothDevice, get_icon) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_icon(&self, icon: String) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothDevice, set_icon, icon) + } + + pub fn get_class(&self) -> Result<u32, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothDevice, get_class) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_class(&self, class: u32) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothDevice, set_class, class) + } + + pub fn get_appearance(&self) -> Result<u16, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothDevice, get_appearance) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_appearance(&self, appearance: u16) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothDevice, set_appearance, Some(appearance)) + } + + pub fn get_uuids(&self) -> Result<Vec<String>, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothDevice, get_uuids) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_uuids(&self, uuids: Vec<String>) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothDevice, set_uuids, uuids) + } + + pub fn is_paired(&self) -> Result<bool, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothDevice, is_paired) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_paired(&self, paired: bool) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothDevice, set_paired, paired) + } + + pub fn is_connected(&self) -> Result<bool, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothDevice, is_connected) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_connected(&self, connected: bool) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothDevice, set_connected, connected) + } + + #[cfg(feature = "bluetooth-test")] + pub fn is_connectable(&self) -> Result<bool, Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothDevice, is_connectable) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_connectable(&self, connectable: bool) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothDevice, set_connectable, connectable) + } + + pub fn is_trusted(&self) -> Result<bool, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothDevice, is_trusted) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_trusted(&self, trusted: bool) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothDevice, set_trusted, trusted) + } + + pub fn is_blocked(&self) -> Result<bool, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothDevice, is_blocked) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_blocked(&self, blocked: bool) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothDevice, set_blocked, blocked) + } + + pub fn get_alias(&self) -> Result<String, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothDevice, get_alias) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_alias(&self, alias: String) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothDevice, set_alias, alias) + } + + pub fn is_legacy_pairing(&self) -> Result<bool, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothDevice, is_legacy_pairing) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_legacy_pairing(&self, legacy_pairing: bool) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothDevice, set_legacy_pairing, legacy_pairing) + } + + pub fn get_vendor_id_source(&self) -> Result<String, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothDevice, get_vendor_id_source) + } + + pub fn get_vendor_id(&self) -> Result<u32, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothDevice, get_vendor_id) + } + + pub fn get_product_id(&self) -> Result<u32, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothDevice, get_product_id) + } + + pub fn get_device_id(&self) -> Result<u32, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothDevice, get_device_id) + } + + pub fn get_modalias(&self) -> Result<(String, u32, u32, u32), Box<dyn Error>> { + get_inner_and_call!(self, BluetoothDevice, get_modalias) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_modalias(&self, modalias: String) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothDevice, set_modalias, modalias) + } + + pub fn get_rssi(&self) -> Result<i16, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothDevice, get_rssi) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_rssi(&self, rssi: i16) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothDevice, set_rssi, Some(rssi)) + } + + pub fn get_tx_power(&self) -> Result<i16, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothDevice, get_tx_power) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_tx_power(&self, tx_power: i16) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothDevice, set_tx_power, Some(tx_power)) + } + + pub fn get_manufacturer_data(&self) -> Result<HashMap<u16, Vec<u8>>, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothDevice, get_manufacturer_data) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_manufacturer_data( + &self, + manufacturer_data: HashMap<u16, Vec<u8>>, + ) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!( + self, + BluetoothDevice, + set_manufacturer_data, + Some(manufacturer_data) + ) + } + + pub fn get_service_data(&self) -> Result<HashMap<String, Vec<u8>>, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothDevice, get_service_data) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_service_data( + &self, + service_data: HashMap<String, Vec<u8>>, + ) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothDevice, set_service_data, Some(service_data)) + } + + pub fn get_gatt_services(&self) -> Result<Vec<BluetoothGATTService>, Box<dyn Error>> { + let services = get_inner_and_call!(self, BluetoothDevice, get_gatt_services)?; + Ok(services + .into_iter() + .map(|service| BluetoothGATTService::create_service(self.clone(), service)) + .collect()) + } + + pub fn connect(&self) -> Result<(), Box<dyn Error>> { + get_inner_and_call!(self, BluetoothDevice, connect) + } + + pub fn disconnect(&self) -> Result<(), Box<dyn Error>> { + get_inner_and_call!(self, BluetoothDevice, disconnect) + } + + pub fn connect_profile(&self, uuid: String) -> Result<(), Box<dyn Error>> { + get_inner_and_call!(self, BluetoothDevice, connect_profile, uuid) + } + + pub fn disconnect_profile(&self, uuid: String) -> Result<(), Box<dyn Error>> { + get_inner_and_call!(self, BluetoothDevice, disconnect_profile, uuid) + } + + pub fn pair(&self) -> Result<(), Box<dyn Error>> { + get_inner_and_call!(self, BluetoothDevice, pair) + } + + pub fn cancel_pairing(&self) -> Result<(), Box<dyn Error>> { + get_inner_and_call!(self, BluetoothDevice, cancel_pairing) + } +} + +impl BluetoothGATTService { + fn create_service(device: BluetoothDevice, service: String) -> BluetoothGATTService { + match device { + #[cfg(all(target_os = "linux", feature = "bluetooth"))] + BluetoothDevice::Bluez(_bluez_device) => { + BluetoothGATTService::Bluez(Arc::new(BluetoothGATTServiceBluez::new(service))) + }, + #[cfg(all(target_os = "android", feature = "bluetooth"))] + BluetoothDevice::Android(android_device) => BluetoothGATTService::Android(Arc::new( + BluetoothGATTServiceAndroid::new(android_device, service), + )), + #[cfg(all(target_os = "macos", feature = "bluetooth"))] + BluetoothDevice::Mac(mac_device) => BluetoothGATTService::Mac(Arc::new( + BluetoothGATTServiceMac::new(mac_device, service), + )), + #[cfg(not(any( + all(target_os = "linux", feature = "bluetooth"), + all(target_os = "android", feature = "bluetooth"), + all(target_os = "macos", feature = "bluetooth") + )))] + BluetoothDevice::Empty(_device) => { + BluetoothGATTService::Empty(Arc::new(BluetoothGATTServiceEmpty::new(service))) + }, + #[cfg(feature = "bluetooth-test")] + BluetoothDevice::Mock(fake_device) => BluetoothGATTService::Mock( + FakeBluetoothGATTService::new_empty(fake_device, service), + ), + } + } + + #[cfg(feature = "bluetooth-test")] + pub fn create_mock_service( + device: BluetoothDevice, + service: String, + ) -> Result<BluetoothGATTService, Box<dyn Error>> { + match device { + BluetoothDevice::Mock(fake_device) => Ok(BluetoothGATTService::Mock( + FakeBluetoothGATTService::new_empty(fake_device, service), + )), + _ => Err(Box::from( + "Error! The first parameter must be a mock structure!", + )), + } + } + + pub fn get_id(&self) -> String { + get_inner_and_call!(self, BluetoothGATTService, get_id) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_id(&self, id: String) { + match self { + &BluetoothGATTService::Mock(ref fake_service) => fake_service.set_id(id), + _ => (), + } + } + + pub fn get_uuid(&self) -> Result<String, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothGATTService, get_uuid) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_uuid(&self, uuid: String) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothGATTService, set_uuid, uuid) + } + + pub fn is_primary(&self) -> Result<bool, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothGATTService, is_primary) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_primary(&self, primary: bool) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothGATTService, set_is_primary, primary) + } + + pub fn get_includes( + &self, + device: BluetoothDevice, + ) -> Result<Vec<BluetoothGATTService>, Box<dyn Error>> { + let services = get_inner_and_call!(self, BluetoothGATTService, get_includes)?; + Ok(services + .into_iter() + .map(|service| BluetoothGATTService::create_service(device.clone(), service)) + .collect()) + } + + pub fn get_gatt_characteristics( + &self, + ) -> Result<Vec<BluetoothGATTCharacteristic>, Box<dyn Error>> { + let characteristics = + get_inner_and_call!(self, BluetoothGATTService, get_gatt_characteristics)?; + Ok(characteristics + .into_iter() + .map(|characteristic| { + BluetoothGATTCharacteristic::create_characteristic(self.clone(), characteristic) + }) + .collect()) + } +} + +impl BluetoothGATTCharacteristic { + fn create_characteristic( + service: BluetoothGATTService, + characteristic: String, + ) -> BluetoothGATTCharacteristic { + match service { + #[cfg(all(target_os = "linux", feature = "bluetooth"))] + BluetoothGATTService::Bluez(_bluez_service) => BluetoothGATTCharacteristic::Bluez( + Arc::new(BluetoothGATTCharacteristicBluez::new(characteristic)), + ), + #[cfg(all(target_os = "android", feature = "bluetooth"))] + BluetoothGATTService::Android(android_service) => { + BluetoothGATTCharacteristic::Android(Arc::new( + BluetoothGATTCharacteristicAndroid::new(android_service, characteristic), + )) + }, + #[cfg(all(target_os = "macos", feature = "bluetooth"))] + BluetoothGATTService::Mac(mac_service) => BluetoothGATTCharacteristic::Mac(Arc::new( + BluetoothGATTCharacteristicMac::new(mac_service, characteristic), + )), + #[cfg(not(any( + all(target_os = "linux", feature = "bluetooth"), + all(target_os = "android", feature = "bluetooth"), + all(target_os = "macos", feature = "bluetooth") + )))] + BluetoothGATTService::Empty(_service) => BluetoothGATTCharacteristic::Empty(Arc::new( + BluetoothGATTCharacteristicEmpty::new(characteristic), + )), + #[cfg(feature = "bluetooth-test")] + BluetoothGATTService::Mock(fake_service) => BluetoothGATTCharacteristic::Mock( + FakeBluetoothGATTCharacteristic::new_empty(fake_service, characteristic), + ), + } + } + + #[cfg(feature = "bluetooth-test")] + pub fn create_mock_characteristic( + service: BluetoothGATTService, + characteristic: String, + ) -> Result<BluetoothGATTCharacteristic, Box<dyn Error>> { + match service { + BluetoothGATTService::Mock(fake_service) => Ok(BluetoothGATTCharacteristic::Mock( + FakeBluetoothGATTCharacteristic::new_empty(fake_service, characteristic), + )), + _ => Err(Box::from( + "Error! The first parameter must be a mock structure!", + )), + } + } + + pub fn get_id(&self) -> String { + get_inner_and_call!(self, BluetoothGATTCharacteristic, get_id) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_id(&self, id: String) { + match self { + &BluetoothGATTCharacteristic::Mock(ref fake_characteristic) => { + fake_characteristic.set_id(id) + }, + _ => (), + } + } + + pub fn get_uuid(&self) -> Result<String, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothGATTCharacteristic, get_uuid) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_uuid(&self, uuid: String) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothGATTCharacteristic, set_uuid, uuid) + } + + pub fn get_value(&self) -> Result<Vec<u8>, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothGATTCharacteristic, get_value) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_value(&self, value: Vec<u8>) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothGATTCharacteristic, set_value, Some(value)) + } + + pub fn is_notifying(&self) -> Result<bool, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothGATTCharacteristic, is_notifying) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_notifying(&self, notifying: bool) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothGATTCharacteristic, set_notifying, notifying) + } + + pub fn get_flags(&self) -> Result<Vec<String>, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothGATTCharacteristic, get_flags) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_flags(&self, flags: Vec<String>) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothGATTCharacteristic, set_flags, flags) + } + + pub fn get_gatt_descriptors(&self) -> Result<Vec<BluetoothGATTDescriptor>, Box<dyn Error>> { + let descriptors = + get_inner_and_call!(self, BluetoothGATTCharacteristic, get_gatt_descriptors)?; + Ok(descriptors + .into_iter() + .map(|descriptor| BluetoothGATTDescriptor::create_descriptor(self.clone(), descriptor)) + .collect()) + } + + pub fn read_value(&self) -> Result<Vec<u8>, Box<dyn Error>> { + get_inner_and_call!(@with_bluez_offset, self, BluetoothGATTCharacteristic, read_value) + } + + pub fn write_value(&self, values: Vec<u8>) -> Result<(), Box<dyn Error>> { + get_inner_and_call!(@with_bluez_offset, self, BluetoothGATTCharacteristic, write_value, values) + } + + pub fn start_notify(&self) -> Result<(), Box<dyn Error>> { + get_inner_and_call!(self, BluetoothGATTCharacteristic, start_notify) + } + + pub fn stop_notify(&self) -> Result<(), Box<dyn Error>> { + get_inner_and_call!(self, BluetoothGATTCharacteristic, stop_notify) + } +} + +impl BluetoothGATTDescriptor { + fn create_descriptor( + characteristic: BluetoothGATTCharacteristic, + descriptor: String, + ) -> BluetoothGATTDescriptor { + match characteristic { + #[cfg(all(target_os = "linux", feature = "bluetooth"))] + BluetoothGATTCharacteristic::Bluez(_bluez_characteristic) => { + BluetoothGATTDescriptor::Bluez(Arc::new(BluetoothGATTDescriptorBluez::new( + descriptor, + ))) + }, + #[cfg(all(target_os = "android", feature = "bluetooth"))] + BluetoothGATTCharacteristic::Android(android_characteristic) => { + BluetoothGATTDescriptor::Android(Arc::new(BluetoothGATTDescriptorAndroid::new( + android_characteristic, + descriptor, + ))) + }, + #[cfg(all(target_os = "macos", feature = "bluetooth"))] + BluetoothGATTCharacteristic::Mac(_mac_characteristic) => { + BluetoothGATTDescriptor::Mac(Arc::new(BluetoothGATTDescriptorMac::new(descriptor))) + }, + #[cfg(not(any( + all(target_os = "linux", feature = "bluetooth"), + all(target_os = "android", feature = "bluetooth"), + all(target_os = "macos", feature = "bluetooth") + )))] + BluetoothGATTCharacteristic::Empty(_characteristic) => BluetoothGATTDescriptor::Empty( + Arc::new(BluetoothGATTDescriptorEmpty::new(descriptor)), + ), + #[cfg(feature = "bluetooth-test")] + BluetoothGATTCharacteristic::Mock(fake_characteristic) => { + BluetoothGATTDescriptor::Mock(FakeBluetoothGATTDescriptor::new_empty( + fake_characteristic, + descriptor, + )) + }, + } + } + + #[cfg(feature = "bluetooth-test")] + pub fn create_mock_descriptor( + characteristic: BluetoothGATTCharacteristic, + descriptor: String, + ) -> Result<BluetoothGATTDescriptor, Box<dyn Error>> { + match characteristic { + BluetoothGATTCharacteristic::Mock(fake_characteristic) => { + Ok(BluetoothGATTDescriptor::Mock( + FakeBluetoothGATTDescriptor::new_empty(fake_characteristic, descriptor), + )) + }, + _ => Err(Box::from(NOT_SUPPORTED_ON_MOCK_ERROR)), + } + } + + pub fn get_id(&self) -> String { + get_inner_and_call!(self, BluetoothGATTDescriptor, get_id) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_id(&self, id: String) { + match self { + &BluetoothGATTDescriptor::Mock(ref fake_descriptor) => fake_descriptor.set_id(id), + _ => (), + } + } + + pub fn get_uuid(&self) -> Result<String, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothGATTDescriptor, get_uuid) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_uuid(&self, uuid: String) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothGATTDescriptor, set_uuid, uuid) + } + + pub fn get_value(&self) -> Result<Vec<u8>, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothGATTDescriptor, get_value) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_value(&self, value: Vec<u8>) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothGATTDescriptor, set_value, Some(value)) + } + + pub fn get_flags(&self) -> Result<Vec<String>, Box<dyn Error>> { + get_inner_and_call!(self, BluetoothGATTDescriptor, get_flags) + } + + #[cfg(feature = "bluetooth-test")] + pub fn set_flags(&self, flags: Vec<String>) -> Result<(), Box<dyn Error>> { + get_inner_and_call_test_func!(self, BluetoothGATTDescriptor, set_flags, flags) + } + + pub fn read_value(&self) -> Result<Vec<u8>, Box<dyn Error>> { + get_inner_and_call!(@with_bluez_offset, self, BluetoothGATTDescriptor, read_value) + } + + pub fn write_value(&self, values: Vec<u8>) -> Result<(), Box<dyn Error>> { + get_inner_and_call!(@with_bluez_offset, self, BluetoothGATTDescriptor, write_value, values) + } +} diff --git a/components/bluetooth/empty.rs b/components/bluetooth/empty.rs new file mode 100644 index 00000000000..27773a10a47 --- /dev/null +++ b/components/bluetooth/empty.rs @@ -0,0 +1,377 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::collections::HashMap; +use std::error::Error; +use std::sync::Arc; + +const NOT_SUPPORTED_ERROR: &'static str = "Error! Not supported platform!"; + +#[derive(Clone, Debug)] +pub struct EmptyAdapter {} + +impl EmptyAdapter { + pub fn init() -> Result<EmptyAdapter, Box<dyn Error>> { + Ok(EmptyAdapter::new()) + } + + fn new() -> EmptyAdapter { + EmptyAdapter {} + } + + pub fn get_id(&self) -> String { + String::new() + } + + pub fn get_device_list(&self) -> Result<Vec<String>, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_address(&self) -> Result<String, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_name(&self) -> Result<String, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_alias(&self) -> Result<String, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn set_alias(&self, _value: String) -> Result<(), Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_class(&self) -> Result<u32, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn is_powered(&self) -> Result<bool, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn set_powered(&self, _value: bool) -> Result<(), Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn is_discoverable(&self) -> Result<bool, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn set_discoverable(&self, _value: bool) -> Result<(), Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn is_pairable(&self) -> Result<bool, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn set_pairable(&self, _value: bool) -> Result<(), Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_pairable_timeout(&self) -> Result<u32, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn set_pairable_timeout(&self, _value: u32) -> Result<(), Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_discoverable_timeout(&self) -> Result<u32, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn set_discoverable_timeout(&self, _value: u32) -> Result<(), Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn is_discovering(&self) -> Result<bool, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_uuids(&self) -> Result<Vec<String>, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_vendor_id_source(&self) -> Result<String, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_vendor_id(&self) -> Result<u32, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_product_id(&self) -> Result<u32, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_device_id(&self) -> Result<u32, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_modalias(&self) -> Result<(String, u32, u32, u32), Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } +} + +#[derive(Clone, Debug)] +pub struct BluetoothDiscoverySession {} + +impl BluetoothDiscoverySession { + pub fn create_session( + _adapter: Arc<EmptyAdapter>, + ) -> Result<BluetoothDiscoverySession, Box<dyn Error>> { + Ok(BluetoothDiscoverySession {}) + } + + pub fn start_discovery(&self) -> Result<(), Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn stop_discovery(&self) -> Result<(), Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } +} + +#[derive(Clone, Debug)] +pub struct BluetoothDevice {} + +impl BluetoothDevice { + pub fn new(_device: String) -> BluetoothDevice { + BluetoothDevice {} + } + + pub fn get_id(&self) -> String { + String::new() + } + + pub fn get_address(&self) -> Result<String, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_name(&self) -> Result<String, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_icon(&self) -> Result<String, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_class(&self) -> Result<u32, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_appearance(&self) -> Result<u16, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_uuids(&self) -> Result<Vec<String>, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn is_paired(&self) -> Result<bool, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn is_connected(&self) -> Result<bool, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn is_trusted(&self) -> Result<bool, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn is_blocked(&self) -> Result<bool, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_alias(&self) -> Result<String, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn set_alias(&self, _value: String) -> Result<(), Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn is_legacy_pairing(&self) -> Result<bool, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_vendor_id_source(&self) -> Result<String, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_vendor_id(&self) -> Result<u32, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_product_id(&self) -> Result<u32, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_device_id(&self) -> Result<u32, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_modalias(&self) -> Result<(String, u32, u32, u32), Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_rssi(&self) -> Result<i16, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_tx_power(&self) -> Result<i16, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_manufacturer_data(&self) -> Result<HashMap<u16, Vec<u8>>, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_service_data(&self) -> Result<HashMap<String, Vec<u8>>, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_gatt_services(&self) -> Result<Vec<String>, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn connect(&self) -> Result<(), Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn disconnect(&self) -> Result<(), Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn connect_profile(&self, _uuid: String) -> Result<(), Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn disconnect_profile(&self, _uuid: String) -> Result<(), Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn pair(&self) -> Result<(), Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn cancel_pairing(&self) -> Result<(), Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } +} + +#[derive(Clone, Debug)] +pub struct BluetoothGATTService {} + +impl BluetoothGATTService { + pub fn new(_service: String) -> BluetoothGATTService { + BluetoothGATTService {} + } + + pub fn get_id(&self) -> String { + String::new() + } + + pub fn get_uuid(&self) -> Result<String, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn is_primary(&self) -> Result<bool, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_includes(&self) -> Result<Vec<String>, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_gatt_characteristics(&self) -> Result<Vec<String>, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } +} + +#[derive(Clone, Debug)] +pub struct BluetoothGATTCharacteristic {} + +impl BluetoothGATTCharacteristic { + pub fn new(_characteristic: String) -> BluetoothGATTCharacteristic { + BluetoothGATTCharacteristic {} + } + + pub fn get_id(&self) -> String { + String::new() + } + + pub fn get_uuid(&self) -> Result<String, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_value(&self) -> Result<Vec<u8>, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn is_notifying(&self) -> Result<bool, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_flags(&self) -> Result<Vec<String>, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_gatt_descriptors(&self) -> Result<Vec<String>, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn read_value(&self) -> Result<Vec<u8>, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn write_value(&self, _values: Vec<u8>) -> Result<(), Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn start_notify(&self) -> Result<(), Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn stop_notify(&self) -> Result<(), Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } +} + +#[derive(Clone, Debug)] +pub struct BluetoothGATTDescriptor {} + +impl BluetoothGATTDescriptor { + pub fn new(_descriptor: String) -> BluetoothGATTDescriptor { + BluetoothGATTDescriptor {} + } + + pub fn get_id(&self) -> String { + String::new() + } + + pub fn get_uuid(&self) -> Result<String, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_value(&self) -> Result<Vec<u8>, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_flags(&self) -> Result<Vec<String>, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn read_value(&self) -> Result<Vec<u8>, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn write_value(&self, _values: Vec<u8>) -> Result<(), Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } +} diff --git a/components/bluetooth/lib.rs b/components/bluetooth/lib.rs index e6d7a533a53..f2a7bc4de70 100644 --- a/components/bluetooth/lib.rs +++ b/components/bluetooth/lib.rs @@ -2,6 +2,15 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +pub mod adapter; +pub mod bluetooth; +#[cfg(not(any( + all(target_os = "linux", feature = "bluetooth"), + all(target_os = "android", feature = "bluetooth"), + all(target_os = "macos", feature = "bluetooth") +)))] +mod empty; +mod macros; pub mod test; use std::borrow::ToOwned; @@ -20,16 +29,17 @@ use bluetooth_traits::{ BluetoothRequest, BluetoothResponse, BluetoothResponseResult, BluetoothResult, BluetoothServiceMsg, GATTType, }; -use device::bluetooth::{ - BluetoothAdapter, BluetoothDevice, BluetoothGATTCharacteristic, BluetoothGATTDescriptor, - BluetoothGATTService, -}; use embedder_traits::{EmbedderMsg, EmbedderProxy}; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use log::warn; use servo_config::pref; use servo_rand::{self, Rng}; +use crate::bluetooth::{ + BluetoothAdapter, BluetoothDevice, BluetoothGATTCharacteristic, BluetoothGATTDescriptor, + BluetoothGATTService, +}; + // A transaction not completed within 30 seconds shall time out. Such a transaction shall be considered to have failed. // https://www.bluetooth.org/DocMan/handlers/DownloadDoc.ashx?doc_id=286439 (Vol. 3, page 480) const MAXIMUM_TRANSACTION_TIME: u8 = 30; @@ -67,9 +77,9 @@ impl BluetoothThreadFactory for IpcSender<BluetoothRequest> { fn new(embedder_proxy: EmbedderProxy) -> IpcSender<BluetoothRequest> { let (sender, receiver) = ipc::channel().unwrap(); let adapter = if pref!(dom.bluetooth.enabled) { - BluetoothAdapter::init() + BluetoothAdapter::new() } else { - BluetoothAdapter::init_mock() + BluetoothAdapter::new_mock() } .ok(); thread::Builder::new() @@ -287,7 +297,7 @@ impl BluetoothManager { self.cached_characteristics.clear(); self.cached_descriptors.clear(); self.allowed_services.clear(); - self.adapter = BluetoothAdapter::init_mock().ok(); + self.adapter = BluetoothAdapter::new_mock().ok(); match test::test(self, data_set_name) { Ok(_) => return Ok(()), Err(error) => Err(BluetoothError::Type(error.to_string())), @@ -324,7 +334,7 @@ impl BluetoothManager { .as_ref() .map_or(false, |a| a.get_address().is_ok()); if !adapter_valid { - self.adapter = BluetoothAdapter::init().ok(); + self.adapter = BluetoothAdapter::new().ok(); } let adapter = self.adapter.as_ref()?; diff --git a/components/bluetooth/macros.rs b/components/bluetooth/macros.rs new file mode 100644 index 00000000000..fd76d1eb549 --- /dev/null +++ b/components/bluetooth/macros.rs @@ -0,0 +1,98 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +macro_rules! get_inner_and_call( + ($enum_value: expr, $enum_type: ident, $function_name: ident) => { + match $enum_value { + #[cfg(all(target_os = "linux", feature = "bluetooth"))] + &$enum_type::Bluez(ref bluez) => bluez.$function_name(), + #[cfg(all(target_os = "android", feature = "bluetooth"))] + &$enum_type::Android(ref android) => android.$function_name(), + #[cfg(all(target_os = "macos", feature = "bluetooth"))] + &$enum_type::Mac(ref mac) => mac.$function_name(), + #[cfg(not(any(all(target_os = "linux", feature = "bluetooth"), + all(target_os = "android", feature = "bluetooth"), + all(target_os = "macos", feature = "bluetooth"))))] + &$enum_type::Empty(ref empty) => empty.$function_name(), + #[cfg(feature = "bluetooth-test")] + &$enum_type::Mock(ref fake) => fake.$function_name(), + } + }; + + (@with_bluez_offset, $enum_value: expr, $enum_type: ident, $function_name: ident) => { + match $enum_value { + #[cfg(all(target_os = "linux", feature = "bluetooth"))] + &$enum_type::Bluez(ref bluez) => bluez.$function_name(None), + #[cfg(all(target_os = "android", feature = "bluetooth"))] + &$enum_type::Android(ref android) => android.$function_name(), + #[cfg(all(target_os = "macos", feature = "bluetooth"))] + &$enum_type::Mac(ref mac) => mac.$function_name(), + #[cfg(not(any(all(target_os = "linux", feature = "bluetooth"), + all(target_os = "android", feature = "bluetooth"), + all(target_os = "macos", feature = "bluetooth"))))] + &$enum_type::Empty(ref empty) => empty.$function_name(), + #[cfg(feature = "bluetooth-test")] + &$enum_type::Mock(ref fake) => fake.$function_name(), + } + }; + + ($enum_value: expr, $enum_type: ident, $function_name: ident, $value: expr) => { + match $enum_value { + #[cfg(all(target_os = "linux", feature = "bluetooth"))] + &$enum_type::Bluez(ref bluez) => bluez.$function_name($value), + #[cfg(all(target_os = "android", feature = "bluetooth"))] + &$enum_type::Android(ref android) => android.$function_name($value), + #[cfg(all(target_os = "macos", feature = "bluetooth"))] + &$enum_type::Mac(ref mac) => mac.$function_name($value), + #[cfg(not(any(all(target_os = "linux", feature = "bluetooth"), + all(target_os = "android", feature = "bluetooth"), + all(target_os = "macos", feature = "bluetooth"))))] + &$enum_type::Empty(ref empty) => empty.$function_name($value), + #[cfg(feature = "bluetooth-test")] + &$enum_type::Mock(ref fake) => fake.$function_name($value), + } + }; + + (@with_bluez_offset, $enum_value: expr, $enum_type: ident, $function_name: ident, $value: expr) => { + match $enum_value { + #[cfg(all(target_os = "linux", feature = "bluetooth"))] + &$enum_type::Bluez(ref bluez) => bluez.$function_name($value, None), + #[cfg(all(target_os = "android", feature = "bluetooth"))] + &$enum_type::Android(ref android) => android.$function_name($value), + #[cfg(all(target_os = "macos", feature = "bluetooth"))] + &$enum_type::Mac(ref mac) => mac.$function_name($value), + #[cfg(not(any(all(target_os = "linux", feature = "bluetooth"), + all(target_os = "android", feature = "bluetooth"), + all(target_os = "macos", feature = "bluetooth"))))] + &$enum_type::Empty(ref empty) => empty.$function_name($value), + #[cfg(feature = "bluetooth-test")] + &$enum_type::Mock(ref fake) => fake.$function_name($value), + } + }; +); + +#[cfg(feature = "bluetooth-test")] +macro_rules! get_inner_and_call_test_func { + ($enum_value: expr, $enum_type: ident, $function_name: ident, $value: expr) => { + match $enum_value { + &$enum_type::Mock(ref fake) => fake.$function_name($value), + _ => Err(Box::from( + "Error! Test functions are not supported on real devices!", + )), + } + }; + + ($enum_value: expr, $enum_type: ident, $function_name: ident) => { + match $enum_value { + &$enum_type::Mock(ref fake) => fake.$function_name(), + _ => Err(Box::from( + "Error! Test functions are not supported on real devices!", + )), + } + }; +} + +pub(crate) use get_inner_and_call; +#[cfg(feature = "bluetooth-test")] +pub(crate) use get_inner_and_call_test_func; diff --git a/components/bluetooth/test.rs b/components/bluetooth/test.rs index 25e4d9cbb7d..bf3b19b82cc 100644 --- a/components/bluetooth/test.rs +++ b/components/bluetooth/test.rs @@ -8,12 +8,12 @@ use std::collections::{HashMap, HashSet}; use std::error::Error; use std::string::String; -use device::bluetooth::{ +use uuid::Uuid; + +use crate::bluetooth::{ BluetoothAdapter, BluetoothDevice, BluetoothGATTCharacteristic, BluetoothGATTDescriptor, BluetoothGATTService, }; -use uuid::Uuid; - use crate::BluetoothManager; thread_local!(pub static CACHED_IDS: RefCell<HashSet<Uuid>> = RefCell::new(HashSet::new())); @@ -152,7 +152,7 @@ fn create_device( name: String, address: String, ) -> Result<BluetoothDevice, Box<dyn Error>> { - let device = BluetoothDevice::create_mock_device(adapter.clone(), generate_id().to_string())?; + let device = adapter.create_mock_device(generate_id().to_string())?; device.set_name(Some(name))?; device.set_address(address)?; device.set_connectable(true)?; diff --git a/third_party/blurmac/Cargo.lock b/third_party/blurmac/Cargo.lock new file mode 100644 index 00000000000..b3394b03adb --- /dev/null +++ b/third_party/blurmac/Cargo.lock @@ -0,0 +1,41 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "blurmac" +version = "0.1.0" +dependencies = [ + "log", + "objc", +] + +[[package]] +name = "libc" +version = "0.2.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] diff --git a/third_party/blurmac/Cargo.toml b/third_party/blurmac/Cargo.toml new file mode 100644 index 00000000000..84a051e4eb3 --- /dev/null +++ b/third_party/blurmac/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "blurmac" +description = "Bluetooth Rust lib using macOS CoreBluetooth" +version = "0.1.0" +readme = "README.md" +keywords = ["bluetooth", "ble", "macOS", "CoreBluetooth"] +repository = "https://github.com/akosthekiss/blurmac" +authors = ["Akos Kiss <akiss@inf.u-szeged.hu>"] +license = "BSD-3-Clause" + +[lib] +name = "blurmac" +path = "src/lib.rs" +crate-type = ["rlib"] + +[dependencies] +log = "0.4" +objc = "0.2" diff --git a/third_party/blurmac/LICENSE.md b/third_party/blurmac/LICENSE.md new file mode 100644 index 00000000000..6adf7cfc67b --- /dev/null +++ b/third_party/blurmac/LICENSE.md @@ -0,0 +1,27 @@ +Copyright (c) 2017 Akos Kiss. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/third_party/blurmac/README.md b/third_party/blurmac/README.md new file mode 100644 index 00000000000..ec6e536d66a --- /dev/null +++ b/third_party/blurmac/README.md @@ -0,0 +1,55 @@ +# Bluetooth Rust lib using macOS CoreBluetooth + +[](https://travis-ci.org/akosthekiss/blurmac) +[](https://crates.io/crates/blurmac) + +The main aim of BlurMac is to enable [WebBluetooth](https://webbluetoothcg.github.io) +in [Servo](https://github.com/servo/servo) on macOS. Thus, API and implementation +decisions are affected by the encapsulating [Devices](https://github.com/servo/devices), +and the sibling [BlurZ](https://github.com/szeged/blurz) and [BlurDroid](https://github.com/szeged/blurdroid) +crates. + + +## Run Servo with WebBluetooth Enabled + +Usually, you don't want to work with BlurMac on its own but use it within Servo. +So, most probably you'll want to run Servo with WebBluetooth enabled: + +``` +RUST_LOG=blurmac \ +./mach run \ + --dev \ + --pref=dom.bluetooth.enabled \ + --pref=dom.permissions.testing.allowed_in_nonsecure_contexts \ + URL +``` + +Notes: +* The above command is actually not really BlurMac-specific (except for the `RUST_LOG` + part). It runs Servo with WBT enabled on any platform where WBT is supported. +* You don't need the `RUST_LOG=blurmac` part if you don't want to see BlurMac debug + messages on the console. +* You don't need the `--dev` part if you want to run a release build. +* You don't need the `--pref=dom.permissions.testing.allowed_in_nonsecure_contexts` + part if your `URL` is https (but you do need it if you test a local file). + + +## Known Issues + +* Device RSSI can not be retrieved yet. +* Support for included services is incomplete. +* Descriptors are not supported yet. +* Notifications on characteristics are not supported yet (the limitation comes from + Devices). + + +## Compatibility + +Tested on: + +* macOS Sierra 10.12. + + +## Copyright and Licensing + +Licensed under the BSD 3-Clause [License](LICENSE.md). diff --git a/third_party/blurmac/src/adapter.rs b/third_party/blurmac/src/adapter.rs new file mode 100644 index 00000000000..eb629f3ea03 --- /dev/null +++ b/third_party/blurmac/src/adapter.rs @@ -0,0 +1,212 @@ +// Copyright (c) 2017 Akos Kiss. +// +// Licensed under the BSD 3-Clause License +// <LICENSE.md or https://opensource.org/licenses/BSD-3-Clause>. +// This file may not be copied, modified, or distributed except +// according to those terms. + +use std::error::Error; +use std::os::raw::c_int; + +use delegate::bm; +use framework::{cb, io, ns}; +use objc::runtime::{Object, YES}; +use utils::{nsx, NOT_SUPPORTED_ERROR}; + +#[derive(Clone, Debug)] +pub struct BluetoothAdapter { + pub(crate) manager: *mut Object, + pub(crate) delegate: *mut Object, +} +// TODO: implement std::fmt::Debug and/or std::fmt::Display instead of derive? + +unsafe impl Send for BluetoothAdapter {} +unsafe impl Sync for BluetoothAdapter {} + +impl BluetoothAdapter { + pub fn init() -> Result<BluetoothAdapter, Box<dyn Error>> { + trace!("BluetoothAdapter::init"); + let delegate = bm::delegate(); + let manager = cb::centralmanager(delegate); + let adapter = BluetoothAdapter { + manager: manager, + delegate: delegate, + }; + + // NOTE: start discovery at once, servo leaves close to no time to do a proper discovery + // in a BluetoothDiscoverySession + adapter.start_discovery().unwrap(); + + Ok(adapter) + } + + pub fn get_id(&self) -> String { + trace!("BluetoothAdapter::get_id"); + // NOTE: not aware of any better native ID than the address string + self.get_address().unwrap() + } + + pub fn get_name(&self) -> Result<String, Box<dyn Error>> { + trace!("BluetoothAdapter::get_name"); + let controller = io::bluetoothhostcontroller_defaultcontroller(); + let name = io::bluetoothhostcontroller_nameasstring(controller); + Ok(nsx::string_to_string(name)) + } + + pub fn get_address(&self) -> Result<String, Box<dyn Error>> { + trace!("BluetoothAdapter::get_address"); + let controller = io::bluetoothhostcontroller_defaultcontroller(); + let address = io::bluetoothhostcontroller_addressasstring(controller); + Ok(nsx::string_to_string(address)) + } + + pub fn get_class(&self) -> Result<u32, Box<dyn Error>> { + trace!("BluetoothAdapter::get_class"); + let controller = io::bluetoothhostcontroller_defaultcontroller(); + let device_class = io::bluetoothhostcontroller_classofdevice(controller); + Ok(device_class) + } + + pub fn is_powered(&self) -> Result<bool, Box<dyn Error>> { + trace!("BluetoothAdapter::is_powered"); + // NOTE: might be also available through + // [[IOBluetoothHostController defaultController] powerState], but that's readonly, so keep + // it in sync with set_powered + Ok(io::bluetoothpreferencegetcontrollerpowerstate() == 1) + } + + pub fn set_powered(&self, value: bool) -> Result<(), Box<dyn Error>> { + trace!("BluetoothAdapter::set_powered"); + io::bluetoothpreferencesetcontrollerpowerstate(value as c_int); + // TODO: wait for change to happen? whether it really happened? + Ok(()) + } + + pub fn is_discoverable(&self) -> Result<bool, Box<dyn Error>> { + trace!("BluetoothAdapter::is_discoverable"); + Ok(io::bluetoothpreferencegetdiscoverablestate() == 1) + } + + pub fn set_discoverable(&self, value: bool) -> Result<(), Box<dyn Error>> { + trace!("BluetoothAdapter::set_discoverable"); + io::bluetoothpreferencesetdiscoverablestate(value as c_int); + // TODO: wait for change to happen? whether it really happened? + Ok(()) + } + + pub fn get_device_list(&self) -> Result<Vec<String>, Box<dyn Error>> { + trace!("BluetoothAdapter::get_device_list"); + let mut v = vec![]; + let peripherals = bm::delegate_peripherals(self.delegate); + let keys = ns::dictionary_allkeys(peripherals); + for i in 0..ns::array_count(keys) { + v.push(nsx::string_to_string(ns::array_objectatindex(keys, i))); + } + Ok(v) + } + + // Was in BluetoothDiscoverySession + + fn start_discovery(&self) -> Result<(), Box<dyn Error>> { + trace!("BluetoothAdapter::start_discovery"); + let options = ns::mutabledictionary(); + // NOTE: If duplicates are not allowed then a peripheral will not show up again once + // connected and then disconnected. + ns::mutabledictionary_setobject_forkey(options, ns::number_withbool(YES), unsafe { + cb::CENTRALMANAGERSCANOPTIONALLOWDUPLICATESKEY + }); + cb::centralmanager_scanforperipherals_options(self.manager, options); + Ok(()) + } + + fn stop_discovery(&self) -> Result<(), Box<dyn Error>> { + trace!("BluetoothAdapter::stop_discovery"); + cb::centralmanager_stopscan(self.manager); + Ok(()) + } + + // Not supported + + pub fn get_alias(&self) -> Result<String, Box<dyn Error>> { + warn!("BluetoothAdapter::get_alias not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn set_alias(&self, _value: String) -> Result<(), Box<dyn Error>> { + warn!("BluetoothAdapter::set_alias not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn is_pairable(&self) -> Result<bool, Box<dyn Error>> { + warn!("BluetoothAdapter::is_pairable not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn set_pairable(&self, _value: bool) -> Result<(), Box<dyn Error>> { + warn!("BluetoothAdapter::set_pairable not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_pairable_timeout(&self) -> Result<u32, Box<dyn Error>> { + warn!("BluetoothAdapter::get_pairable_timeout not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn set_pairable_timeout(&self, _value: u32) -> Result<(), Box<dyn Error>> { + warn!("BluetoothAdapter::set_pairable_timeout not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_discoverable_timeout(&self) -> Result<u32, Box<dyn Error>> { + warn!("BluetoothAdapter::get_discoverable_timeout not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn set_discoverable_timeout(&self, _value: u32) -> Result<(), Box<dyn Error>> { + warn!("BluetoothAdapter::set_discoverable_timeout not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn is_discovering(&self) -> Result<bool, Box<dyn Error>> { + warn!("BluetoothAdapter::is_discovering not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_uuids(&self) -> Result<Vec<String>, Box<dyn Error>> { + warn!("BluetoothAdapter::get_uuids not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_vendor_id_source(&self) -> Result<String, Box<dyn Error>> { + warn!("BluetoothAdapter::get_vendor_id_source not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_vendor_id(&self) -> Result<u32, Box<dyn Error>> { + warn!("BluetoothAdapter::get_vendor_id not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_product_id(&self) -> Result<u32, Box<dyn Error>> { + warn!("BluetoothAdapter::get_product_id not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_device_id(&self) -> Result<u32, Box<dyn Error>> { + warn!("BluetoothAdapter::get_device_id not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_modalias(&self) -> Result<(String, u32, u32, u32), Box<dyn Error>> { + warn!("BluetoothAdapter::get_modalias not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } +} + +impl Drop for BluetoothAdapter { + fn drop(&mut self) { + trace!("BluetoothAdapter::drop"); + // NOTE: stop discovery only here instead of in BluetoothDiscoverySession + self.stop_discovery().unwrap(); + } +} diff --git a/third_party/blurmac/src/delegate.rs b/third_party/blurmac/src/delegate.rs new file mode 100644 index 00000000000..3a7b8615ae6 --- /dev/null +++ b/third_party/blurmac/src/delegate.rs @@ -0,0 +1,443 @@ +// Copyright (c) 2017 Akos Kiss. +// +// Licensed under the BSD 3-Clause License +// <LICENSE.md or https://opensource.org/licenses/BSD-3-Clause>. +// This file may not be copied, modified, or distributed except +// according to those terms. + +use std::error::Error; +use std::sync::Once; + +use framework::{cb, nil, ns}; +use objc::declare::ClassDecl; +use objc::runtime::{Class, Object, Protocol, Sel}; +use utils::{cbx, nsx, wait, NO_PERIPHERAL_FOUND}; + +pub mod bm { + use super::*; + + // BlurMacDelegate : CBCentralManagerDelegate, CBPeripheralDelegate + + const DELEGATE_PERIPHERALS_IVAR: &'static str = "_peripherals"; + + fn delegate_class() -> &'static Class { + trace!("delegate_class"); + static REGISTER_DELEGATE_CLASS: Once = Once::new(); + + REGISTER_DELEGATE_CLASS.call_once(|| { + let mut decl = ClassDecl::new("BlurMacDelegate", Class::get("NSObject").unwrap()).unwrap(); + decl.add_protocol(Protocol::get("CBCentralManagerDelegate").unwrap()); + + decl.add_ivar::<*mut Object>(DELEGATE_PERIPHERALS_IVAR); /* NSMutableDictionary<NSString*, BlurMacPeripheralData*>* */ + + unsafe { + decl.add_method(sel!(init), delegate_init as extern fn(&mut Object, Sel) -> *mut Object); + decl.add_method(sel!(centralManagerDidUpdateState:), delegate_centralmanagerdidupdatestate as extern fn(&mut Object, Sel, *mut Object)); + // decl.add_method(sel!(centralManager:willRestoreState:), delegate_centralmanager_willrestorestate as extern fn(&mut Object, Sel, *mut Object, *mut Object)); + decl.add_method(sel!(centralManager:didConnectPeripheral:), delegate_centralmanager_didconnectperipheral as extern fn(&mut Object, Sel, *mut Object, *mut Object)); + decl.add_method(sel!(centralManager:didDisconnectPeripheral:error:), delegate_centralmanager_diddisconnectperipheral_error as extern fn(&mut Object, Sel, *mut Object, *mut Object, *mut Object)); + // decl.add_method(sel!(centralManager:didFailToConnectPeripheral:error:), delegate_centralmanager_didfailtoconnectperipheral_error as extern fn(&mut Object, Sel, *mut Object, *mut Object, *mut Object)); + decl.add_method(sel!(centralManager:didDiscoverPeripheral:advertisementData:RSSI:), delegate_centralmanager_diddiscoverperipheral_advertisementdata_rssi as extern fn(&mut Object, Sel, *mut Object, *mut Object, *mut Object, *mut Object)); + + decl.add_method(sel!(peripheral:didDiscoverServices:), delegate_peripheral_diddiscoverservices as extern fn(&mut Object, Sel, *mut Object, *mut Object)); + decl.add_method(sel!(peripheral:didDiscoverIncludedServicesForService:error:), delegate_peripheral_diddiscoverincludedservicesforservice_error as extern fn(&mut Object, Sel, *mut Object, *mut Object, *mut Object)); + decl.add_method(sel!(peripheral:didDiscoverCharacteristicsForService:error:), delegate_peripheral_diddiscovercharacteristicsforservice_error as extern fn(&mut Object, Sel, *mut Object, *mut Object, *mut Object)); + decl.add_method(sel!(peripheral:didUpdateValueForCharacteristic:error:), delegate_peripheral_didupdatevalueforcharacteristic_error as extern fn(&mut Object, Sel, *mut Object, *mut Object, *mut Object)); + decl.add_method(sel!(peripheral:didWriteValueForCharacteristic:error:), delegate_peripheral_didwritevalueforcharacteristic_error as extern fn(&mut Object, Sel, *mut Object, *mut Object, *mut Object)); + decl.add_method(sel!(peripheral:didReadRSSI:error:), delegate_peripheral_didreadrssi_error as extern fn(&mut Object, Sel, *mut Object, *mut Object, *mut Object)); + } + + decl.register(); + }); + + Class::get("BlurMacDelegate").unwrap() + } + + extern "C" fn delegate_init(delegate: &mut Object, _cmd: Sel) -> *mut Object { + trace!("delegate_init"); + unsafe { + delegate.set_ivar::<*mut Object>(DELEGATE_PERIPHERALS_IVAR, ns::mutabledictionary()); + } + delegate + } + + extern "C" fn delegate_centralmanagerdidupdatestate( + _delegate: &mut Object, + _cmd: Sel, + _central: *mut Object, + ) { + trace!("delegate_centralmanagerdidupdatestate"); + // NOTE: this is a no-op but kept because it is a required method of the protocol + } + + // extern fn delegate_centralmanager_willrestorestate(_delegate: &mut Object, _cmd: Sel, _central: *mut Object, _dict: *mut Object) { + // trace!("delegate_centralmanager_willrestorestate"); + // } + + extern "C" fn delegate_centralmanager_didconnectperipheral( + delegate: &mut Object, + _cmd: Sel, + _central: *mut Object, + peripheral: *mut Object, + ) { + trace!( + "delegate_centralmanager_didconnectperipheral {}", + cbx::peripheral_debug(peripheral) + ); + cb::peripheral_setdelegate(peripheral, delegate); + cb::peripheral_discoverservices(peripheral); + } + + extern "C" fn delegate_centralmanager_diddisconnectperipheral_error( + delegate: &mut Object, + _cmd: Sel, + _central: *mut Object, + peripheral: *mut Object, + _error: *mut Object, + ) { + trace!( + "delegate_centralmanager_diddisconnectperipheral_error {}", + cbx::peripheral_debug(peripheral) + ); + ns::mutabledictionary_removeobjectforkey( + delegate_peripherals(delegate), + ns::uuid_uuidstring(cb::peer_identifier(peripheral)), + ); + } + + // extern fn delegate_centralmanager_didfailtoconnectperipheral_error(_delegate: &mut Object, _cmd: Sel, _central: *mut Object, _peripheral: *mut Object, _error: *mut Object) { + // trace!("delegate_centralmanager_didfailtoconnectperipheral_error"); + // } + + extern "C" fn delegate_centralmanager_diddiscoverperipheral_advertisementdata_rssi( + delegate: &mut Object, + _cmd: Sel, + _central: *mut Object, + peripheral: *mut Object, + adv_data: *mut Object, + rssi: *mut Object, + ) { + trace!( + "delegate_centralmanager_diddiscoverperipheral_advertisementdata_rssi {}", + cbx::peripheral_debug(peripheral) + ); + let peripherals = delegate_peripherals(delegate); + let uuid_nsstring = ns::uuid_uuidstring(cb::peer_identifier(peripheral)); + let mut data = ns::dictionary_objectforkey(peripherals, uuid_nsstring); + if data == nil { + data = ns::mutabledictionary(); + ns::mutabledictionary_setobject_forkey(peripherals, data, uuid_nsstring); + } + + ns::mutabledictionary_setobject_forkey( + data, + ns::object_copy(peripheral), + nsx::string_from_str(PERIPHERALDATA_PERIPHERALKEY), + ); + + ns::mutabledictionary_setobject_forkey( + data, + rssi, + nsx::string_from_str(PERIPHERALDATA_RSSIKEY), + ); + + let cbuuids_nsarray = + ns::dictionary_objectforkey(adv_data, unsafe { cb::ADVERTISEMENTDATASERVICEUUIDSKEY }); + if cbuuids_nsarray != nil { + ns::mutabledictionary_setobject_forkey( + data, + cbuuids_nsarray, + nsx::string_from_str(PERIPHERALDATA_UUIDSKEY), + ); + } + + if ns::dictionary_objectforkey(data, nsx::string_from_str(PERIPHERALDATA_EVENTSKEY)) == nil + { + ns::mutabledictionary_setobject_forkey( + data, + ns::mutabledictionary(), + nsx::string_from_str(PERIPHERALDATA_EVENTSKEY), + ); + } + } + + extern "C" fn delegate_peripheral_diddiscoverservices( + delegate: &mut Object, + _cmd: Sel, + peripheral: *mut Object, + error: *mut Object, + ) { + trace!( + "delegate_peripheral_diddiscoverservices {} {}", + cbx::peripheral_debug(peripheral), + if error != nil { "error" } else { "" } + ); + if error == nil { + let services = cb::peripheral_services(peripheral); + for i in 0..ns::array_count(services) { + let s = ns::array_objectatindex(services, i); + cb::peripheral_discovercharacteristicsforservice(peripheral, s); + cb::peripheral_discoverincludedservicesforservice(peripheral, s); + } + + // Notify BluetoothDevice::get_gatt_services that discovery was successful. + match bmx::peripheralevents(delegate, peripheral) { + Ok(events) => ns::mutabledictionary_setobject_forkey( + events, + wait::now(), + nsx::string_from_str(PERIPHERALEVENT_SERVICESDISCOVEREDKEY), + ), + Err(_) => {}, + } + } + } + + extern "C" fn delegate_peripheral_diddiscoverincludedservicesforservice_error( + delegate: &mut Object, + _cmd: Sel, + peripheral: *mut Object, + service: *mut Object, + error: *mut Object, + ) { + trace!( + "delegate_peripheral_diddiscoverincludedservicesforservice_error {} {} {}", + cbx::peripheral_debug(peripheral), + cbx::service_debug(service), + if error != nil { "error" } else { "" } + ); + if error == nil { + let includes = cb::service_includedservices(service); + for i in 0..ns::array_count(includes) { + let s = ns::array_objectatindex(includes, i); + cb::peripheral_discovercharacteristicsforservice(peripheral, s); + } + + // Notify BluetoothGATTService::get_includes that discovery was successful. + match bmx::peripheralevents(delegate, peripheral) { + Ok(events) => ns::mutabledictionary_setobject_forkey( + events, + wait::now(), + bmx::includedservicesdiscoveredkey(service), + ), + Err(_) => {}, + } + } + } + + extern "C" fn delegate_peripheral_diddiscovercharacteristicsforservice_error( + delegate: &mut Object, + _cmd: Sel, + peripheral: *mut Object, + service: *mut Object, + error: *mut Object, + ) { + trace!( + "delegate_peripheral_diddiscovercharacteristicsforservice_error {} {} {}", + cbx::peripheral_debug(peripheral), + cbx::service_debug(service), + if error != nil { "error" } else { "" } + ); + if error == nil { + let chars = cb::service_characteristics(service); + for i in 0..ns::array_count(chars) { + let c = ns::array_objectatindex(chars, i); + cb::peripheral_discoverdescriptorsforcharacteristic(peripheral, c); + } + + // Notify BluetoothGATTService::get_gatt_characteristics that discovery was successful. + match bmx::peripheralevents(delegate, peripheral) { + Ok(events) => ns::mutabledictionary_setobject_forkey( + events, + wait::now(), + bmx::characteristicsdiscoveredkey(service), + ), + Err(_) => {}, + } + } + } + + extern "C" fn delegate_peripheral_didupdatevalueforcharacteristic_error( + delegate: &mut Object, + _cmd: Sel, + peripheral: *mut Object, + characteristic: *mut Object, + error: *mut Object, + ) { + trace!( + "delegate_peripheral_didupdatevalueforcharacteristic_error {} {} {}", + cbx::peripheral_debug(peripheral), + cbx::characteristic_debug(characteristic), + if error != nil { "error" } else { "" } + ); + if error == nil { + // Notify BluetoothGATTCharacteristic::read_value that read was successful. + match bmx::peripheralevents(delegate, peripheral) { + Ok(events) => ns::mutabledictionary_setobject_forkey( + events, + wait::now(), + bmx::valueupdatedkey(characteristic), + ), + Err(_) => {}, + } + } + } + + extern "C" fn delegate_peripheral_didwritevalueforcharacteristic_error( + delegate: &mut Object, + _cmd: Sel, + peripheral: *mut Object, + characteristic: *mut Object, + error: *mut Object, + ) { + trace!( + "delegate_peripheral_didwritevalueforcharacteristic_error {} {} {}", + cbx::peripheral_debug(peripheral), + cbx::characteristic_debug(characteristic), + if error != nil { "error" } else { "" } + ); + if error == nil { + // Notify BluetoothGATTCharacteristic::write_value that write was successful. + match bmx::peripheralevents(delegate, peripheral) { + Ok(events) => ns::mutabledictionary_setobject_forkey( + events, + wait::now(), + bmx::valuewrittenkey(characteristic), + ), + Err(_) => {}, + } + } + } + + // extern fn delegate_peripheral_didupdatenotificationstateforcharacteristic_error(_delegate: &mut Object, _cmd: Sel, _peripheral: *mut Object, _characteristic: *mut Object, _error: *mut Object) { + // trace!("delegate_peripheral_didupdatenotificationstateforcharacteristic_error"); + // // TODO: this is where notifications should be handled... + // } + + // extern fn delegate_peripheral_diddiscoverdescriptorsforcharacteristic_error(_delegate: &mut Object, _cmd: Sel, _peripheral: *mut Object, _characteristic: *mut Object, _error: *mut Object) { + // trace!("delegate_peripheral_diddiscoverdescriptorsforcharacteristic_error"); + // } + + // extern fn delegate_peripheral_didupdatevaluefordescriptor(_delegate: &mut Object, _cmd: Sel, _peripheral: *mut Object, _descriptor: *mut Object, _error: *mut Object) { + // trace!("delegate_peripheral_didupdatevaluefordescriptor"); + // } + + // extern fn delegate_peripheral_didwritevaluefordescriptor_error(_delegate: &mut Object, _cmd: Sel, _peripheral: *mut Object, _descriptor: *mut Object, _error: *mut Object) { + // trace!("delegate_peripheral_didwritevaluefordescriptor_error"); + // } + + extern "C" fn delegate_peripheral_didreadrssi_error( + delegate: &mut Object, + _cmd: Sel, + peripheral: *mut Object, + rssi: *mut Object, + error: *mut Object, + ) { + trace!( + "delegate_peripheral_didreadrssi_error {}", + cbx::peripheral_debug(peripheral) + ); + if error == nil { + let peripherals = delegate_peripherals(delegate); + let uuid_nsstring = ns::uuid_uuidstring(cb::peer_identifier(peripheral)); + let data = ns::dictionary_objectforkey(peripherals, uuid_nsstring); + if data != nil { + ns::mutabledictionary_setobject_forkey( + data, + rssi, + nsx::string_from_str(PERIPHERALDATA_RSSIKEY), + ); + } + } + } + + pub fn delegate() -> *mut Object { + unsafe { + let mut delegate: *mut Object = msg_send![delegate_class(), alloc]; + delegate = msg_send![delegate, init]; + delegate + } + } + + pub fn delegate_peripherals(delegate: *mut Object) -> *mut Object { + unsafe { + let peripherals: *mut Object = + *(&mut *delegate).get_ivar::<*mut Object>(DELEGATE_PERIPHERALS_IVAR); + peripherals + } + } + + // "BlurMacPeripheralData" = NSMutableDictionary<NSString*, id> + + pub const PERIPHERALDATA_PERIPHERALKEY: &'static str = "peripheral"; + pub const PERIPHERALDATA_RSSIKEY: &'static str = "rssi"; + pub const PERIPHERALDATA_UUIDSKEY: &'static str = "uuids"; + pub const PERIPHERALDATA_EVENTSKEY: &'static str = "events"; + + pub const PERIPHERALEVENT_SERVICESDISCOVEREDKEY: &'static str = "services"; + pub const PERIPHERALEVENT_INCLUDEDSERVICESDISCOVEREDKEYSUFFIX: &'static str = ":includes"; + pub const PERIPHERALEVENT_CHARACTERISTICSDISCOVEREDKEYSUFFIX: &'static str = ":characteristics"; + pub const PERIPHERALEVENT_VALUEUPDATEDKEYSUFFIX: &'static str = ":updated"; + pub const PERIPHERALEVENT_VALUEWRITTENKEYSUFFIX: &'static str = ":written"; +} + +pub mod bmx { + use super::*; + + pub fn peripheraldata( + delegate: *mut Object, + peripheral: *mut Object, + ) -> Result<*mut Object, Box<dyn Error>> { + let peripherals = bm::delegate_peripherals(delegate); + let data = ns::dictionary_objectforkey( + peripherals, + ns::uuid_uuidstring(cb::peer_identifier(peripheral)), + ); + if data == nil { + warn!("peripheraldata -> NOT FOUND"); + return Err(Box::from(NO_PERIPHERAL_FOUND)); + } + Ok(data) + } + + pub fn peripheralevents( + delegate: *mut Object, + peripheral: *mut Object, + ) -> Result<*mut Object, Box<dyn Error>> { + let data = peripheraldata(delegate, peripheral)?; + Ok(ns::dictionary_objectforkey( + data, + nsx::string_from_str(bm::PERIPHERALDATA_EVENTSKEY), + )) + } + + pub fn includedservicesdiscoveredkey(service: *mut Object) -> *mut Object { + suffixedkey( + service, + bm::PERIPHERALEVENT_INCLUDEDSERVICESDISCOVEREDKEYSUFFIX, + ) + } + + pub fn characteristicsdiscoveredkey(service: *mut Object) -> *mut Object { + suffixedkey( + service, + bm::PERIPHERALEVENT_CHARACTERISTICSDISCOVEREDKEYSUFFIX, + ) + } + + pub fn valueupdatedkey(characteristic: *mut Object) -> *mut Object { + suffixedkey(characteristic, bm::PERIPHERALEVENT_VALUEUPDATEDKEYSUFFIX) + } + + pub fn valuewrittenkey(characteristic: *mut Object) -> *mut Object { + suffixedkey(characteristic, bm::PERIPHERALEVENT_VALUEWRITTENKEYSUFFIX) + } + + fn suffixedkey(attribute: *mut Object, suffix: &str) -> *mut Object { + let key = format!( + "{}{}", + cbx::uuid_to_canonical_uuid_string(cb::attribute_uuid(attribute)), + suffix + ); + nsx::string_from_str(key.as_str()) + } +} diff --git a/third_party/blurmac/src/device.rs b/third_party/blurmac/src/device.rs new file mode 100644 index 00000000000..583eefba31d --- /dev/null +++ b/third_party/blurmac/src/device.rs @@ -0,0 +1,280 @@ +// Copyright (c) 2017 Akos Kiss. +// +// Licensed under the BSD 3-Clause License +// <LICENSE.md or https://opensource.org/licenses/BSD-3-Clause>. +// This file may not be copied, modified, or distributed except +// according to those terms. + +use std::collections::HashMap; +use std::error::Error; +use std::sync::Arc; + +use adapter::BluetoothAdapter; +use delegate::{bm, bmx}; +use framework::{cb, nil, ns}; +use objc::runtime::Object; +use utils::{cbx, nsx, wait, NOT_SUPPORTED_ERROR, NO_PERIPHERAL_FOUND}; + +#[derive(Clone, Debug)] +pub struct BluetoothDevice { + pub(crate) adapter: Arc<BluetoothAdapter>, + pub(crate) peripheral: *mut Object, +} +// TODO: implement std::fmt::Debug and/or std::fmt::Display instead of derive? + +impl BluetoothDevice { + pub fn new(adapter: Arc<BluetoothAdapter>, uuid: String) -> BluetoothDevice { + trace!("BluetoothDevice::new"); + // NOTE: It can happen that there is no peripheral for the given UUID, in that case + // self.peripheral will be nil and all methods that return a Result will return + // Err(Box::from(NO_PERIPHERAL_FOUND)), while others will return some meaningless value. + let peripheral = Self::peripheral_by_uuid(adapter.delegate, &uuid); + + if peripheral == nil { + warn!("BluetoothDevice::new found no peripheral for UUID {}", uuid); + } + + BluetoothDevice { + adapter: adapter.clone(), + peripheral: peripheral, + } + } + + fn peripheral_by_uuid(delegate: *mut Object, uuid: &String) -> *mut Object { + let peripherals = bm::delegate_peripherals(delegate); + let keys = ns::dictionary_allkeys(peripherals); + for i in 0..ns::array_count(keys) { + let uuid_nsstring = ns::array_objectatindex(keys, i); + if nsx::string_to_string(uuid_nsstring) == *uuid { + let data = ns::dictionary_objectforkey(peripherals, uuid_nsstring); + return ns::dictionary_objectforkey( + data, + nsx::string_from_str(bm::PERIPHERALDATA_PERIPHERALKEY), + ); + } + } + nil + } + + pub fn get_id(&self) -> String { + trace!("BluetoothDevice::get_id -> get_address"); + self.get_address().unwrap_or(String::new()) + } + + pub fn get_address(&self) -> Result<String, Box<dyn Error>> { + trace!("BluetoothDevice::get_address"); + if self.peripheral == nil { + return Err(Box::from(NO_PERIPHERAL_FOUND)); + } + + // NOTE: There is no better substitute for address than identifier. + let uuid_string = + nsx::string_to_string(ns::uuid_uuidstring(cb::peer_identifier(self.peripheral))); + debug!("BluetoothDevice::get_address -> {}", uuid_string); + Ok(uuid_string) + } + + pub fn get_name(&self) -> Result<String, Box<dyn Error>> { + trace!("BluetoothDevice::get_name"); + if self.peripheral == nil { + return Err(Box::from(NO_PERIPHERAL_FOUND)); + } + + let name_nsstring = cb::peripheral_name(self.peripheral); + let name = if name_nsstring != nil { + nsx::string_to_string(name_nsstring) + } else { + String::from("") + }; + debug!("BluetoothDevice::get_name -> {}", name); + Ok(name) + } + + pub fn get_uuids(&self) -> Result<Vec<String>, Box<dyn Error>> { + trace!("BluetoothDevice::get_uuids"); + if self.peripheral == nil { + return Err(Box::from(NO_PERIPHERAL_FOUND)); + } + + let data = bmx::peripheraldata(self.adapter.delegate, self.peripheral)?; + let mut v = vec![]; + let cbuuids_nsarray = + ns::dictionary_objectforkey(data, nsx::string_from_str(bm::PERIPHERALDATA_UUIDSKEY)); + if cbuuids_nsarray != nil { + for i in 0..ns::array_count(cbuuids_nsarray) { + v.push(cbx::uuid_to_canonical_uuid_string(ns::array_objectatindex( + cbuuids_nsarray, + i, + ))); + } + } + debug!("BluetoothDevice::get_uuids -> {:?}", v); + Ok(v) + } + + pub fn connect(&self) -> Result<(), Box<dyn Error>> { + trace!("BluetoothDevice::connect"); + if self.peripheral == nil { + return Err(Box::from(NO_PERIPHERAL_FOUND)); + } + + cb::centralmanager_connectperipheral(self.adapter.manager, self.peripheral); + Ok(()) + } + + pub fn disconnect(&self) -> Result<(), Box<dyn Error>> { + trace!("BluetoothDevice::disconnect"); + if self.peripheral == nil { + return Err(Box::from(NO_PERIPHERAL_FOUND)); + } + + cb::centralmanager_cancelperipheralconnection(self.adapter.manager, self.peripheral); + Ok(()) + } + + pub fn is_connected(&self) -> Result<bool, Box<dyn Error>> { + trace!("BluetoothDevice::is_connected"); + if self.peripheral == nil { + return Err(Box::from(NO_PERIPHERAL_FOUND)); + } + + let state = cb::peripheral_state(self.peripheral); + debug!("BluetoothDevice::is_connected -> {}", state); + Ok(state == cb::PERIPHERALSTATE_CONNECTED) + } + + pub fn get_gatt_services(&self) -> Result<Vec<String>, Box<dyn Error>> { + trace!("BluetoothDevice::get_gatt_services"); + if self.peripheral == nil { + return Err(Box::from(NO_PERIPHERAL_FOUND)); + } + + let events = bmx::peripheralevents(self.adapter.delegate, self.peripheral)?; + let key = nsx::string_from_str(bm::PERIPHERALEVENT_SERVICESDISCOVEREDKEY); + wait::wait_or_timeout(|| ns::dictionary_objectforkey(events, key) != nil)?; + + let mut v = vec![]; + let services = cb::peripheral_services(self.peripheral); + for i in 0..ns::array_count(services) { + let uuid_string = cbx::uuid_to_canonical_uuid_string(cb::attribute_uuid( + ns::array_objectatindex(services, i), + )); + v.push(uuid_string); + } + debug!("BluetoothDevice::get_gatt_services -> {:?}", v); + Ok(v) + } + + // Not supported + + pub fn get_rssi(&self) -> Result<i16, Box<dyn Error>> { + warn!("BluetoothDevice::get_rssi not supported by BlurMac"); + // TODO: Now available from peripheral data in BluetoothAdapter. + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_tx_power(&self) -> Result<i16, Box<dyn Error>> { + warn!("BluetoothDevice::get_tx_power not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_manufacturer_data(&self) -> Result<HashMap<u16, Vec<u8>>, Box<dyn Error>> { + warn!("BluetoothDevice::get_manufacturer_data not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_service_data(&self) -> Result<HashMap<String, Vec<u8>>, Box<dyn Error>> { + warn!("BluetoothDevice::get_service_data not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_icon(&self) -> Result<String, Box<dyn Error>> { + warn!("BluetoothDevice::get_icon not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_class(&self) -> Result<u32, Box<dyn Error>> { + warn!("BluetoothDevice::get_class not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_appearance(&self) -> Result<u16, Box<dyn Error>> { + warn!("BluetoothDevice::get_appearance not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn is_paired(&self) -> Result<bool, Box<dyn Error>> { + warn!("BluetoothDevice::is_paired not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn is_trusted(&self) -> Result<bool, Box<dyn Error>> { + warn!("BluetoothDevice::is_trusted not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn is_blocked(&self) -> Result<bool, Box<dyn Error>> { + warn!("BluetoothDevice::is_blocked not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_alias(&self) -> Result<String, Box<dyn Error>> { + warn!("BluetoothDevice::get_alias not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn set_alias(&self, _value: String) -> Result<(), Box<dyn Error>> { + warn!("BluetoothDevice::set_alias not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn is_legacy_pairing(&self) -> Result<bool, Box<dyn Error>> { + warn!("BluetoothDevice::is_legacy_pairing not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_vendor_id_source(&self) -> Result<String, Box<dyn Error>> { + warn!("BluetoothDevice::get_vendor_id_source not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_vendor_id(&self) -> Result<u32, Box<dyn Error>> { + warn!("BluetoothDevice::get_vendor_id not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_product_id(&self) -> Result<u32, Box<dyn Error>> { + warn!("BluetoothDevice::get_product_id not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_device_id(&self) -> Result<u32, Box<dyn Error>> { + warn!("BluetoothDevice::get_device_id not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_modalias(&self) -> Result<(String, u32, u32, u32), Box<dyn Error>> { + warn!("BluetoothDevice::get_modalias not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn connect_profile(&self, _uuid: String) -> Result<(), Box<dyn Error>> { + warn!("BluetoothDevice::connect_profile not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn disconnect_profile(&self, _uuid: String) -> Result<(), Box<dyn Error>> { + warn!("BluetoothDevice::disconnect_profile not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn pair(&self) -> Result<(), Box<dyn Error>> { + warn!("BluetoothDevice::pair not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn cancel_pairing(&self) -> Result<(), Box<dyn Error>> { + warn!("BluetoothDevice::cancel_pairing not supported by BlurMac"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } +} diff --git a/third_party/blurmac/src/discovery_session.rs b/third_party/blurmac/src/discovery_session.rs new file mode 100644 index 00000000000..89da90803f7 --- /dev/null +++ b/third_party/blurmac/src/discovery_session.rs @@ -0,0 +1,39 @@ +// Copyright (c) 2017 Akos Kiss. +// +// Licensed under the BSD 3-Clause License +// <LICENSE.md or https://opensource.org/licenses/BSD-3-Clause>. +// This file may not be copied, modified, or distributed except +// according to those terms. + +use std::error::Error; +use std::sync::Arc; + +use adapter::BluetoothAdapter; + +#[derive(Clone, Debug)] +pub struct BluetoothDiscoverySession { + // pub(crate) adapter: Arc<BluetoothAdapter>, +} + +impl BluetoothDiscoverySession { + pub fn create_session( + _adapter: Arc<BluetoothAdapter>, + ) -> Result<BluetoothDiscoverySession, Box<dyn Error>> { + trace!("BluetoothDiscoverySession::create_session"); + Ok(BluetoothDiscoverySession { + // adapter: adapter.clone() + }) + } + + pub fn start_discovery(&self) -> Result<(), Box<dyn Error>> { + trace!("BluetoothDiscoverySession::start_discovery"); + // NOTE: discovery is started by BluetoothAdapter::new to allow devices to pop up + Ok(()) + } + + pub fn stop_discovery(&self) -> Result<(), Box<dyn Error>> { + trace!("BluetoothDiscoverySession::stop_discovery"); + // NOTE: discovery is only stopped when BluetoothAdapter is dropped + Ok(()) + } +} diff --git a/third_party/blurmac/src/framework.rs b/third_party/blurmac/src/framework.rs new file mode 100644 index 00000000000..a441b9ecb18 --- /dev/null +++ b/third_party/blurmac/src/framework.rs @@ -0,0 +1,490 @@ +// Copyright (c) 2017 Akos Kiss. +// +// Licensed under the BSD 3-Clause License +// <LICENSE.md or https://opensource.org/licenses/BSD-3-Clause>. +// This file may not be copied, modified, or distributed except +// according to those terms. + +use std::os::raw::{c_char, c_int, c_uint}; + +use objc::runtime::{Class, Object, BOOL}; + +#[allow(non_upper_case_globals)] +pub const nil: *mut Object = 0 as *mut Object; + +pub mod ns { + use super::*; + + // NSObject + + pub fn object_copy(nsobject: *mut Object) -> *mut Object { + unsafe { + let copy: *mut Object = msg_send![nsobject, copy]; + copy + } + } + + // NSNumber + + pub fn number_withbool(value: BOOL) -> *mut Object { + unsafe { + let nsnumber: *mut Object = + msg_send![Class::get("NSNumber").unwrap(), numberWithBool: value]; + nsnumber + } + } + + pub fn number_withunsignedlonglong(value: u64) -> *mut Object { + unsafe { + let nsnumber: *mut Object = msg_send![ + Class::get("NSNumber").unwrap(), + numberWithUnsignedLongLong: value + ]; + nsnumber + } + } + + pub fn number_unsignedlonglongvalue(nsnumber: *mut Object) -> u64 { + unsafe { + let value: u64 = msg_send![nsnumber, unsignedLongLongValue]; + value + } + } + + // NSString + + pub fn string(cstring: *const c_char) -> *mut Object /* NSString* */ { + unsafe { + let nsstring: *mut Object = msg_send![ + Class::get("NSString").unwrap(), + stringWithUTF8String: cstring + ]; + nsstring + } + } + + pub fn string_utf8string(nsstring: *mut Object) -> *const c_char { + unsafe { + let utf8string: *const c_char = msg_send![nsstring, UTF8String]; + utf8string + } + } + + // NSArray + + pub fn array_count(nsarray: *mut Object) -> c_uint { + unsafe { + let count: c_uint = msg_send![nsarray, count]; + count + } + } + + pub fn array_objectatindex(nsarray: *mut Object, index: c_uint) -> *mut Object { + unsafe { + let object: *mut Object = msg_send![nsarray, objectAtIndex: index]; + object + } + } + + // NSDictionary + + pub fn dictionary_allkeys(nsdict: *mut Object) -> *mut Object /* NSArray* */ { + unsafe { + let keys: *mut Object = msg_send![nsdict, allKeys]; + keys + } + } + + pub fn dictionary_objectforkey(nsdict: *mut Object, key: *mut Object) -> *mut Object { + unsafe { + let object: *mut Object = msg_send![nsdict, objectForKey: key]; + object + } + } + + // NSMutableDictionary : NSDictionary + + pub fn mutabledictionary() -> *mut Object { + unsafe { + let nsmutdict: *mut Object = + msg_send![Class::get("NSMutableDictionary").unwrap(), dictionaryWithCapacity:0]; + nsmutdict + } + } + + pub fn mutabledictionary_removeobjectforkey(nsmutdict: *mut Object, key: *mut Object) { + unsafe { + let () = msg_send![nsmutdict, removeObjectForKey: key]; + } + } + + pub fn mutabledictionary_setobject_forkey( + nsmutdict: *mut Object, + object: *mut Object, + key: *mut Object, + ) { + unsafe { + let () = msg_send![nsmutdict, setObject:object forKey:key]; + } + } + + // NSData + + pub fn data(bytes: *const u8, length: c_uint) -> *mut Object /* NSData* */ { + unsafe { + let data: *mut Object = + msg_send![Class::get("NSData").unwrap(), dataWithBytes:bytes length:length]; + data + } + } + + pub fn data_length(nsdata: *mut Object) -> c_uint { + unsafe { + let length: c_uint = msg_send![nsdata, length]; + length + } + } + + pub fn data_bytes(nsdata: *mut Object) -> *const u8 { + unsafe { + let bytes: *const u8 = msg_send![nsdata, bytes]; + bytes + } + } + + // NSUUID + + pub fn uuid_uuidstring(nsuuid: *mut Object) -> *mut Object /* NSString* */ { + unsafe { + let uuidstring: *mut Object = msg_send![nsuuid, UUIDString]; + uuidstring + } + } +} + +pub mod io { + use super::*; + + #[link(name = "IOBluetooth", kind = "framework")] + extern "C" { + pub fn IOBluetoothPreferenceGetControllerPowerState() -> c_int; + pub fn IOBluetoothPreferenceSetControllerPowerState(state: c_int); + + pub fn IOBluetoothPreferenceGetDiscoverableState() -> c_int; + pub fn IOBluetoothPreferenceSetDiscoverableState(state: c_int); + } + + // IOBluetoothHostController + + pub fn bluetoothhostcontroller_defaultcontroller() -> *mut Object /* IOBluetoothHostController* */ + { + unsafe { + let defaultcontroller: *mut Object = msg_send![ + Class::get("IOBluetoothHostController").unwrap(), + defaultController + ]; + defaultcontroller + } + } + + pub fn bluetoothhostcontroller_nameasstring(iobthc: *mut Object) -> *mut Object /* NSString* */ + { + unsafe { + let name: *mut Object = msg_send![iobthc, nameAsString]; + name + } + } + + pub fn bluetoothhostcontroller_addressasstring(iobthc: *mut Object) -> *mut Object /* NSString* */ + { + unsafe { + let address: *mut Object = msg_send![iobthc, addressAsString]; + address + } + } + + pub fn bluetoothhostcontroller_classofdevice(iobthc: *mut Object) -> u32 { + unsafe { + let classofdevice: u32 = msg_send![iobthc, classOfDevice]; + classofdevice + } + } + + // IOBluetoothPreference... + + pub fn bluetoothpreferencegetcontrollerpowerstate() -> c_int { + unsafe { IOBluetoothPreferenceGetControllerPowerState() } + } + + pub fn bluetoothpreferencesetcontrollerpowerstate(state: c_int) { + unsafe { + IOBluetoothPreferenceSetControllerPowerState(state); + } + } + + pub fn bluetoothpreferencegetdiscoverablestate() -> c_int { + unsafe { IOBluetoothPreferenceGetDiscoverableState() } + } + + pub fn bluetoothpreferencesetdiscoverablestate(state: c_int) { + unsafe { + IOBluetoothPreferenceSetDiscoverableState(state); + } + } +} + +pub mod cb { + use super::*; + + mod link { + use super::*; + + #[link(name = "CoreBluetooth", kind = "framework")] + extern "C" { + pub static CBAdvertisementDataServiceUUIDsKey: *mut Object; + + pub static CBCentralManagerScanOptionAllowDuplicatesKey: *mut Object; + } + } + + // CBCentralManager + + pub fn centralmanager(delegate: *mut Object, /*CBCentralManagerDelegate* */) -> *mut Object /*CBCentralManager* */ + { + unsafe { + let cbcentralmanager: *mut Object = + msg_send![Class::get("CBCentralManager").unwrap(), alloc]; + let () = msg_send![cbcentralmanager, initWithDelegate:delegate queue:nil]; + cbcentralmanager + } + } + + pub fn centralmanager_scanforperipherals_options( + cbcentralmanager: *mut Object, + options: *mut Object, /* NSDictionary<NSString*,id> */ + ) { + unsafe { + let () = + msg_send![cbcentralmanager, scanForPeripheralsWithServices:nil options:options]; + } + } + + pub fn centralmanager_stopscan(cbcentralmanager: *mut Object) { + unsafe { + let () = msg_send![cbcentralmanager, stopScan]; + } + } + + pub fn centralmanager_connectperipheral( + cbcentralmanager: *mut Object, + peripheral: *mut Object, /* CBPeripheral* */ + ) { + unsafe { + let () = msg_send![cbcentralmanager, connectPeripheral:peripheral options:nil]; + } + } + + pub fn centralmanager_cancelperipheralconnection( + cbcentralmanager: *mut Object, + peripheral: *mut Object, /* CBPeripheral* */ + ) { + unsafe { + let () = msg_send![cbcentralmanager, cancelPeripheralConnection: peripheral]; + } + } + + // CBPeer + + pub fn peer_identifier(cbpeer: *mut Object) -> *mut Object /* NSUUID* */ { + unsafe { + let identifier: *mut Object = msg_send![cbpeer, identifier]; + identifier + } + } + + // CBPeripheral : CBPeer + + pub fn peripheral_name(cbperipheral: *mut Object) -> *mut Object /* NSString* */ { + unsafe { + let name: *mut Object = msg_send![cbperipheral, name]; + name + } + } + + pub fn peripheral_state(cbperipheral: *mut Object) -> c_int { + unsafe { + let state: c_int = msg_send![cbperipheral, state]; + state + } + } + + pub fn peripheral_setdelegate( + cbperipheral: *mut Object, + delegate: *mut Object, /* CBPeripheralDelegate* */ + ) { + unsafe { + let () = msg_send![cbperipheral, setDelegate: delegate]; + } + } + + pub fn peripheral_discoverservices(cbperipheral: *mut Object) { + unsafe { + let () = msg_send![cbperipheral, discoverServices: nil]; + } + } + + pub fn peripheral_discoverincludedservicesforservice( + cbperipheral: *mut Object, + service: *mut Object, /* CBService* */ + ) { + unsafe { + let () = msg_send![cbperipheral, discoverIncludedServices:nil forService:service]; + } + } + + pub fn peripheral_services(cbperipheral: *mut Object) -> *mut Object /* NSArray<CBService*>* */ + { + unsafe { + let services: *mut Object = msg_send![cbperipheral, services]; + services + } + } + + pub fn peripheral_discovercharacteristicsforservice( + cbperipheral: *mut Object, + service: *mut Object, /* CBService* */ + ) { + unsafe { + let () = msg_send![cbperipheral, discoverCharacteristics:nil forService:service]; + } + } + + pub fn peripheral_readvalueforcharacteristic( + cbperipheral: *mut Object, + characteristic: *mut Object, /* CBCharacteristic* */ + ) { + unsafe { + let () = msg_send![cbperipheral, readValueForCharacteristic: characteristic]; + } + } + + pub fn peripheral_writevalue_forcharacteristic( + cbperipheral: *mut Object, + value: *mut Object, /* NSData* */ + characteristic: *mut Object, /* CBCharacteristic* */ + ) { + unsafe { + let () = + msg_send![cbperipheral, writeValue:value forCharacteristic:characteristic type:0]; + // CBCharacteristicWriteWithResponse from CBPeripheral.h + } + } + + pub fn peripheral_setnotifyvalue_forcharacteristic( + cbperipheral: *mut Object, + value: BOOL, + characteristic: *mut Object, /* CBCharacteristic* */ + ) { + unsafe { + let () = msg_send![cbperipheral, setNotifyValue:value forCharacteristic:characteristic]; + } + } + + pub fn peripheral_discoverdescriptorsforcharacteristic( + cbperipheral: *mut Object, + characteristic: *mut Object, /* CBCharacteristic* */ + ) { + unsafe { + let () = msg_send![ + cbperipheral, + discoverDescriptorsForCharacteristic: characteristic + ]; + } + } + + // CBPeripheralState = NSInteger from CBPeripheral.h + + pub const PERIPHERALSTATE_CONNECTED: c_int = 2; // CBPeripheralStateConnected + + // CBAttribute + + pub fn attribute_uuid(cbattribute: *mut Object) -> *mut Object /* CBUUID* */ { + unsafe { + let uuid: *mut Object = msg_send![cbattribute, UUID]; + uuid + } + } + + // CBService : CBAttribute + + // pub fn service_isprimary(cbservice: *mut Object) -> BOOL { + // unsafe { + // let isprimary: BOOL = msg_send![cbservice, isPrimary]; + // isprimary + // } + // } + + pub fn service_includedservices(cbservice: *mut Object) -> *mut Object /* NSArray<CBService*>* */ + { + unsafe { + let includedservices: *mut Object = msg_send![cbservice, includedServices]; + includedservices + } + } + + pub fn service_characteristics(cbservice: *mut Object) -> *mut Object /* NSArray<CBCharacteristic*>* */ + { + unsafe { + let characteristics: *mut Object = msg_send![cbservice, characteristics]; + characteristics + } + } + + // CBCharacteristic : CBAttribute + + pub fn characteristic_isnotifying(cbcharacteristic: *mut Object) -> BOOL { + unsafe { + let isnotifying: BOOL = msg_send![cbcharacteristic, isNotifying]; + isnotifying + } + } + + pub fn characteristic_value(cbcharacteristic: *mut Object) -> *mut Object /* NSData* */ { + unsafe { + let value: *mut Object = msg_send![cbcharacteristic, value]; + value + } + } + + pub fn characteristic_properties(cbcharacteristic: *mut Object) -> c_uint { + unsafe { + let properties: c_uint = msg_send![cbcharacteristic, properties]; + properties + } + } + + // CBCharacteristicProperties = NSUInteger from CBCharacteristic.h + + pub const CHARACTERISTICPROPERTY_BROADCAST: c_uint = 0x01; // CBCharacteristicPropertyBroadcast + pub const CHARACTERISTICPROPERTY_READ: c_uint = 0x02; // CBCharacteristicPropertyRead + pub const CHARACTERISTICPROPERTY_WRITEWITHOUTRESPONSE: c_uint = 0x04; // CBCharacteristicPropertyWriteWithoutResponse + pub const CHARACTERISTICPROPERTY_WRITE: c_uint = 0x08; // CBCharacteristicPropertyWrite + pub const CHARACTERISTICPROPERTY_NOTIFY: c_uint = 0x10; // CBCharacteristicPropertyNotify + pub const CHARACTERISTICPROPERTY_INDICATE: c_uint = 0x20; // CBCharacteristicPropertyIndicate + pub const CHARACTERISTICPROPERTY_AUTHENTICATEDSIGNEDWRITES: c_uint = 0x40; // CBCharacteristicPropertyAuthenticatedSignedWrites + + // CBUUID + + pub fn uuid_uuidstring(cbuuid: *mut Object) -> *mut Object /* NSString* */ { + unsafe { + let uuidstring: *mut Object = msg_send![cbuuid, UUIDString]; + uuidstring + } + } + + // CBCentralManagerScanOption...Key + + // CBAdvertisementData...Key + pub use self::link::CBAdvertisementDataServiceUUIDsKey as ADVERTISEMENTDATASERVICEUUIDSKEY; + pub use self::link::CBCentralManagerScanOptionAllowDuplicatesKey as CENTRALMANAGERSCANOPTIONALLOWDUPLICATESKEY; +} diff --git a/third_party/blurmac/src/gatt_characteristic.rs b/third_party/blurmac/src/gatt_characteristic.rs new file mode 100644 index 00000000000..aa1dd3e0c9a --- /dev/null +++ b/third_party/blurmac/src/gatt_characteristic.rs @@ -0,0 +1,225 @@ +// Copyright (c) 2017 Akos Kiss. +// +// Licensed under the BSD 3-Clause License +// <LICENSE.md or https://opensource.org/licenses/BSD-3-Clause>. +// This file may not be copied, modified, or distributed except +// according to those terms. + +use std::error::Error; +use std::os::raw::c_uint; +use std::slice; +use std::sync::Arc; + +use delegate::bmx; +use framework::{cb, nil, ns}; +use gatt_service::BluetoothGATTService; +use objc::runtime::{Object, NO, YES}; +use utils::{cbx, wait, NOT_SUPPORTED_ERROR, NO_CHARACTERISTIC_FOUND}; + +#[derive(Clone, Debug)] +pub struct BluetoothGATTCharacteristic { + pub(crate) service: Arc<BluetoothGATTService>, + pub(crate) characteristic: *mut Object, +} +// TODO: implement std::fmt::Debug and/or std::fmt::Display instead of derive? + +impl BluetoothGATTCharacteristic { + pub fn new(service: Arc<BluetoothGATTService>, uuid: String) -> BluetoothGATTCharacteristic { + // NOTE: It can happen that there is no characteristic for the given UUID, in that case + // self.characteristic will be nil and all methods that return a Result will return + // Err(Box::from(NO_CHARACTERISTIC_FOUND)), while others will return some meaningless value. + let characteristic = Self::characteristic_by_uuid(service.service, &uuid); + + if characteristic == nil { + warn!( + "BluetoothGATTCharacteristic::new found no characteristic for UUID {}", + uuid + ); + } + + BluetoothGATTCharacteristic { + service: service.clone(), + characteristic: characteristic, + } + } + + fn characteristic_by_uuid(service: *mut Object, uuid: &String) -> *mut Object { + if service != nil { + let chars = cb::service_characteristics(service); + for i in 0..ns::array_count(chars) { + let c = ns::array_objectatindex(chars, i); + if cbx::uuid_to_canonical_uuid_string(cb::attribute_uuid(c)) == *uuid { + return c; + } + } + } + nil + } + + pub fn get_id(&self) -> String { + trace!("BluetoothGATTCharacteristic::get_id"); + self.get_uuid().unwrap_or(String::new()) + } + + pub fn get_uuid(&self) -> Result<String, Box<dyn Error>> { + trace!("BluetoothGATTCharacteristic::get_uuid"); + if self.characteristic == nil { + return Err(Box::from(NO_CHARACTERISTIC_FOUND)); + } + + let uuid_string = + cbx::uuid_to_canonical_uuid_string(cb::attribute_uuid(self.characteristic)); + debug!("BluetoothGATTCharacteristic::get_uuid -> {}", uuid_string); + Ok(uuid_string) + } + + pub fn get_value(&self) -> Result<Vec<u8>, Box<dyn Error>> { + trace!("BluetoothGATTCharacteristic::get_value"); + if self.characteristic == nil { + return Err(Box::from(NO_CHARACTERISTIC_FOUND)); + } + + let value = cb::characteristic_value(self.characteristic); + let length = ns::data_length(value); + if length == 0 { + return Ok(vec![]); + } + + let bytes = ns::data_bytes(value); + let v = unsafe { slice::from_raw_parts(bytes, length as usize).to_vec() }; + debug!("BluetoothGATTCharacteristic::get_value -> {:?}", v); + Ok(v) + } + + pub fn read_value(&self) -> Result<Vec<u8>, Box<dyn Error>> { + trace!("BluetoothGATTCharacteristic::read_value"); + if self.characteristic == nil { + return Err(Box::from(NO_CHARACTERISTIC_FOUND)); + } + + let events = bmx::peripheralevents( + self.service.device.adapter.delegate, + self.service.device.peripheral, + )?; + let key = bmx::valueupdatedkey(self.characteristic); + let t = wait::get_timestamp(); + + cb::peripheral_readvalueforcharacteristic( + self.service.device.peripheral, + self.characteristic, + ); + + wait::wait_or_timeout(|| { + let nsnumber = ns::dictionary_objectforkey(events, key); + (nsnumber != nil) && (ns::number_unsignedlonglongvalue(nsnumber) >= t) + })?; + + self.get_value() + } + + pub fn write_value(&self, values: Vec<u8>) -> Result<(), Box<dyn Error>> { + trace!("BluetoothGATTCharacteristic::write_value"); + if self.characteristic == nil { + return Err(Box::from(NO_CHARACTERISTIC_FOUND)); + } + + let events = bmx::peripheralevents( + self.service.device.adapter.delegate, + self.service.device.peripheral, + )?; + let key = bmx::valuewrittenkey(self.characteristic); + let t = wait::get_timestamp(); + + cb::peripheral_writevalue_forcharacteristic( + self.service.device.peripheral, + ns::data(values.as_ptr(), values.len() as c_uint), + self.characteristic, + ); + + wait::wait_or_timeout(|| { + let nsnumber = ns::dictionary_objectforkey(events, key); + (nsnumber != nil) && (ns::number_unsignedlonglongvalue(nsnumber) >= t) + })?; + + Ok(()) + } + + pub fn is_notifying(&self) -> Result<bool, Box<dyn Error>> { + trace!("BluetoothGATTCharacteristic::is_notifying"); + if self.characteristic == nil { + return Err(Box::from(NO_CHARACTERISTIC_FOUND)); + } + + let notifying = cb::characteristic_isnotifying(self.characteristic); + debug!("BluetoothGATTCharacteristic::is_notifying -> {}", notifying); + Ok(notifying != NO) + } + + pub fn start_notify(&self) -> Result<(), Box<dyn Error>> { + trace!("BluetoothGATTCharacteristic::start_notify"); + if self.characteristic == nil { + return Err(Box::from(NO_CHARACTERISTIC_FOUND)); + } + + cb::peripheral_setnotifyvalue_forcharacteristic( + self.service.device.peripheral, + YES, + self.characteristic, + ); + Ok(()) + } + + pub fn stop_notify(&self) -> Result<(), Box<dyn Error>> { + trace!("BluetoothGATTCharacteristic::stop_notify"); + if self.characteristic == nil { + return Err(Box::from(NO_CHARACTERISTIC_FOUND)); + } + + cb::peripheral_setnotifyvalue_forcharacteristic( + self.service.device.peripheral, + NO, + self.characteristic, + ); + Ok(()) + } + + pub fn get_gatt_descriptors(&self) -> Result<Vec<String>, Box<dyn Error>> { + warn!("BluetoothGATTCharacteristic::get_gatt_descriptors"); + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_flags(&self) -> Result<Vec<String>, Box<dyn Error>> { + trace!("BluetoothGATTCharacteristic::get_flags"); + if self.characteristic == nil { + return Err(Box::from(NO_CHARACTERISTIC_FOUND)); + } + + let flags = cb::characteristic_properties(self.characteristic); + // NOTE: It is not documented anywhere what strings to return. Strings below were + // reverse-engineered from the sources of blurdroid. + let mut v = vec![]; + if (flags & cb::CHARACTERISTICPROPERTY_BROADCAST) != 0 { + v.push(String::from("broadcast")); + } + if (flags & cb::CHARACTERISTICPROPERTY_READ) != 0 { + v.push(String::from("read")); + } + if (flags & cb::CHARACTERISTICPROPERTY_WRITEWITHOUTRESPONSE) != 0 { + v.push(String::from("write-without-response")); + } + if (flags & cb::CHARACTERISTICPROPERTY_WRITE) != 0 { + v.push(String::from("write")); + } + if (flags & cb::CHARACTERISTICPROPERTY_NOTIFY) != 0 { + v.push(String::from("notify")); + } + if (flags & cb::CHARACTERISTICPROPERTY_INDICATE) != 0 { + v.push(String::from("indicate")); + } + if (flags & cb::CHARACTERISTICPROPERTY_AUTHENTICATEDSIGNEDWRITES) != 0 { + v.push(String::from("authenticated-signed-writes")); + } + debug!("BluetoothGATTCharacteristic::get_flags -> {:?}", v); + Ok(v) + } +} diff --git a/third_party/blurmac/src/gatt_descriptor.rs b/third_party/blurmac/src/gatt_descriptor.rs new file mode 100644 index 00000000000..2a4ce5b7eb3 --- /dev/null +++ b/third_party/blurmac/src/gatt_descriptor.rs @@ -0,0 +1,43 @@ +// Copyright (c) 2017 Akos Kiss. +// +// Licensed under the BSD 3-Clause License +// <LICENSE.md or https://opensource.org/licenses/BSD-3-Clause>. +// This file may not be copied, modified, or distributed except +// according to those terms. + +use std::error::Error; + +use utils::NOT_SUPPORTED_ERROR; + +#[derive(Clone, Debug)] +pub struct BluetoothGATTDescriptor {} + +impl BluetoothGATTDescriptor { + pub fn new(_descriptor: String) -> BluetoothGATTDescriptor { + BluetoothGATTDescriptor {} + } + + pub fn get_id(&self) -> String { + String::new() + } + + pub fn get_uuid(&self) -> Result<String, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_value(&self) -> Result<Vec<u8>, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn get_flags(&self) -> Result<Vec<String>, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn read_value(&self) -> Result<Vec<u8>, Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } + + pub fn write_value(&self, _values: Vec<u8>) -> Result<(), Box<dyn Error>> { + Err(Box::from(NOT_SUPPORTED_ERROR)) + } +} diff --git a/third_party/blurmac/src/gatt_service.rs b/third_party/blurmac/src/gatt_service.rs new file mode 100644 index 00000000000..23bc2799c6a --- /dev/null +++ b/third_party/blurmac/src/gatt_service.rs @@ -0,0 +1,128 @@ +// Copyright (c) 2017 Akos Kiss. +// +// Licensed under the BSD 3-Clause License +// <LICENSE.md or https://opensource.org/licenses/BSD-3-Clause>. +// This file may not be copied, modified, or distributed except +// according to those terms. + +use std::error::Error; +use std::sync::Arc; + +use delegate::bmx; +use device::BluetoothDevice; +use framework::{cb, nil, ns}; +use objc::runtime::Object; +use utils::{cbx, wait, NO_SERVICE_FOUND}; + +#[derive(Clone, Debug)] +pub struct BluetoothGATTService { + pub(crate) device: Arc<BluetoothDevice>, + pub(crate) service: *mut Object, +} +// TODO: implement std::fmt::Debug and/or std::fmt::Display instead of derive? + +impl BluetoothGATTService { + pub fn new(device: Arc<BluetoothDevice>, uuid: String) -> BluetoothGATTService { + trace!("BluetoothGATTService::new"); + // NOTE: It can happen that there is no service for the given UUID, in that case + // self.service will be nil and all methods that return a Result will return + // Err(Box::from(NO_SERVICE_FOUND)), while others will return some meaningless value. + let service = Self::service_by_uuid(device.peripheral, &uuid); + + if service == nil { + warn!( + "BluetoothGATTService::new found no service for UUID {}", + uuid + ); + } + + BluetoothGATTService { + device: device.clone(), + service: service, + } + } + + fn service_by_uuid(peripheral: *mut Object, uuid: &String) -> *mut Object { + if peripheral != nil { + // TODO: This function will most probably not find included services. Make it recursively + // descend into included services if first loop did not find what it was looking for. + let services = cb::peripheral_services(peripheral); + for i in 0..ns::array_count(services) { + let s = ns::array_objectatindex(services, i); + if cbx::uuid_to_canonical_uuid_string(cb::attribute_uuid(s)) == *uuid { + return s; + } + } + } + nil + } + + pub fn get_id(&self) -> String { + trace!("BluetoothGATTService::get_id"); + self.get_uuid().unwrap_or(String::new()) + } + + pub fn get_uuid(&self) -> Result<String, Box<dyn Error>> { + trace!("BluetoothGATTService::get_uuid"); + if self.service == nil { + return Err(Box::from(NO_SERVICE_FOUND)); + } + + let uuid_string = cbx::uuid_to_canonical_uuid_string(cb::attribute_uuid(self.service)); + debug!("BluetoothGATTService::get_uuid -> {}", uuid_string); + Ok(uuid_string) + } + + pub fn is_primary(&self) -> Result<bool, Box<dyn Error>> { + trace!("BluetoothGATTService::is_primary"); + if self.service == nil { + return Err(Box::from(NO_SERVICE_FOUND)); + } + + // let primary = cb::service_isprimary(self.service); + // debug!("BluetoothGATTService::is_primary -> {}", primary); + // Ok(primary != NO) + // FIXME: dirty hack. no idea why [CBService isPrimary] returns NO for a primary service. + Ok(true) + } + + pub fn get_includes(&self) -> Result<Vec<String>, Box<dyn Error>> { + trace!("BluetoothGATTService::get_includes"); + if self.service == nil { + return Err(Box::from(NO_SERVICE_FOUND)); + } + + let events = bmx::peripheralevents(self.device.adapter.delegate, self.device.peripheral)?; + let key = bmx::includedservicesdiscoveredkey(self.service); + wait::wait_or_timeout(|| ns::dictionary_objectforkey(events, key) != nil)?; + + let mut v = vec![]; + let includes = cb::service_includedservices(self.service); + for i in 0..ns::array_count(includes) { + v.push(cbx::uuid_to_canonical_uuid_string(cb::attribute_uuid( + ns::array_objectatindex(includes, i), + ))); + } + Ok(v) + } + + pub fn get_gatt_characteristics(&self) -> Result<Vec<String>, Box<dyn Error>> { + trace!("BluetoothGATTService::get_gatt_characteristics"); + if self.service == nil { + return Err(Box::from(NO_SERVICE_FOUND)); + } + + let events = bmx::peripheralevents(self.device.adapter.delegate, self.device.peripheral)?; + let key = bmx::characteristicsdiscoveredkey(self.service); + wait::wait_or_timeout(|| ns::dictionary_objectforkey(events, key) != nil)?; + + let mut v = vec![]; + let chars = cb::service_characteristics(self.service); + for i in 0..ns::array_count(chars) { + v.push(cbx::uuid_to_canonical_uuid_string(cb::attribute_uuid( + ns::array_objectatindex(chars, i), + ))); + } + Ok(v) + } +} diff --git a/third_party/blurmac/src/lib.rs b/third_party/blurmac/src/lib.rs new file mode 100644 index 00000000000..0385c6af860 --- /dev/null +++ b/third_party/blurmac/src/lib.rs @@ -0,0 +1,28 @@ +// Copyright (c) 2017 Akos Kiss. +// +// Licensed under the BSD 3-Clause License +// <LICENSE.md or https://opensource.org/licenses/BSD-3-Clause>. +// This file may not be copied, modified, or distributed except +// according to those terms. + +#[macro_use] +extern crate log; +#[macro_use] +extern crate objc; + +mod adapter; +mod delegate; +mod device; +mod discovery_session; +mod framework; +mod gatt_characteristic; +mod gatt_descriptor; +mod gatt_service; +mod utils; + +pub use adapter::BluetoothAdapter; +pub use device::BluetoothDevice; +pub use discovery_session::BluetoothDiscoverySession; +pub use gatt_characteristic::BluetoothGATTCharacteristic; +pub use gatt_descriptor::BluetoothGATTDescriptor; +pub use gatt_service::BluetoothGATTService; diff --git a/third_party/blurmac/src/utils.rs b/third_party/blurmac/src/utils.rs new file mode 100644 index 00000000000..d2b5c555f7e --- /dev/null +++ b/third_party/blurmac/src/utils.rs @@ -0,0 +1,122 @@ +// Copyright (c) 2017 Akos Kiss. +// +// Licensed under the BSD 3-Clause License +// <LICENSE.md or https://opensource.org/licenses/BSD-3-Clause>. +// This file may not be copied, modified, or distributed except +// according to those terms. + +use std::error::Error; +use std::ffi::{CStr, CString}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::{thread, time}; + +use framework::{cb, nil, ns}; +use objc::runtime::Object; + +pub const NOT_SUPPORTED_ERROR: &'static str = "Error! Not supported by blurmac!"; +pub const NO_PERIPHERAL_FOUND: &'static str = "Error! No peripheral found!"; +pub const NO_SERVICE_FOUND: &'static str = "Error! No service found!"; +pub const NO_CHARACTERISTIC_FOUND: &'static str = "Error! No characteristic found!"; + +pub mod nsx { + use super::*; + + pub fn string_to_string(nsstring: *mut Object) -> String { + if nsstring == nil { + return String::from("nil"); + } + unsafe { + String::from( + CStr::from_ptr(ns::string_utf8string(nsstring)) + .to_str() + .unwrap(), + ) + } + } + + pub fn string_from_str(string: &str) -> *mut Object { + let cstring = CString::new(string).unwrap(); + ns::string(cstring.as_ptr()) + } +} + +pub mod cbx { + use super::*; + + pub fn uuid_to_canonical_uuid_string(cbuuid: *mut Object) -> String { + // NOTE: CoreBluetooth tends to return uppercase UUID strings, and only 4 character long if the + // UUID is short (16 bits). However, WebBluetooth mandates lowercase UUID strings. And Servo + // seems to compare strings, not the binary representation. + let uuid = nsx::string_to_string(cb::uuid_uuidstring(cbuuid)); + let long = if uuid.len() == 4 { + format!("0000{}-0000-1000-8000-00805f9b34fb", uuid) + } else { + uuid + }; + long.to_lowercase() + } + + pub fn peripheral_debug(peripheral: *mut Object) -> String { + if peripheral == nil { + return String::from("nil"); + } + let name = cb::peripheral_name(peripheral); + let uuid = ns::uuid_uuidstring(cb::peer_identifier(peripheral)); + if name != nil { + format!( + "CBPeripheral({}, {})", + nsx::string_to_string(name), + nsx::string_to_string(uuid) + ) + } else { + format!("CBPeripheral({})", nsx::string_to_string(uuid)) + } + } + + pub fn service_debug(service: *mut Object) -> String { + if service == nil { + return String::from("nil"); + } + let uuid = cb::uuid_uuidstring(cb::attribute_uuid(service)); + format!("CBService({})", nsx::string_to_string(uuid)) + } + + pub fn characteristic_debug(characteristic: *mut Object) -> String { + if characteristic == nil { + return String::from("nil"); + } + let uuid = cb::uuid_uuidstring(cb::attribute_uuid(characteristic)); + format!("CBCharacteristic({})", nsx::string_to_string(uuid)) + } +} + +pub mod wait { + use super::*; + + pub type Timestamp = u64; + + static TIMESTAMP: AtomicUsize = AtomicUsize::new(0); + + pub fn get_timestamp() -> Timestamp { + TIMESTAMP.fetch_add(1, Ordering::SeqCst) as u64 + } + + pub fn now() -> *mut Object { + ns::number_withunsignedlonglong(get_timestamp()) + } + + pub fn wait_or_timeout<F>(mut f: F) -> Result<(), Box<dyn Error>> + where + F: FnMut() -> bool, + { + let now = time::Instant::now(); + + while !f() { + thread::sleep(time::Duration::from_secs(1)); + if now.elapsed().as_secs() > 30 { + return Err(Box::from("timeout")); + } + } + Ok(()) + } +} |