aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Robinson <mrobinson@igalia.com>2024-01-09 10:13:41 +0100
committerGitHub <noreply@github.com>2024-01-09 09:13:41 +0000
commit6a804cd775e3976103269ac54ae997a3accc8618 (patch)
tree1a125d1901e5aa3cc98a1f5beabebd927a3b9472
parentfddc4a430fca591152c69f0793ab946dcdc81617 (diff)
downloadservo-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
-rw-r--r--Cargo.lock17
-rw-r--r--components/bluetooth/Cargo.toml14
-rw-r--r--components/bluetooth/README.md55
-rw-r--r--components/bluetooth/adapter.rs408
-rw-r--r--components/bluetooth/bluetooth.rs753
-rw-r--r--components/bluetooth/empty.rs377
-rw-r--r--components/bluetooth/lib.rs26
-rw-r--r--components/bluetooth/macros.rs98
-rw-r--r--components/bluetooth/test.rs8
-rw-r--r--third_party/blurmac/Cargo.lock41
-rw-r--r--third_party/blurmac/Cargo.toml18
-rw-r--r--third_party/blurmac/LICENSE.md27
-rw-r--r--third_party/blurmac/README.md55
-rw-r--r--third_party/blurmac/src/adapter.rs212
-rw-r--r--third_party/blurmac/src/delegate.rs443
-rw-r--r--third_party/blurmac/src/device.rs280
-rw-r--r--third_party/blurmac/src/discovery_session.rs39
-rw-r--r--third_party/blurmac/src/framework.rs490
-rw-r--r--third_party/blurmac/src/gatt_characteristic.rs225
-rw-r--r--third_party/blurmac/src/gatt_descriptor.rs43
-rw-r--r--third_party/blurmac/src/gatt_service.rs128
-rw-r--r--third_party/blurmac/src/lib.rs28
-rw-r--r--third_party/blurmac/src/utils.rs122
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
+
+[![Build Status](https://travis-ci.org/akosthekiss/blurmac.svg?branch=master)](https://travis-ci.org/akosthekiss/blurmac)
+[![Crates.io](https://img.shields.io/crates/v/blurmac.svg)](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
+
+[![Build Status](https://travis-ci.org/akosthekiss/blurmac.svg?branch=master)](https://travis-ci.org/akosthekiss/blurmac)
+[![Crates.io](https://img.shields.io/crates/v/blurmac.svg)](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(())
+ }
+}