├── .DS_Store ├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── appveyor.yml ├── blurmac ├── Cargo.toml ├── LICENSE.md ├── README.md └── src │ ├── adapter.rs │ ├── delegate.rs │ ├── device.rs │ ├── discovery_session.rs │ ├── framework.rs │ ├── gatt_characteristic.rs │ ├── gatt_descriptor.rs │ ├── gatt_service.rs │ ├── lib.rs │ └── utils.rs └── src ├── adapter.rs ├── bluetooth.rs ├── empty.rs ├── lib.rs └── macros.rs /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/servo/devices/f8d8ba4ca51108ef09ceb79da0e83a5fdc9b36dc/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.o 3 | *.so 4 | *.rlib 5 | *.dll 6 | 7 | # Executables 8 | *.exe 9 | 10 | # Generated by Cargo 11 | /target/ 12 | 13 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 14 | # More information here https://doc.crates.io/guide.html#cargotoml-vs-cargolock 15 | Cargo.lock 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | rust: 4 | - stable 5 | - nightly 6 | 7 | os: 8 | - linux 9 | - osx 10 | 11 | dist: trusty 12 | addons: 13 | apt: 14 | packages: libdbus-1-dev 15 | 16 | script: 17 | - cargo build --features bluetooth-test 18 | - cargo test 19 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "device" 3 | version = "0.0.1" 4 | authors = ["The Servo Project Developers"] 5 | edition = "2018" 6 | 7 | [features] 8 | bluetooth = ["blurz", "blurdroid", "blurmac"] 9 | bluetooth-test = ["blurmock"] 10 | 11 | [target.'cfg(target_os = "linux")'.dependencies] 12 | blurz = { version = "0.3", optional = true } 13 | 14 | [target.'cfg(target_os = "android")'.dependencies] 15 | blurdroid = { version = "0.1.2", optional = true } 16 | 17 | [target.'cfg(target_os = "macos")'.dependencies] 18 | blurmac = { path = "blurmac", optional = true } 19 | 20 | [dependencies] 21 | blurmock = { version = "0.1.2", optional = true } 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # devices 2 | Servo-specific APIs to access various devices 3 | 4 | ---------- 5 | 6 | ## Bluetooth 7 | Bluetooth related code is located in `bluetooth.rs`. 8 | 9 | ### Implementation 10 | Underlying dependency crates: 11 | 12 | - Android platform: [blurdroid](https://crates.io/crates/blurdroid) 13 | - Linux platform: [blurz](https://crates.io/crates/blurz) 14 | - MacOS platform: [blurmac](https://crates.io/crates/blurmac) 15 | - `Fake` prefixed structures: [blurmock](https://crates.io/crates/blurmock) 16 | 17 | `Empty` prefixed structures are located in `empty.rs`. 18 | 19 | ### Usage 20 | 21 | #### Without the *bluetooth-test* feature 22 | There are three supported platforms (Android, Linux, MacOS), on other platforms we fall back to a default (`Empty` prefixed) implementation. Each enum (`BluetoothAdapter`, `BluetoothDevice`, etc.) will contain only one variant for each targeted platform. See the following `BluetoothAdapter` example: 23 | 24 | Android: 25 | ```rust 26 | pub enum BluetoothAdapter { 27 | Android(Arc), 28 | } 29 | ``` 30 | Linux: 31 | ```rust 32 | pub enum BluetoothAdapter { 33 | Bluez(Arc), 34 | } 35 | ``` 36 | MacOS: 37 | ```rust 38 | pub enum BluetoothAdapter { 39 | Mac(Arc), 40 | } 41 | ``` 42 | 43 | unsupported platforms: 44 | ```rust 45 | pub enum BluetoothAdapter { 46 | Empty(Arc), 47 | } 48 | ``` 49 | You will have a platform specific adapter, e.g. on android target, `BluetoothAdapter::init()` will create a `BluetoothAdapter::Android` enum variant, which wraps an `Arc`. 50 | 51 | ```rust 52 | pub fn init() -> Result> { 53 | let blurdroid_adapter = try!(BluetoothAdapterAndroid::get_adapter()); 54 | Ok(BluetoothAdapter::Android(Arc::new(blurdroid_adapter))) 55 | } 56 | ``` 57 | On each platform you can call the same functions to reach the same GATT hierarchy elements. The following code can access the same bluetooth device on all supported platforms: 58 | 59 | ```rust 60 | use device::{BluetoothAdapter, BluetoothDevice}; 61 | 62 | fn main() { 63 | // Get the bluetooth adapter. 64 | let adapter = BluetoothAdpater::init().expect("No bluetooth adapter found!"); 65 | // Get a device with the id 01:2A:00:4D:00:04 if it exists. 66 | let device = adapter.get_device("01:2A:00:4D:00:04".to_owned() /*device address*/) 67 | .expect("No bluetooth device found!"); 68 | } 69 | ``` 70 | 71 | #### With the *bluetooth-test* feature 72 | The `bluetooth-test` feature is not a default feature, to use it, append `features = ["bluetooth-test"]`, to the `device` crate dependency in the project's `Cargo.toml`. 73 | 74 | Each enum (`BluetoothAdapter`, `BluetoothDevice`, etc.) will contain one variant of the three possible default target, and a `Mock` variant, which wraps a `Fake` structure. 75 | 76 | Android: 77 | ```rust 78 | pub enum BluetoothAdapter { 79 | Android(Arc), 80 | Mock(Arc), 81 | } 82 | ``` 83 | Linux: 84 | ```rust 85 | pub enum BluetoothAdapter { 86 | Bluez(Arc), 87 | Mock(Arc), 88 | } 89 | ``` 90 | Mac: 91 | ```rust 92 | pub enum BluetoothAdapter { 93 | Mac(Arc), 94 | Mock(Arc), 95 | } 96 | ``` 97 | 98 | unsupported platforms: 99 | ```rust 100 | pub enum BluetoothAdapter { 101 | Empty(Arc), 102 | Mock(Arc), 103 | } 104 | ``` 105 | 106 | Beside the platform specific structures, you can create and access mock adapters, devices, services etc. These mock structures implements all the platform specific functions too. To create a mock GATT hierarchy, first you need to call the `BluetoothAdapter::init_mock()` function, insted of `BluetoothAdapter::init()`. 107 | 108 | ```rust 109 | use device::{BluetoothAdapter, BluetoothDevice}; 110 | use std::String; 111 | 112 | // This function takes a BluetoothAdapter, 113 | // and print the ids of the devices, which the adapter can find. 114 | fn print_device_ids(adapter: &BluetoothAdpater) { 115 | let devices = match adapter.get_devices().expect("No devices on the adapter!"); 116 | for device in devices { 117 | println!("{:?}", device.get_id()); 118 | } 119 | } 120 | 121 | fn main() { 122 | // This code uses a real adapter. 123 | // Get the bluetooth adapter. 124 | let adapter = BluetoothAdpater::init().expect("No bluetooth adapter found!"); 125 | // Get a device with the id 01:2A:00:4D:00:04 if it exists. 126 | let device = adapter.get_device("01:2A:00:4D:00:04".to_owned() /*device address*/) 127 | .expect("No bluetooth device found!"); 128 | 129 | // This code uses a mock adapter. 130 | // Creating a mock adapter. 131 | let mock_adapter = BluetoothAdpater::init_mock().unwrap(); 132 | // Creating a mock device. 133 | let mock_device = 134 | BluetoothDevice::create_mock_device(mock_adapter, 135 | "device_id_string_goes_here".to_owned()) 136 | .unwrap(); 137 | // Changing its device_id. 138 | let new_device_id = String::from("new_device_id_string".to_owned()); 139 | mock_device.set_id(new_device_id.clone()); 140 | // Calling the get_id function must return the last id we set. 141 | assert_equals!(new_device_id, mock_device.get_id()); 142 | // Getting the mock_device with its id 143 | // must return the same mock device object we created before. 144 | assert_equals!(Some(mock_device), 145 | mock_adapter.get_device(new_device_id.clone()).unwrap()); 146 | // The print_device_ids function accept real and mock adapters too. 147 | print_device_ids(&adapter); 148 | print_device_ids(&mock_adapter); 149 | } 150 | ``` 151 | Calling a test function on a not `Mock` structure, will result an error with the message: `Error! Test functions are not supported on real devices!`. 152 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | PATH: '%PATH%;C:\Program Files (x86)\Rust\bin;C:\MinGW\bin' 3 | matrix: 4 | - TARGET: x86_64-pc-windows-msvc 5 | - TARGET: i686-pc-windows-msvc 6 | - TARGET: i686-pc-windows-gnu 7 | install: 8 | - ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-nightly-${env:TARGET}.exe" 9 | - rust-nightly-%TARGET%.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust" 10 | - rustc -V 11 | - cargo -V 12 | 13 | build_script: 14 | - cargo build --features bluetooth-test 15 | 16 | test_script: 17 | - cargo test --verbose 18 | -------------------------------------------------------------------------------- /blurmac/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "blurmac" 3 | description = "Bluetooth Rust lib using macOS CoreBluetooth" 4 | version = "0.1.0" 5 | readme = "README.md" 6 | keywords = ["bluetooth", "ble", "macOS", "CoreBluetooth"] 7 | repository = "https://github.com/akosthekiss/blurmac" 8 | authors = ["Akos Kiss "] 9 | license = "BSD-3-Clause" 10 | 11 | [lib] 12 | name = "blurmac" 13 | path = "src/lib.rs" 14 | crate-type = ["rlib"] 15 | 16 | [dependencies] 17 | log = "0.4" 18 | objc = "0.2" 19 | -------------------------------------------------------------------------------- /blurmac/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Akos Kiss. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its contributors 15 | may be used to endorse or promote products derived from this software 16 | without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /blurmac/README.md: -------------------------------------------------------------------------------- 1 | # Bluetooth Rust lib using macOS CoreBluetooth 2 | 3 | [![Build Status](https://travis-ci.org/akosthekiss/blurmac.svg?branch=master)](https://travis-ci.org/akosthekiss/blurmac) 4 | [![Crates.io](https://img.shields.io/crates/v/blurmac.svg)](https://crates.io/crates/blurmac) 5 | 6 | The main aim of BlurMac is to enable [WebBluetooth](https://webbluetoothcg.github.io) 7 | in [Servo](https://github.com/servo/servo) on macOS. Thus, API and implementation 8 | decisions are affected by the encapsulating [Devices](https://github.com/servo/devices), 9 | and the sibling [BlurZ](https://github.com/szeged/blurz) and [BlurDroid](https://github.com/szeged/blurdroid) 10 | crates. 11 | 12 | 13 | ## Run Servo with WebBluetooth Enabled 14 | 15 | Usually, you don't want to work with BlurMac on its own but use it within Servo. 16 | So, most probably you'll want to run Servo with WebBluetooth enabled: 17 | 18 | ``` 19 | RUST_LOG=blurmac \ 20 | ./mach run \ 21 | --dev \ 22 | --pref=dom.bluetooth.enabled \ 23 | --pref=dom.permissions.testing.allowed_in_nonsecure_contexts \ 24 | URL 25 | ``` 26 | 27 | Notes: 28 | * The above command is actually not really BlurMac-specific (except for the `RUST_LOG` 29 | part). It runs Servo with WBT enabled on any platform where WBT is supported. 30 | * You don't need the `RUST_LOG=blurmac` part if you don't want to see BlurMac debug 31 | messages on the console. 32 | * You don't need the `--dev` part if you want to run a release build. 33 | * You don't need the `--pref=dom.permissions.testing.allowed_in_nonsecure_contexts` 34 | part if your `URL` is https (but you do need it if you test a local file). 35 | 36 | 37 | ## Known Issues 38 | 39 | * Device RSSI can not be retrieved yet. 40 | * Support for included services is incomplete. 41 | * Descriptors are not supported yet. 42 | * Notifications on characteristics are not supported yet (the limitation comes from 43 | Devices). 44 | 45 | 46 | ## Compatibility 47 | 48 | Tested on: 49 | 50 | * macOS Sierra 10.12. 51 | 52 | 53 | ## Copyright and Licensing 54 | 55 | Licensed under the BSD 3-Clause [License](LICENSE.md). 56 | -------------------------------------------------------------------------------- /blurmac/src/adapter.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Akos Kiss. 2 | // 3 | // Licensed under the BSD 3-Clause License 4 | // . 5 | // This file may not be copied, modified, or distributed except 6 | // according to those terms. 7 | 8 | use std::error::Error; 9 | use std::os::raw::c_int; 10 | 11 | use objc::runtime::{Object, YES}; 12 | 13 | use delegate::bm; 14 | use framework::{cb, io, ns}; 15 | use utils::{NOT_SUPPORTED_ERROR, nsx}; 16 | 17 | 18 | #[derive(Clone, Debug)] 19 | pub struct BluetoothAdapter { 20 | pub(crate) manager: *mut Object, 21 | pub(crate) delegate: *mut Object, 22 | } 23 | // TODO: implement std::fmt::Debug and/or std::fmt::Display instead of derive? 24 | 25 | unsafe impl Send for BluetoothAdapter {} 26 | unsafe impl Sync for BluetoothAdapter {} 27 | 28 | impl BluetoothAdapter { 29 | pub fn init() -> Result> { 30 | trace!("BluetoothAdapter::init"); 31 | let delegate = bm::delegate(); 32 | let manager = cb::centralmanager(delegate); 33 | let adapter = BluetoothAdapter { manager: manager, delegate: delegate }; 34 | 35 | // NOTE: start discovery at once, servo leaves close to no time to do a proper discovery 36 | // in a BluetoothDiscoverySession 37 | adapter.start_discovery().unwrap(); 38 | 39 | Ok(adapter) 40 | } 41 | 42 | pub fn get_id(&self) -> String { 43 | trace!("BluetoothAdapter::get_id"); 44 | // NOTE: not aware of any better native ID than the address string 45 | self.get_address().unwrap() 46 | } 47 | 48 | pub fn get_name(&self) -> Result> { 49 | trace!("BluetoothAdapter::get_name"); 50 | let controller = io::bluetoothhostcontroller_defaultcontroller(); 51 | let name = io::bluetoothhostcontroller_nameasstring(controller); 52 | Ok(nsx::string_to_string(name)) 53 | } 54 | 55 | pub fn get_address(&self) -> Result> { 56 | trace!("BluetoothAdapter::get_address"); 57 | let controller = io::bluetoothhostcontroller_defaultcontroller(); 58 | let address = io::bluetoothhostcontroller_addressasstring(controller); 59 | Ok(nsx::string_to_string(address)) 60 | } 61 | 62 | pub fn get_class(&self) -> Result> { 63 | trace!("BluetoothAdapter::get_class"); 64 | let controller = io::bluetoothhostcontroller_defaultcontroller(); 65 | let device_class = io::bluetoothhostcontroller_classofdevice(controller); 66 | Ok(device_class) 67 | } 68 | 69 | pub fn is_powered(&self) -> Result> { 70 | trace!("BluetoothAdapter::is_powered"); 71 | // NOTE: might be also available through 72 | // [[IOBluetoothHostController defaultController] powerState], but that's readonly, so keep 73 | // it in sync with set_powered 74 | Ok(io::bluetoothpreferencegetcontrollerpowerstate() == 1) 75 | } 76 | 77 | pub fn set_powered(&self, value: bool) -> Result<(), Box> { 78 | trace!("BluetoothAdapter::set_powered"); 79 | io::bluetoothpreferencesetcontrollerpowerstate(value as c_int); 80 | // TODO: wait for change to happen? whether it really happened? 81 | Ok(()) 82 | } 83 | 84 | pub fn is_discoverable(&self) -> Result> { 85 | trace!("BluetoothAdapter::is_discoverable"); 86 | Ok(io::bluetoothpreferencegetdiscoverablestate() == 1) 87 | } 88 | 89 | pub fn set_discoverable(&self, value: bool) -> Result<(), Box> { 90 | trace!("BluetoothAdapter::set_discoverable"); 91 | io::bluetoothpreferencesetdiscoverablestate(value as c_int); 92 | // TODO: wait for change to happen? whether it really happened? 93 | Ok(()) 94 | } 95 | 96 | pub fn get_device_list(&self) -> Result, Box> { 97 | trace!("BluetoothAdapter::get_device_list"); 98 | let mut v = vec!(); 99 | let peripherals = bm::delegate_peripherals(self.delegate); 100 | let keys = ns::dictionary_allkeys(peripherals); 101 | for i in 0..ns::array_count(keys) { 102 | v.push(nsx::string_to_string(ns::array_objectatindex(keys, i))); 103 | } 104 | Ok(v) 105 | } 106 | 107 | // Was in BluetoothDiscoverySession 108 | 109 | fn start_discovery(&self) -> Result<(), Box> { 110 | trace!("BluetoothAdapter::start_discovery"); 111 | let options = ns::mutabledictionary(); 112 | // NOTE: If duplicates are not allowed then a peripheral will not show up again once 113 | // connected and then disconnected. 114 | ns::mutabledictionary_setobject_forkey(options, ns::number_withbool(YES), unsafe { cb::CENTRALMANAGERSCANOPTIONALLOWDUPLICATESKEY }); 115 | cb::centralmanager_scanforperipherals_options(self.manager, options); 116 | Ok(()) 117 | } 118 | 119 | fn stop_discovery(&self) -> Result<(), Box> { 120 | trace!("BluetoothAdapter::stop_discovery"); 121 | cb::centralmanager_stopscan(self.manager); 122 | Ok(()) 123 | } 124 | 125 | // Not supported 126 | 127 | pub fn get_alias(&self) -> Result> { 128 | warn!("BluetoothAdapter::get_alias not supported by BlurMac"); 129 | Err(Box::from(NOT_SUPPORTED_ERROR)) 130 | } 131 | 132 | pub fn set_alias(&self, _value: String) -> Result<(), Box> { 133 | warn!("BluetoothAdapter::set_alias not supported by BlurMac"); 134 | Err(Box::from(NOT_SUPPORTED_ERROR)) 135 | } 136 | 137 | pub fn is_pairable(&self) -> Result> { 138 | warn!("BluetoothAdapter::is_pairable not supported by BlurMac"); 139 | Err(Box::from(NOT_SUPPORTED_ERROR)) 140 | } 141 | 142 | pub fn set_pairable(&self, _value: bool) -> Result<(), Box> { 143 | warn!("BluetoothAdapter::set_pairable not supported by BlurMac"); 144 | Err(Box::from(NOT_SUPPORTED_ERROR)) 145 | } 146 | 147 | pub fn get_pairable_timeout(&self) -> Result> { 148 | warn!("BluetoothAdapter::get_pairable_timeout not supported by BlurMac"); 149 | Err(Box::from(NOT_SUPPORTED_ERROR)) 150 | } 151 | 152 | pub fn set_pairable_timeout(&self, _value: u32) -> Result<(), Box> { 153 | warn!("BluetoothAdapter::set_pairable_timeout not supported by BlurMac"); 154 | Err(Box::from(NOT_SUPPORTED_ERROR)) 155 | } 156 | 157 | pub fn get_discoverable_timeout(&self) -> Result> { 158 | warn!("BluetoothAdapter::get_discoverable_timeout not supported by BlurMac"); 159 | Err(Box::from(NOT_SUPPORTED_ERROR)) 160 | } 161 | 162 | pub fn set_discoverable_timeout(&self, _value: u32) -> Result<(), Box> { 163 | warn!("BluetoothAdapter::set_discoverable_timeout not supported by BlurMac"); 164 | Err(Box::from(NOT_SUPPORTED_ERROR)) 165 | } 166 | 167 | pub fn is_discovering(&self) -> Result> { 168 | warn!("BluetoothAdapter::is_discovering not supported by BlurMac"); 169 | Err(Box::from(NOT_SUPPORTED_ERROR)) 170 | } 171 | 172 | pub fn get_uuids(&self) -> Result, Box> { 173 | warn!("BluetoothAdapter::get_uuids not supported by BlurMac"); 174 | Err(Box::from(NOT_SUPPORTED_ERROR)) 175 | } 176 | 177 | pub fn get_vendor_id_source(&self) -> Result> { 178 | warn!("BluetoothAdapter::get_vendor_id_source not supported by BlurMac"); 179 | Err(Box::from(NOT_SUPPORTED_ERROR)) 180 | } 181 | 182 | pub fn get_vendor_id(&self) -> Result> { 183 | warn!("BluetoothAdapter::get_vendor_id not supported by BlurMac"); 184 | Err(Box::from(NOT_SUPPORTED_ERROR)) 185 | } 186 | 187 | pub fn get_product_id(&self) -> Result> { 188 | warn!("BluetoothAdapter::get_product_id not supported by BlurMac"); 189 | Err(Box::from(NOT_SUPPORTED_ERROR)) 190 | } 191 | 192 | pub fn get_device_id(&self) -> Result> { 193 | warn!("BluetoothAdapter::get_device_id not supported by BlurMac"); 194 | Err(Box::from(NOT_SUPPORTED_ERROR)) 195 | } 196 | 197 | pub fn get_modalias(&self) -> Result<(String, u32, u32, u32), Box> { 198 | warn!("BluetoothAdapter::get_modalias not supported by BlurMac"); 199 | Err(Box::from(NOT_SUPPORTED_ERROR)) 200 | } 201 | } 202 | 203 | impl Drop for BluetoothAdapter { 204 | fn drop(&mut self) { 205 | trace!("BluetoothAdapter::drop"); 206 | // NOTE: stop discovery only here instead of in BluetoothDiscoverySession 207 | self.stop_discovery().unwrap(); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /blurmac/src/delegate.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Akos Kiss. 2 | // 3 | // Licensed under the BSD 3-Clause License 4 | // . 5 | // This file may not be copied, modified, or distributed except 6 | // according to those terms. 7 | 8 | use std::error::Error; 9 | use std::sync::Once; 10 | 11 | use objc::declare::ClassDecl; 12 | use objc::runtime::{Class, Object, Protocol, Sel}; 13 | 14 | use framework::{nil, cb, ns}; 15 | use utils::{NO_PERIPHERAL_FOUND, cbx, nsx, wait}; 16 | 17 | 18 | pub mod bm { 19 | use super::*; 20 | 21 | // BlurMacDelegate : CBCentralManagerDelegate, CBPeripheralDelegate 22 | 23 | const DELEGATE_PERIPHERALS_IVAR: &'static str = "_peripherals"; 24 | 25 | fn delegate_class() -> &'static Class { 26 | trace!("delegate_class"); 27 | static REGISTER_DELEGATE_CLASS: Once = Once::new(); 28 | 29 | REGISTER_DELEGATE_CLASS.call_once(|| { 30 | let mut decl = ClassDecl::new("BlurMacDelegate", Class::get("NSObject").unwrap()).unwrap(); 31 | decl.add_protocol(Protocol::get("CBCentralManagerDelegate").unwrap()); 32 | 33 | decl.add_ivar::<*mut Object>(DELEGATE_PERIPHERALS_IVAR); /* NSMutableDictionary* */ 34 | 35 | unsafe { 36 | decl.add_method(sel!(init), delegate_init as extern fn(&mut Object, Sel) -> *mut Object); 37 | decl.add_method(sel!(centralManagerDidUpdateState:), delegate_centralmanagerdidupdatestate as extern fn(&mut Object, Sel, *mut Object)); 38 | // decl.add_method(sel!(centralManager:willRestoreState:), delegate_centralmanager_willrestorestate as extern fn(&mut Object, Sel, *mut Object, *mut Object)); 39 | decl.add_method(sel!(centralManager:didConnectPeripheral:), delegate_centralmanager_didconnectperipheral as extern fn(&mut Object, Sel, *mut Object, *mut Object)); 40 | decl.add_method(sel!(centralManager:didDisconnectPeripheral:error:), delegate_centralmanager_diddisconnectperipheral_error as extern fn(&mut Object, Sel, *mut Object, *mut Object, *mut Object)); 41 | // decl.add_method(sel!(centralManager:didFailToConnectPeripheral:error:), delegate_centralmanager_didfailtoconnectperipheral_error as extern fn(&mut Object, Sel, *mut Object, *mut Object, *mut Object)); 42 | 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)); 43 | 44 | decl.add_method(sel!(peripheral:didDiscoverServices:), delegate_peripheral_diddiscoverservices as extern fn(&mut Object, Sel, *mut Object, *mut Object)); 45 | decl.add_method(sel!(peripheral:didDiscoverIncludedServicesForService:error:), delegate_peripheral_diddiscoverincludedservicesforservice_error as extern fn(&mut Object, Sel, *mut Object, *mut Object, *mut Object)); 46 | decl.add_method(sel!(peripheral:didDiscoverCharacteristicsForService:error:), delegate_peripheral_diddiscovercharacteristicsforservice_error as extern fn(&mut Object, Sel, *mut Object, *mut Object, *mut Object)); 47 | decl.add_method(sel!(peripheral:didUpdateValueForCharacteristic:error:), delegate_peripheral_didupdatevalueforcharacteristic_error as extern fn(&mut Object, Sel, *mut Object, *mut Object, *mut Object)); 48 | decl.add_method(sel!(peripheral:didWriteValueForCharacteristic:error:), delegate_peripheral_didwritevalueforcharacteristic_error as extern fn(&mut Object, Sel, *mut Object, *mut Object, *mut Object)); 49 | decl.add_method(sel!(peripheral:didReadRSSI:error:), delegate_peripheral_didreadrssi_error as extern fn(&mut Object, Sel, *mut Object, *mut Object, *mut Object)); 50 | } 51 | 52 | decl.register(); 53 | }); 54 | 55 | Class::get("BlurMacDelegate").unwrap() 56 | } 57 | 58 | extern fn delegate_init(delegate: &mut Object, _cmd: Sel) -> *mut Object { 59 | trace!("delegate_init"); 60 | unsafe { 61 | delegate.set_ivar::<*mut Object>(DELEGATE_PERIPHERALS_IVAR, ns::mutabledictionary()); 62 | } 63 | delegate 64 | } 65 | 66 | extern fn delegate_centralmanagerdidupdatestate(_delegate: &mut Object, _cmd: Sel, _central: *mut Object) { 67 | trace!("delegate_centralmanagerdidupdatestate"); 68 | // NOTE: this is a no-op but kept because it is a required method of the protocol 69 | } 70 | 71 | // extern fn delegate_centralmanager_willrestorestate(_delegate: &mut Object, _cmd: Sel, _central: *mut Object, _dict: *mut Object) { 72 | // trace!("delegate_centralmanager_willrestorestate"); 73 | // } 74 | 75 | extern fn delegate_centralmanager_didconnectperipheral(delegate: &mut Object, _cmd: Sel, _central: *mut Object, peripheral: *mut Object) { 76 | trace!("delegate_centralmanager_didconnectperipheral {}", cbx::peripheral_debug(peripheral)); 77 | cb::peripheral_setdelegate(peripheral, delegate); 78 | cb::peripheral_discoverservices(peripheral); 79 | } 80 | 81 | extern fn delegate_centralmanager_diddisconnectperipheral_error(delegate: &mut Object, _cmd: Sel, _central: *mut Object, peripheral: *mut Object, _error: *mut Object) { 82 | trace!("delegate_centralmanager_diddisconnectperipheral_error {}", cbx::peripheral_debug(peripheral)); 83 | ns::mutabledictionary_removeobjectforkey(delegate_peripherals(delegate), ns::uuid_uuidstring(cb::peer_identifier(peripheral))); 84 | } 85 | 86 | // extern fn delegate_centralmanager_didfailtoconnectperipheral_error(_delegate: &mut Object, _cmd: Sel, _central: *mut Object, _peripheral: *mut Object, _error: *mut Object) { 87 | // trace!("delegate_centralmanager_didfailtoconnectperipheral_error"); 88 | // } 89 | 90 | extern fn delegate_centralmanager_diddiscoverperipheral_advertisementdata_rssi(delegate: &mut Object, _cmd: Sel, _central: *mut Object, peripheral: *mut Object, adv_data: *mut Object, rssi: *mut Object) { 91 | trace!("delegate_centralmanager_diddiscoverperipheral_advertisementdata_rssi {}", cbx::peripheral_debug(peripheral)); 92 | let peripherals = delegate_peripherals(delegate); 93 | let uuid_nsstring = ns::uuid_uuidstring(cb::peer_identifier(peripheral)); 94 | let mut data = ns::dictionary_objectforkey(peripherals, uuid_nsstring); 95 | if data == nil { 96 | data = ns::mutabledictionary(); 97 | ns::mutabledictionary_setobject_forkey(peripherals, data, uuid_nsstring); 98 | } 99 | 100 | ns::mutabledictionary_setobject_forkey(data, ns::object_copy(peripheral), nsx::string_from_str(PERIPHERALDATA_PERIPHERALKEY)); 101 | 102 | ns::mutabledictionary_setobject_forkey(data, rssi, nsx::string_from_str(PERIPHERALDATA_RSSIKEY)); 103 | 104 | let cbuuids_nsarray = ns::dictionary_objectforkey(adv_data, unsafe { cb::ADVERTISEMENTDATASERVICEUUIDSKEY }); 105 | if cbuuids_nsarray != nil { 106 | ns::mutabledictionary_setobject_forkey(data, cbuuids_nsarray, nsx::string_from_str(PERIPHERALDATA_UUIDSKEY)); 107 | } 108 | 109 | if ns::dictionary_objectforkey(data, nsx::string_from_str(PERIPHERALDATA_EVENTSKEY)) == nil { 110 | ns::mutabledictionary_setobject_forkey(data, ns::mutabledictionary(), nsx::string_from_str(PERIPHERALDATA_EVENTSKEY)); 111 | } 112 | } 113 | 114 | extern fn delegate_peripheral_diddiscoverservices(delegate: &mut Object, _cmd: Sel, peripheral: *mut Object, error: *mut Object) { 115 | trace!("delegate_peripheral_diddiscoverservices {} {}", cbx::peripheral_debug(peripheral), if error != nil {"error"} else {""}); 116 | if error == nil { 117 | let services = cb::peripheral_services(peripheral); 118 | for i in 0..ns::array_count(services) { 119 | let s = ns::array_objectatindex(services, i); 120 | cb::peripheral_discovercharacteristicsforservice(peripheral, s); 121 | cb::peripheral_discoverincludedservicesforservice(peripheral, s); 122 | } 123 | 124 | // Notify BluetoothDevice::get_gatt_services that discovery was successful. 125 | match bmx::peripheralevents(delegate, peripheral) { 126 | Ok(events) => ns::mutabledictionary_setobject_forkey(events, wait::now(), nsx::string_from_str(PERIPHERALEVENT_SERVICESDISCOVEREDKEY)), 127 | Err(_) => {}, 128 | } 129 | } 130 | } 131 | 132 | extern fn delegate_peripheral_diddiscoverincludedservicesforservice_error(delegate: &mut Object, _cmd: Sel, peripheral: *mut Object, service: *mut Object, error: *mut Object) { 133 | trace!("delegate_peripheral_diddiscoverincludedservicesforservice_error {} {} {}", cbx::peripheral_debug(peripheral), cbx::service_debug(service), if error != nil {"error"} else {""}); 134 | if error == nil { 135 | let includes = cb::service_includedservices(service); 136 | for i in 0..ns::array_count(includes) { 137 | let s = ns::array_objectatindex(includes, i); 138 | cb::peripheral_discovercharacteristicsforservice(peripheral, s); 139 | } 140 | 141 | // Notify BluetoothGATTService::get_includes that discovery was successful. 142 | match bmx::peripheralevents(delegate, peripheral) { 143 | Ok(events) => ns::mutabledictionary_setobject_forkey(events, wait::now(), bmx::includedservicesdiscoveredkey(service)), 144 | Err(_) => {}, 145 | } 146 | } 147 | } 148 | 149 | extern fn delegate_peripheral_diddiscovercharacteristicsforservice_error(delegate: &mut Object, _cmd: Sel, peripheral: *mut Object, service: *mut Object, error: *mut Object) { 150 | trace!("delegate_peripheral_diddiscovercharacteristicsforservice_error {} {} {}", cbx::peripheral_debug(peripheral), cbx::service_debug(service), if error != nil {"error"} else {""}); 151 | if error == nil { 152 | let chars = cb::service_characteristics(service); 153 | for i in 0..ns::array_count(chars) { 154 | let c = ns::array_objectatindex(chars, i); 155 | cb::peripheral_discoverdescriptorsforcharacteristic(peripheral, c); 156 | } 157 | 158 | // Notify BluetoothGATTService::get_gatt_characteristics that discovery was successful. 159 | match bmx::peripheralevents(delegate, peripheral) { 160 | Ok(events) => ns::mutabledictionary_setobject_forkey(events, wait::now(), bmx::characteristicsdiscoveredkey(service)), 161 | Err(_) => {}, 162 | } 163 | } 164 | } 165 | 166 | extern fn delegate_peripheral_didupdatevalueforcharacteristic_error(delegate: &mut Object, _cmd: Sel, peripheral: *mut Object, characteristic: *mut Object, error: *mut Object) { 167 | trace!("delegate_peripheral_didupdatevalueforcharacteristic_error {} {} {}", cbx::peripheral_debug(peripheral), cbx::characteristic_debug(characteristic), if error != nil {"error"} else {""}); 168 | if error == nil { 169 | // Notify BluetoothGATTCharacteristic::read_value that read was successful. 170 | match bmx::peripheralevents(delegate, peripheral) { 171 | Ok(events) => ns::mutabledictionary_setobject_forkey(events, wait::now(), bmx::valueupdatedkey(characteristic)), 172 | Err(_) => {}, 173 | } 174 | } 175 | } 176 | 177 | extern fn delegate_peripheral_didwritevalueforcharacteristic_error(delegate: &mut Object, _cmd: Sel, peripheral: *mut Object, characteristic: *mut Object, error: *mut Object) { 178 | trace!("delegate_peripheral_didwritevalueforcharacteristic_error {} {} {}", cbx::peripheral_debug(peripheral), cbx::characteristic_debug(characteristic), if error != nil {"error"} else {""}); 179 | if error == nil { 180 | // Notify BluetoothGATTCharacteristic::write_value that write was successful. 181 | match bmx::peripheralevents(delegate, peripheral) { 182 | Ok(events) => ns::mutabledictionary_setobject_forkey(events, wait::now(), bmx::valuewrittenkey(characteristic)), 183 | Err(_) => {}, 184 | } 185 | } 186 | } 187 | 188 | // extern fn delegate_peripheral_didupdatenotificationstateforcharacteristic_error(_delegate: &mut Object, _cmd: Sel, _peripheral: *mut Object, _characteristic: *mut Object, _error: *mut Object) { 189 | // trace!("delegate_peripheral_didupdatenotificationstateforcharacteristic_error"); 190 | // // TODO: this is where notifications should be handled... 191 | // } 192 | 193 | // extern fn delegate_peripheral_diddiscoverdescriptorsforcharacteristic_error(_delegate: &mut Object, _cmd: Sel, _peripheral: *mut Object, _characteristic: *mut Object, _error: *mut Object) { 194 | // trace!("delegate_peripheral_diddiscoverdescriptorsforcharacteristic_error"); 195 | // } 196 | 197 | // extern fn delegate_peripheral_didupdatevaluefordescriptor(_delegate: &mut Object, _cmd: Sel, _peripheral: *mut Object, _descriptor: *mut Object, _error: *mut Object) { 198 | // trace!("delegate_peripheral_didupdatevaluefordescriptor"); 199 | // } 200 | 201 | // extern fn delegate_peripheral_didwritevaluefordescriptor_error(_delegate: &mut Object, _cmd: Sel, _peripheral: *mut Object, _descriptor: *mut Object, _error: *mut Object) { 202 | // trace!("delegate_peripheral_didwritevaluefordescriptor_error"); 203 | // } 204 | 205 | extern fn delegate_peripheral_didreadrssi_error(delegate: &mut Object, _cmd: Sel, peripheral: *mut Object, rssi: *mut Object, error: *mut Object) { 206 | trace!("delegate_peripheral_didreadrssi_error {}", cbx::peripheral_debug(peripheral)); 207 | if error == nil { 208 | let peripherals = delegate_peripherals(delegate); 209 | let uuid_nsstring = ns::uuid_uuidstring(cb::peer_identifier(peripheral)); 210 | let data = ns::dictionary_objectforkey(peripherals, uuid_nsstring); 211 | if data != nil { 212 | ns::mutabledictionary_setobject_forkey(data, rssi, nsx::string_from_str(PERIPHERALDATA_RSSIKEY)); 213 | } 214 | } 215 | } 216 | 217 | pub fn delegate() -> *mut Object { 218 | unsafe { 219 | let mut delegate: *mut Object = msg_send![delegate_class(), alloc]; 220 | delegate = msg_send![delegate, init]; 221 | delegate 222 | } 223 | } 224 | 225 | pub fn delegate_peripherals(delegate: *mut Object) -> *mut Object { 226 | unsafe { 227 | let peripherals: *mut Object = *(&mut *delegate).get_ivar::<*mut Object>(DELEGATE_PERIPHERALS_IVAR); 228 | peripherals 229 | } 230 | } 231 | 232 | // "BlurMacPeripheralData" = NSMutableDictionary 233 | 234 | pub const PERIPHERALDATA_PERIPHERALKEY: &'static str = "peripheral"; 235 | pub const PERIPHERALDATA_RSSIKEY: &'static str = "rssi"; 236 | pub const PERIPHERALDATA_UUIDSKEY: &'static str = "uuids"; 237 | pub const PERIPHERALDATA_EVENTSKEY: &'static str = "events"; 238 | 239 | pub const PERIPHERALEVENT_SERVICESDISCOVEREDKEY: &'static str = "services"; 240 | pub const PERIPHERALEVENT_INCLUDEDSERVICESDISCOVEREDKEYSUFFIX: &'static str = ":includes"; 241 | pub const PERIPHERALEVENT_CHARACTERISTICSDISCOVEREDKEYSUFFIX: &'static str = ":characteristics"; 242 | pub const PERIPHERALEVENT_VALUEUPDATEDKEYSUFFIX: &'static str = ":updated"; 243 | pub const PERIPHERALEVENT_VALUEWRITTENKEYSUFFIX: &'static str = ":written"; 244 | } 245 | 246 | pub mod bmx { 247 | use super::*; 248 | 249 | pub fn peripheraldata(delegate: *mut Object, peripheral: *mut Object) -> Result<*mut Object, Box> { 250 | let peripherals = bm::delegate_peripherals(delegate); 251 | let data = ns::dictionary_objectforkey(peripherals, ns::uuid_uuidstring(cb::peer_identifier(peripheral))); 252 | if data == nil { 253 | warn!("peripheraldata -> NOT FOUND"); 254 | return Err(Box::from(NO_PERIPHERAL_FOUND)); 255 | } 256 | Ok(data) 257 | } 258 | 259 | pub fn peripheralevents(delegate: *mut Object, peripheral: *mut Object) -> Result<*mut Object, Box> { 260 | let data = peripheraldata(delegate, peripheral)?; 261 | Ok(ns::dictionary_objectforkey(data, nsx::string_from_str(bm::PERIPHERALDATA_EVENTSKEY))) 262 | } 263 | 264 | pub fn includedservicesdiscoveredkey(service: *mut Object) -> *mut Object { 265 | suffixedkey(service, bm::PERIPHERALEVENT_INCLUDEDSERVICESDISCOVEREDKEYSUFFIX) 266 | } 267 | 268 | pub fn characteristicsdiscoveredkey(service: *mut Object) -> *mut Object { 269 | suffixedkey(service, bm::PERIPHERALEVENT_CHARACTERISTICSDISCOVEREDKEYSUFFIX) 270 | } 271 | 272 | pub fn valueupdatedkey(characteristic: *mut Object) -> *mut Object { 273 | suffixedkey(characteristic, bm::PERIPHERALEVENT_VALUEUPDATEDKEYSUFFIX) 274 | } 275 | 276 | pub fn valuewrittenkey(characteristic: *mut Object) -> *mut Object { 277 | suffixedkey(characteristic, bm::PERIPHERALEVENT_VALUEWRITTENKEYSUFFIX) 278 | } 279 | 280 | fn suffixedkey(attribute: *mut Object, suffix: &str) -> *mut Object { 281 | let key = format!("{}{}", cbx::uuid_to_canonical_uuid_string(cb::attribute_uuid(attribute)), suffix); 282 | nsx::string_from_str(key.as_str()) 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /blurmac/src/device.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Akos Kiss. 2 | // 3 | // Licensed under the BSD 3-Clause License 4 | // . 5 | // This file may not be copied, modified, or distributed except 6 | // according to those terms. 7 | 8 | use std::collections::HashMap; 9 | use std::error::Error; 10 | use std::sync::Arc; 11 | 12 | use objc::runtime::Object; 13 | 14 | use adapter::BluetoothAdapter; 15 | use delegate::{bm, bmx}; 16 | use framework::{nil, cb, ns}; 17 | use utils::{NO_PERIPHERAL_FOUND, NOT_SUPPORTED_ERROR, cbx, nsx, wait}; 18 | 19 | 20 | #[derive(Clone, Debug)] 21 | pub struct BluetoothDevice { 22 | pub(crate) adapter: Arc, 23 | pub(crate) peripheral: *mut Object, 24 | } 25 | // TODO: implement std::fmt::Debug and/or std::fmt::Display instead of derive? 26 | 27 | impl BluetoothDevice { 28 | pub fn new(adapter: Arc, uuid: String) -> BluetoothDevice { 29 | trace!("BluetoothDevice::new"); 30 | // NOTE: It can happen that there is no peripheral for the given UUID, in that case 31 | // self.peripheral will be nil and all methods that return a Result will return 32 | // Err(Box::from(NO_PERIPHERAL_FOUND)), while others will return some meaningless value. 33 | let peripheral = Self::peripheral_by_uuid(adapter.delegate, &uuid); 34 | 35 | if peripheral == nil { 36 | warn!("BluetoothDevice::new found no peripheral for UUID {}", uuid); 37 | } 38 | 39 | BluetoothDevice { adapter: adapter.clone(), peripheral: peripheral } 40 | } 41 | 42 | fn peripheral_by_uuid(delegate: *mut Object, uuid: &String) -> *mut Object { 43 | let peripherals = bm::delegate_peripherals(delegate); 44 | let keys = ns::dictionary_allkeys(peripherals); 45 | for i in 0..ns::array_count(keys) { 46 | let uuid_nsstring = ns::array_objectatindex(keys, i); 47 | if nsx::string_to_string(uuid_nsstring) == *uuid { 48 | let data = ns::dictionary_objectforkey(peripherals, uuid_nsstring); 49 | return ns::dictionary_objectforkey(data, nsx::string_from_str(bm::PERIPHERALDATA_PERIPHERALKEY)); 50 | } 51 | } 52 | nil 53 | } 54 | 55 | pub fn get_id(&self) -> String { 56 | trace!("BluetoothDevice::get_id -> get_address"); 57 | self.get_address().unwrap_or(String::new()) 58 | } 59 | 60 | pub fn get_address(&self) -> Result> { 61 | trace!("BluetoothDevice::get_address"); 62 | if self.peripheral == nil { 63 | return Err(Box::from(NO_PERIPHERAL_FOUND)); 64 | } 65 | 66 | // NOTE: There is no better substitute for address than identifier. 67 | let uuid_string = nsx::string_to_string(ns::uuid_uuidstring(cb::peer_identifier(self.peripheral))); 68 | debug!("BluetoothDevice::get_address -> {}", uuid_string); 69 | Ok(uuid_string) 70 | } 71 | 72 | pub fn get_name(&self) -> Result> { 73 | trace!("BluetoothDevice::get_name"); 74 | if self.peripheral == nil { 75 | return Err(Box::from(NO_PERIPHERAL_FOUND)); 76 | } 77 | 78 | let name_nsstring = cb::peripheral_name(self.peripheral); 79 | let name = if name_nsstring != nil { nsx::string_to_string(name_nsstring) } else { String::from("") }; 80 | debug!("BluetoothDevice::get_name -> {}", name); 81 | Ok(name) 82 | } 83 | 84 | pub fn get_uuids(&self) -> Result, Box> { 85 | trace!("BluetoothDevice::get_uuids"); 86 | if self.peripheral == nil { 87 | return Err(Box::from(NO_PERIPHERAL_FOUND)); 88 | } 89 | 90 | let data = bmx::peripheraldata(self.adapter.delegate, self.peripheral)?; 91 | let mut v = vec!(); 92 | let cbuuids_nsarray = ns::dictionary_objectforkey(data, nsx::string_from_str(bm::PERIPHERALDATA_UUIDSKEY)); 93 | if cbuuids_nsarray != nil { 94 | for i in 0..ns::array_count(cbuuids_nsarray) { 95 | v.push(cbx::uuid_to_canonical_uuid_string(ns::array_objectatindex(cbuuids_nsarray, i))); 96 | } 97 | } 98 | debug!("BluetoothDevice::get_uuids -> {:?}", v); 99 | Ok(v) 100 | 101 | } 102 | 103 | pub fn connect(&self) -> Result<(), Box> { 104 | trace!("BluetoothDevice::connect"); 105 | if self.peripheral == nil { 106 | return Err(Box::from(NO_PERIPHERAL_FOUND)); 107 | } 108 | 109 | cb::centralmanager_connectperipheral(self.adapter.manager, self.peripheral); 110 | Ok(()) 111 | } 112 | 113 | pub fn disconnect(&self) -> Result<(), Box> { 114 | trace!("BluetoothDevice::disconnect"); 115 | if self.peripheral == nil { 116 | return Err(Box::from(NO_PERIPHERAL_FOUND)); 117 | } 118 | 119 | cb::centralmanager_cancelperipheralconnection(self.adapter.manager, self.peripheral); 120 | Ok(()) 121 | } 122 | 123 | pub fn is_connected(&self) -> Result> { 124 | trace!("BluetoothDevice::is_connected"); 125 | if self.peripheral == nil { 126 | return Err(Box::from(NO_PERIPHERAL_FOUND)); 127 | } 128 | 129 | let state = cb::peripheral_state(self.peripheral); 130 | debug!("BluetoothDevice::is_connected -> {}", state); 131 | Ok(state == cb::PERIPHERALSTATE_CONNECTED) 132 | } 133 | 134 | pub fn get_gatt_services(&self) -> Result, Box> { 135 | trace!("BluetoothDevice::get_gatt_services"); 136 | if self.peripheral == nil { 137 | return Err(Box::from(NO_PERIPHERAL_FOUND)); 138 | } 139 | 140 | let events = bmx::peripheralevents(self.adapter.delegate, self.peripheral)?; 141 | let key = nsx::string_from_str(bm::PERIPHERALEVENT_SERVICESDISCOVEREDKEY); 142 | wait::wait_or_timeout(|| { ns::dictionary_objectforkey(events, key) != nil })?; 143 | 144 | let mut v = vec!(); 145 | let services = cb::peripheral_services(self.peripheral); 146 | for i in 0..ns::array_count(services) { 147 | let uuid_string = cbx::uuid_to_canonical_uuid_string(cb::attribute_uuid(ns::array_objectatindex(services, i))); 148 | v.push(uuid_string); 149 | } 150 | debug!("BluetoothDevice::get_gatt_services -> {:?}", v); 151 | Ok(v) 152 | } 153 | 154 | // Not supported 155 | 156 | pub fn get_rssi(&self) -> Result> { 157 | warn!("BluetoothDevice::get_rssi not supported by BlurMac"); 158 | // TODO: Now available from peripheral data in BluetoothAdapter. 159 | Err(Box::from(NOT_SUPPORTED_ERROR)) 160 | } 161 | 162 | pub fn get_tx_power(&self) -> Result> { 163 | warn!("BluetoothDevice::get_tx_power not supported by BlurMac"); 164 | Err(Box::from(NOT_SUPPORTED_ERROR)) 165 | } 166 | 167 | pub fn get_manufacturer_data(&self) -> Result>, Box> { 168 | warn!("BluetoothDevice::get_manufacturer_data not supported by BlurMac"); 169 | Err(Box::from(NOT_SUPPORTED_ERROR)) 170 | } 171 | 172 | pub fn get_service_data(&self) -> Result>, Box> { 173 | warn!("BluetoothDevice::get_service_data not supported by BlurMac"); 174 | Err(Box::from(NOT_SUPPORTED_ERROR)) 175 | } 176 | 177 | pub fn get_icon(&self) -> Result> { 178 | warn!("BluetoothDevice::get_icon not supported by BlurMac"); 179 | Err(Box::from(NOT_SUPPORTED_ERROR)) 180 | } 181 | 182 | pub fn get_class(&self) -> Result> { 183 | warn!("BluetoothDevice::get_class not supported by BlurMac"); 184 | Err(Box::from(NOT_SUPPORTED_ERROR)) 185 | } 186 | 187 | pub fn get_appearance(&self) -> Result> { 188 | warn!("BluetoothDevice::get_appearance not supported by BlurMac"); 189 | Err(Box::from(NOT_SUPPORTED_ERROR)) 190 | } 191 | 192 | pub fn is_paired(&self) -> Result> { 193 | warn!("BluetoothDevice::is_paired not supported by BlurMac"); 194 | Err(Box::from(NOT_SUPPORTED_ERROR)) 195 | } 196 | 197 | pub fn is_trusted(&self) -> Result> { 198 | warn!("BluetoothDevice::is_trusted not supported by BlurMac"); 199 | Err(Box::from(NOT_SUPPORTED_ERROR)) 200 | } 201 | 202 | pub fn is_blocked(&self) -> Result> { 203 | warn!("BluetoothDevice::is_blocked not supported by BlurMac"); 204 | Err(Box::from(NOT_SUPPORTED_ERROR)) 205 | } 206 | 207 | pub fn get_alias(&self) -> Result> { 208 | warn!("BluetoothDevice::get_alias not supported by BlurMac"); 209 | Err(Box::from(NOT_SUPPORTED_ERROR)) 210 | } 211 | 212 | pub fn set_alias(&self, _value: String) -> Result<(), Box> { 213 | warn!("BluetoothDevice::set_alias not supported by BlurMac"); 214 | Err(Box::from(NOT_SUPPORTED_ERROR)) 215 | } 216 | 217 | pub fn is_legacy_pairing(&self) -> Result> { 218 | warn!("BluetoothDevice::is_legacy_pairing not supported by BlurMac"); 219 | Err(Box::from(NOT_SUPPORTED_ERROR)) 220 | } 221 | 222 | pub fn get_vendor_id_source(&self) -> Result> { 223 | warn!("BluetoothDevice::get_vendor_id_source not supported by BlurMac"); 224 | Err(Box::from(NOT_SUPPORTED_ERROR)) 225 | } 226 | 227 | pub fn get_vendor_id(&self) -> Result> { 228 | warn!("BluetoothDevice::get_vendor_id not supported by BlurMac"); 229 | Err(Box::from(NOT_SUPPORTED_ERROR)) 230 | } 231 | 232 | pub fn get_product_id(&self) -> Result> { 233 | warn!("BluetoothDevice::get_product_id not supported by BlurMac"); 234 | Err(Box::from(NOT_SUPPORTED_ERROR)) 235 | } 236 | 237 | pub fn get_device_id(&self) -> Result> { 238 | warn!("BluetoothDevice::get_device_id not supported by BlurMac"); 239 | Err(Box::from(NOT_SUPPORTED_ERROR)) 240 | } 241 | 242 | pub fn get_modalias(&self) -> Result<(String, u32, u32, u32), Box> { 243 | warn!("BluetoothDevice::get_modalias not supported by BlurMac"); 244 | Err(Box::from(NOT_SUPPORTED_ERROR)) 245 | } 246 | 247 | pub fn connect_profile(&self, _uuid: String) -> Result<(), Box> { 248 | warn!("BluetoothDevice::connect_profile not supported by BlurMac"); 249 | Err(Box::from(NOT_SUPPORTED_ERROR)) 250 | } 251 | 252 | pub fn disconnect_profile(&self, _uuid: String) -> Result<(), Box> { 253 | warn!("BluetoothDevice::disconnect_profile not supported by BlurMac"); 254 | Err(Box::from(NOT_SUPPORTED_ERROR)) 255 | } 256 | 257 | pub fn pair(&self) -> Result<(), Box> { 258 | warn!("BluetoothDevice::pair not supported by BlurMac"); 259 | Err(Box::from(NOT_SUPPORTED_ERROR)) 260 | } 261 | 262 | pub fn cancel_pairing(&self) -> Result<(), Box> { 263 | warn!("BluetoothDevice::cancel_pairing not supported by BlurMac"); 264 | Err(Box::from(NOT_SUPPORTED_ERROR)) 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /blurmac/src/discovery_session.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Akos Kiss. 2 | // 3 | // Licensed under the BSD 3-Clause License 4 | // . 5 | // This file may not be copied, modified, or distributed except 6 | // according to those terms. 7 | 8 | use std::error::Error; 9 | use std::sync::Arc; 10 | 11 | use adapter::BluetoothAdapter; 12 | 13 | 14 | #[derive(Clone, Debug)] 15 | pub struct BluetoothDiscoverySession { 16 | // pub(crate) adapter: Arc, 17 | } 18 | 19 | impl BluetoothDiscoverySession { 20 | pub fn create_session(_adapter: Arc) -> Result> { 21 | trace!("BluetoothDiscoverySession::create_session"); 22 | Ok(BluetoothDiscoverySession { 23 | // adapter: adapter.clone() 24 | }) 25 | } 26 | 27 | pub fn start_discovery(&self) -> Result<(), Box> { 28 | trace!("BluetoothDiscoverySession::start_discovery"); 29 | // NOTE: discovery is started by BluetoothAdapter::new to allow devices to pop up 30 | Ok(()) 31 | } 32 | 33 | pub fn stop_discovery(&self) -> Result<(), Box> { 34 | trace!("BluetoothDiscoverySession::stop_discovery"); 35 | // NOTE: discovery is only stopped when BluetoothAdapter is dropped 36 | Ok(()) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /blurmac/src/framework.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Akos Kiss. 2 | // 3 | // Licensed under the BSD 3-Clause License 4 | // . 5 | // This file may not be copied, modified, or distributed except 6 | // according to those terms. 7 | 8 | use std::os::raw::{c_char, c_int, c_uint}; 9 | 10 | use objc::runtime::{BOOL, Class, Object}; 11 | 12 | 13 | #[allow(non_upper_case_globals)] 14 | pub const nil: *mut Object = 0 as *mut Object; 15 | 16 | 17 | pub mod ns { 18 | use super::*; 19 | 20 | // NSObject 21 | 22 | pub fn object_copy(nsobject: *mut Object) -> *mut Object { 23 | unsafe { 24 | let copy: *mut Object = msg_send![nsobject, copy]; 25 | copy 26 | } 27 | } 28 | 29 | // NSNumber 30 | 31 | pub fn number_withbool(value: BOOL) -> *mut Object { 32 | unsafe { 33 | let nsnumber: *mut Object = msg_send![Class::get("NSNumber").unwrap(), numberWithBool:value]; 34 | nsnumber 35 | } 36 | } 37 | 38 | pub fn number_withunsignedlonglong(value: u64) -> *mut Object { 39 | unsafe { 40 | let nsnumber: *mut Object = msg_send![Class::get("NSNumber").unwrap(), numberWithUnsignedLongLong:value]; 41 | nsnumber 42 | } 43 | } 44 | 45 | pub fn number_unsignedlonglongvalue(nsnumber: *mut Object) -> u64 { 46 | unsafe { 47 | let value: u64 = msg_send![nsnumber, unsignedLongLongValue]; 48 | value 49 | } 50 | } 51 | 52 | // NSString 53 | 54 | pub fn string(cstring: *const c_char) -> *mut Object /* NSString* */ { 55 | unsafe { 56 | let nsstring: *mut Object = msg_send![Class::get("NSString").unwrap(), stringWithUTF8String:cstring]; 57 | nsstring 58 | } 59 | } 60 | 61 | pub fn string_utf8string(nsstring: *mut Object) -> *const c_char { 62 | unsafe { 63 | let utf8string: *const c_char = msg_send![nsstring, UTF8String]; 64 | utf8string 65 | } 66 | } 67 | 68 | // NSArray 69 | 70 | pub fn array_count(nsarray: *mut Object) -> c_uint { 71 | unsafe { 72 | let count: c_uint = msg_send![nsarray, count]; 73 | count 74 | } 75 | } 76 | 77 | pub fn array_objectatindex(nsarray: *mut Object, index: c_uint) -> *mut Object { 78 | unsafe { 79 | let object: *mut Object = msg_send![nsarray, objectAtIndex:index]; 80 | object 81 | } 82 | } 83 | 84 | // NSDictionary 85 | 86 | pub fn dictionary_allkeys(nsdict: *mut Object) -> *mut Object /* NSArray* */ { 87 | unsafe { 88 | let keys: *mut Object = msg_send![nsdict, allKeys]; 89 | keys 90 | } 91 | } 92 | 93 | pub fn dictionary_objectforkey(nsdict: *mut Object, key: *mut Object) -> *mut Object { 94 | unsafe { 95 | let object: *mut Object = msg_send![nsdict, objectForKey:key]; 96 | object 97 | } 98 | } 99 | 100 | // NSMutableDictionary : NSDictionary 101 | 102 | pub fn mutabledictionary() -> *mut Object { 103 | unsafe { 104 | let nsmutdict: *mut Object = msg_send![Class::get("NSMutableDictionary").unwrap(), dictionaryWithCapacity:0]; 105 | nsmutdict 106 | } 107 | } 108 | 109 | pub fn mutabledictionary_removeobjectforkey(nsmutdict: *mut Object, key: *mut Object) { 110 | unsafe { 111 | let () = msg_send![nsmutdict, removeObjectForKey:key]; 112 | } 113 | } 114 | 115 | pub fn mutabledictionary_setobject_forkey(nsmutdict: *mut Object, object: *mut Object, key: *mut Object) { 116 | unsafe { 117 | let () = msg_send![nsmutdict, setObject:object forKey:key]; 118 | } 119 | } 120 | 121 | // NSData 122 | 123 | pub fn data(bytes: *const u8, length: c_uint) -> *mut Object /* NSData* */ { 124 | unsafe { 125 | let data: *mut Object = msg_send![Class::get("NSData").unwrap(), dataWithBytes:bytes length:length]; 126 | data 127 | } 128 | } 129 | 130 | pub fn data_length(nsdata: *mut Object) -> c_uint { 131 | unsafe { 132 | let length: c_uint = msg_send![nsdata, length]; 133 | length 134 | } 135 | } 136 | 137 | pub fn data_bytes(nsdata: *mut Object) -> *const u8 { 138 | unsafe { 139 | let bytes: *const u8 = msg_send![nsdata, bytes]; 140 | bytes 141 | } 142 | } 143 | 144 | // NSUUID 145 | 146 | pub fn uuid_uuidstring(nsuuid: *mut Object) -> *mut Object /* NSString* */ { 147 | unsafe { 148 | let uuidstring: *mut Object = msg_send![nsuuid, UUIDString]; 149 | uuidstring 150 | } 151 | } 152 | } 153 | 154 | 155 | pub mod io { 156 | use super::*; 157 | 158 | #[link(name = "IOBluetooth", kind = "framework")] 159 | extern { 160 | pub fn IOBluetoothPreferenceGetControllerPowerState() -> c_int; 161 | pub fn IOBluetoothPreferenceSetControllerPowerState(state: c_int); 162 | 163 | pub fn IOBluetoothPreferenceGetDiscoverableState() -> c_int; 164 | pub fn IOBluetoothPreferenceSetDiscoverableState(state: c_int); 165 | } 166 | 167 | // IOBluetoothHostController 168 | 169 | pub fn bluetoothhostcontroller_defaultcontroller() -> *mut Object /* IOBluetoothHostController* */ { 170 | unsafe { 171 | let defaultcontroller: *mut Object = msg_send![Class::get("IOBluetoothHostController").unwrap(), defaultController]; 172 | defaultcontroller 173 | } 174 | } 175 | 176 | pub fn bluetoothhostcontroller_nameasstring(iobthc: *mut Object) -> *mut Object /* NSString* */ { 177 | unsafe { 178 | let name: *mut Object = msg_send![iobthc, nameAsString]; 179 | name 180 | } 181 | } 182 | 183 | pub fn bluetoothhostcontroller_addressasstring(iobthc: *mut Object) -> *mut Object /* NSString* */ { 184 | unsafe { 185 | let address: *mut Object = msg_send![iobthc, addressAsString]; 186 | address 187 | } 188 | } 189 | 190 | pub fn bluetoothhostcontroller_classofdevice(iobthc: *mut Object) -> u32 { 191 | unsafe { 192 | let classofdevice: u32 = msg_send![iobthc, classOfDevice]; 193 | classofdevice 194 | } 195 | } 196 | 197 | // IOBluetoothPreference... 198 | 199 | pub fn bluetoothpreferencegetcontrollerpowerstate() -> c_int { 200 | unsafe { 201 | IOBluetoothPreferenceGetControllerPowerState() 202 | } 203 | } 204 | 205 | pub fn bluetoothpreferencesetcontrollerpowerstate(state: c_int) { 206 | unsafe { 207 | IOBluetoothPreferenceSetControllerPowerState(state); 208 | } 209 | } 210 | 211 | pub fn bluetoothpreferencegetdiscoverablestate() -> c_int { 212 | unsafe { 213 | IOBluetoothPreferenceGetDiscoverableState() 214 | } 215 | } 216 | 217 | pub fn bluetoothpreferencesetdiscoverablestate(state: c_int) { 218 | unsafe { 219 | IOBluetoothPreferenceSetDiscoverableState(state); 220 | } 221 | } 222 | } 223 | 224 | 225 | pub mod cb { 226 | use super::*; 227 | 228 | mod link { 229 | use super::*; 230 | 231 | #[link(name = "CoreBluetooth", kind = "framework")] 232 | extern { 233 | pub static CBAdvertisementDataServiceUUIDsKey: *mut Object; 234 | 235 | pub static CBCentralManagerScanOptionAllowDuplicatesKey: *mut Object; 236 | } 237 | } 238 | 239 | // CBCentralManager 240 | 241 | pub fn centralmanager(delegate: *mut Object /*CBCentralManagerDelegate* */) -> *mut Object /*CBCentralManager* */ { 242 | unsafe { 243 | let cbcentralmanager: *mut Object = msg_send![Class::get("CBCentralManager").unwrap(), alloc]; 244 | let () = msg_send![cbcentralmanager, initWithDelegate:delegate queue:nil]; 245 | cbcentralmanager 246 | } 247 | } 248 | 249 | pub fn centralmanager_scanforperipherals_options(cbcentralmanager: *mut Object, options: *mut Object /* NSDictionary */) { 250 | unsafe { 251 | let () = msg_send![cbcentralmanager, scanForPeripheralsWithServices:nil options:options]; 252 | } 253 | } 254 | 255 | pub fn centralmanager_stopscan(cbcentralmanager: *mut Object) { 256 | unsafe { 257 | let () = msg_send![cbcentralmanager, stopScan]; 258 | } 259 | } 260 | 261 | pub fn centralmanager_connectperipheral(cbcentralmanager: *mut Object, peripheral: *mut Object /* CBPeripheral* */) { 262 | unsafe { 263 | let () = msg_send![cbcentralmanager, connectPeripheral:peripheral options:nil]; 264 | } 265 | } 266 | 267 | pub fn centralmanager_cancelperipheralconnection(cbcentralmanager: *mut Object, peripheral: *mut Object /* CBPeripheral* */) { 268 | unsafe { 269 | let () = msg_send![cbcentralmanager, cancelPeripheralConnection:peripheral]; 270 | } 271 | } 272 | 273 | // CBPeer 274 | 275 | pub fn peer_identifier(cbpeer: *mut Object) -> *mut Object /* NSUUID* */ { 276 | unsafe { 277 | let identifier: *mut Object = msg_send![cbpeer, identifier]; 278 | identifier 279 | } 280 | } 281 | 282 | // CBPeripheral : CBPeer 283 | 284 | pub fn peripheral_name(cbperipheral: *mut Object) -> *mut Object /* NSString* */ { 285 | unsafe { 286 | let name: *mut Object = msg_send![cbperipheral, name]; 287 | name 288 | } 289 | } 290 | 291 | pub fn peripheral_state(cbperipheral: *mut Object) -> c_int { 292 | unsafe { 293 | let state: c_int = msg_send![cbperipheral, state]; 294 | state 295 | } 296 | } 297 | 298 | pub fn peripheral_setdelegate(cbperipheral: *mut Object, delegate: *mut Object /* CBPeripheralDelegate* */) { 299 | unsafe { 300 | let () = msg_send![cbperipheral, setDelegate:delegate]; 301 | } 302 | } 303 | 304 | pub fn peripheral_discoverservices(cbperipheral: *mut Object) { 305 | unsafe { 306 | let () = msg_send![cbperipheral, discoverServices:nil]; 307 | } 308 | } 309 | 310 | pub fn peripheral_discoverincludedservicesforservice(cbperipheral: *mut Object, service: *mut Object /* CBService* */) { 311 | unsafe { 312 | let () = msg_send![cbperipheral, discoverIncludedServices:nil forService:service]; 313 | } 314 | } 315 | 316 | pub fn peripheral_services(cbperipheral: *mut Object) -> *mut Object /* NSArray* */ { 317 | unsafe { 318 | let services: *mut Object = msg_send![cbperipheral, services]; 319 | services 320 | } 321 | } 322 | 323 | pub fn peripheral_discovercharacteristicsforservice(cbperipheral: *mut Object, service: *mut Object /* CBService* */) { 324 | unsafe { 325 | let () = msg_send![cbperipheral, discoverCharacteristics:nil forService:service]; 326 | } 327 | } 328 | 329 | pub fn peripheral_readvalueforcharacteristic(cbperipheral: *mut Object, characteristic: *mut Object /* CBCharacteristic* */) { 330 | unsafe { 331 | let () = msg_send![cbperipheral, readValueForCharacteristic:characteristic]; 332 | } 333 | } 334 | 335 | pub fn peripheral_writevalue_forcharacteristic(cbperipheral: *mut Object, value: *mut Object /* NSData* */, characteristic: *mut Object /* CBCharacteristic* */) { 336 | unsafe { 337 | let () = msg_send![cbperipheral, writeValue:value forCharacteristic:characteristic type:0]; // CBCharacteristicWriteWithResponse from CBPeripheral.h 338 | } 339 | } 340 | 341 | pub fn peripheral_setnotifyvalue_forcharacteristic(cbperipheral: *mut Object, value: BOOL, characteristic: *mut Object /* CBCharacteristic* */) { 342 | unsafe { 343 | let () = msg_send![cbperipheral, setNotifyValue:value forCharacteristic:characteristic]; 344 | } 345 | } 346 | 347 | pub fn peripheral_discoverdescriptorsforcharacteristic(cbperipheral: *mut Object, characteristic: *mut Object /* CBCharacteristic* */) { 348 | unsafe { 349 | let () = msg_send![cbperipheral, discoverDescriptorsForCharacteristic:characteristic]; 350 | } 351 | } 352 | 353 | // CBPeripheralState = NSInteger from CBPeripheral.h 354 | 355 | pub const PERIPHERALSTATE_CONNECTED: c_int = 2; // CBPeripheralStateConnected 356 | 357 | // CBAttribute 358 | 359 | pub fn attribute_uuid(cbattribute: *mut Object) -> *mut Object /* CBUUID* */ { 360 | unsafe { 361 | let uuid: *mut Object = msg_send![cbattribute, UUID]; 362 | uuid 363 | } 364 | } 365 | 366 | // CBService : CBAttribute 367 | 368 | // pub fn service_isprimary(cbservice: *mut Object) -> BOOL { 369 | // unsafe { 370 | // let isprimary: BOOL = msg_send![cbservice, isPrimary]; 371 | // isprimary 372 | // } 373 | // } 374 | 375 | pub fn service_includedservices(cbservice: *mut Object) -> *mut Object /* NSArray* */ { 376 | unsafe { 377 | let includedservices: *mut Object = msg_send![cbservice, includedServices]; 378 | includedservices 379 | } 380 | } 381 | 382 | pub fn service_characteristics(cbservice: *mut Object) -> *mut Object /* NSArray* */ { 383 | unsafe { 384 | let characteristics: *mut Object = msg_send![cbservice, characteristics]; 385 | characteristics 386 | } 387 | } 388 | 389 | // CBCharacteristic : CBAttribute 390 | 391 | pub fn characteristic_isnotifying(cbcharacteristic: *mut Object) -> BOOL { 392 | unsafe { 393 | let isnotifying: BOOL = msg_send![cbcharacteristic, isNotifying]; 394 | isnotifying 395 | } 396 | } 397 | 398 | pub fn characteristic_value(cbcharacteristic: *mut Object) -> *mut Object /* NSData* */ { 399 | unsafe { 400 | let value: *mut Object = msg_send![cbcharacteristic, value]; 401 | value 402 | } 403 | } 404 | 405 | pub fn characteristic_properties(cbcharacteristic: *mut Object) -> c_uint { 406 | unsafe { 407 | let properties: c_uint = msg_send![cbcharacteristic, properties]; 408 | properties 409 | } 410 | } 411 | 412 | // CBCharacteristicProperties = NSUInteger from CBCharacteristic.h 413 | 414 | pub const CHARACTERISTICPROPERTY_BROADCAST : c_uint = 0x01; // CBCharacteristicPropertyBroadcast 415 | pub const CHARACTERISTICPROPERTY_READ : c_uint = 0x02; // CBCharacteristicPropertyRead 416 | pub const CHARACTERISTICPROPERTY_WRITEWITHOUTRESPONSE : c_uint = 0x04; // CBCharacteristicPropertyWriteWithoutResponse 417 | pub const CHARACTERISTICPROPERTY_WRITE : c_uint = 0x08; // CBCharacteristicPropertyWrite 418 | pub const CHARACTERISTICPROPERTY_NOTIFY : c_uint = 0x10; // CBCharacteristicPropertyNotify 419 | pub const CHARACTERISTICPROPERTY_INDICATE : c_uint = 0x20; // CBCharacteristicPropertyIndicate 420 | pub const CHARACTERISTICPROPERTY_AUTHENTICATEDSIGNEDWRITES : c_uint = 0x40; // CBCharacteristicPropertyAuthenticatedSignedWrites 421 | 422 | // CBUUID 423 | 424 | pub fn uuid_uuidstring(cbuuid: *mut Object) -> *mut Object /* NSString* */ { 425 | unsafe { 426 | let uuidstring: *mut Object = msg_send![cbuuid, UUIDString]; 427 | uuidstring 428 | } 429 | } 430 | 431 | // CBCentralManagerScanOption...Key 432 | 433 | pub use self::link::CBCentralManagerScanOptionAllowDuplicatesKey as CENTRALMANAGERSCANOPTIONALLOWDUPLICATESKEY; 434 | 435 | // CBAdvertisementData...Key 436 | 437 | pub use self::link::CBAdvertisementDataServiceUUIDsKey as ADVERTISEMENTDATASERVICEUUIDSKEY; 438 | } 439 | -------------------------------------------------------------------------------- /blurmac/src/gatt_characteristic.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Akos Kiss. 2 | // 3 | // Licensed under the BSD 3-Clause License 4 | // . 5 | // This file may not be copied, modified, or distributed except 6 | // according to those terms. 7 | 8 | use std::error::Error; 9 | use std::os::raw::c_uint; 10 | use std::slice; 11 | use std::sync::Arc; 12 | 13 | use objc::runtime::{NO, Object, YES}; 14 | 15 | use delegate::bmx; 16 | use framework::{nil, cb, ns}; 17 | use gatt_service::BluetoothGATTService; 18 | use utils::{NO_CHARACTERISTIC_FOUND, NOT_SUPPORTED_ERROR, cbx, wait}; 19 | 20 | 21 | #[derive(Clone, Debug)] 22 | pub struct BluetoothGATTCharacteristic { 23 | pub(crate) service: Arc, 24 | pub(crate) characteristic: *mut Object, 25 | } 26 | // TODO: implement std::fmt::Debug and/or std::fmt::Display instead of derive? 27 | 28 | impl BluetoothGATTCharacteristic { 29 | pub fn new(service: Arc, uuid: String) -> BluetoothGATTCharacteristic { 30 | // NOTE: It can happen that there is no characteristic for the given UUID, in that case 31 | // self.characteristic will be nil and all methods that return a Result will return 32 | // Err(Box::from(NO_CHARACTERISTIC_FOUND)), while others will return some meaningless value. 33 | let characteristic = Self::characteristic_by_uuid(service.service, &uuid); 34 | 35 | if characteristic == nil { 36 | warn!("BluetoothGATTCharacteristic::new found no characteristic for UUID {}", uuid); 37 | } 38 | 39 | BluetoothGATTCharacteristic { service: service.clone(), characteristic: characteristic } 40 | } 41 | 42 | fn characteristic_by_uuid(service: *mut Object, uuid: &String) -> *mut Object { 43 | if service != nil { 44 | let chars = cb::service_characteristics(service); 45 | for i in 0..ns::array_count(chars) { 46 | let c = ns::array_objectatindex(chars, i); 47 | if cbx::uuid_to_canonical_uuid_string(cb::attribute_uuid(c)) == *uuid { 48 | return c; 49 | } 50 | } 51 | } 52 | nil 53 | } 54 | 55 | pub fn get_id(&self) -> String { 56 | trace!("BluetoothGATTCharacteristic::get_id"); 57 | self.get_uuid().unwrap_or(String::new()) 58 | } 59 | 60 | pub fn get_uuid(&self) -> Result> { 61 | trace!("BluetoothGATTCharacteristic::get_uuid"); 62 | if self.characteristic == nil { 63 | return Err(Box::from(NO_CHARACTERISTIC_FOUND)); 64 | } 65 | 66 | let uuid_string = cbx::uuid_to_canonical_uuid_string(cb::attribute_uuid(self.characteristic)); 67 | debug!("BluetoothGATTCharacteristic::get_uuid -> {}", uuid_string); 68 | Ok(uuid_string) 69 | } 70 | 71 | pub fn get_value(&self) -> Result, Box> { 72 | trace!("BluetoothGATTCharacteristic::get_value"); 73 | if self.characteristic == nil { 74 | return Err(Box::from(NO_CHARACTERISTIC_FOUND)); 75 | } 76 | 77 | let value = cb::characteristic_value(self.characteristic); 78 | let length = ns::data_length(value); 79 | if length == 0 { 80 | return Ok(vec!()); 81 | } 82 | 83 | let bytes = ns::data_bytes(value); 84 | let v = unsafe { slice::from_raw_parts(bytes, length as usize).to_vec() }; 85 | debug!("BluetoothGATTCharacteristic::get_value -> {:?}", v); 86 | Ok(v) 87 | } 88 | 89 | pub fn read_value(&self) -> Result, Box> { 90 | trace!("BluetoothGATTCharacteristic::read_value"); 91 | if self.characteristic == nil { 92 | return Err(Box::from(NO_CHARACTERISTIC_FOUND)); 93 | } 94 | 95 | let events = bmx::peripheralevents(self.service.device.adapter.delegate, self.service.device.peripheral)?; 96 | let key = bmx::valueupdatedkey(self.characteristic); 97 | let t = wait::get_timestamp(); 98 | 99 | cb::peripheral_readvalueforcharacteristic(self.service.device.peripheral, self.characteristic); 100 | 101 | wait::wait_or_timeout(|| { 102 | let nsnumber = ns::dictionary_objectforkey(events, key); 103 | (nsnumber != nil) && (ns::number_unsignedlonglongvalue(nsnumber) >= t) 104 | })?; 105 | 106 | self.get_value() 107 | } 108 | 109 | pub fn write_value(&self, values: Vec) -> Result<(), Box> { 110 | trace!("BluetoothGATTCharacteristic::write_value"); 111 | if self.characteristic == nil { 112 | return Err(Box::from(NO_CHARACTERISTIC_FOUND)); 113 | } 114 | 115 | let events = bmx::peripheralevents(self.service.device.adapter.delegate, self.service.device.peripheral)?; 116 | let key = bmx::valuewrittenkey(self.characteristic); 117 | let t = wait::get_timestamp(); 118 | 119 | cb::peripheral_writevalue_forcharacteristic(self.service.device.peripheral, ns::data(values.as_ptr(), values.len() as c_uint), self.characteristic); 120 | 121 | wait::wait_or_timeout(|| { 122 | let nsnumber = ns::dictionary_objectforkey(events, key); 123 | (nsnumber != nil) && (ns::number_unsignedlonglongvalue(nsnumber) >= t) 124 | })?; 125 | 126 | Ok(()) 127 | } 128 | 129 | pub fn is_notifying(&self) -> Result> { 130 | trace!("BluetoothGATTCharacteristic::is_notifying"); 131 | if self.characteristic == nil { 132 | return Err(Box::from(NO_CHARACTERISTIC_FOUND)); 133 | } 134 | 135 | let notifying = cb::characteristic_isnotifying(self.characteristic); 136 | debug!("BluetoothGATTCharacteristic::is_notifying -> {}", notifying); 137 | Ok(notifying != NO) 138 | } 139 | 140 | pub fn start_notify(&self) -> Result<(), Box> { 141 | trace!("BluetoothGATTCharacteristic::start_notify"); 142 | if self.characteristic == nil { 143 | return Err(Box::from(NO_CHARACTERISTIC_FOUND)); 144 | } 145 | 146 | cb::peripheral_setnotifyvalue_forcharacteristic(self.service.device.peripheral, YES, self.characteristic); 147 | Ok(()) 148 | } 149 | 150 | pub fn stop_notify(&self) -> Result<(), Box> { 151 | trace!("BluetoothGATTCharacteristic::stop_notify"); 152 | if self.characteristic == nil { 153 | return Err(Box::from(NO_CHARACTERISTIC_FOUND)); 154 | } 155 | 156 | cb::peripheral_setnotifyvalue_forcharacteristic(self.service.device.peripheral, NO, self.characteristic); 157 | Ok(()) 158 | } 159 | 160 | pub fn get_gatt_descriptors(&self) -> Result, Box> { 161 | warn!("BluetoothGATTCharacteristic::get_gatt_descriptors"); 162 | Err(Box::from(NOT_SUPPORTED_ERROR)) 163 | } 164 | 165 | pub fn get_flags(&self) -> Result, Box> { 166 | trace!("BluetoothGATTCharacteristic::get_flags"); 167 | if self.characteristic == nil { 168 | return Err(Box::from(NO_CHARACTERISTIC_FOUND)); 169 | } 170 | 171 | let flags = cb::characteristic_properties(self.characteristic); 172 | // NOTE: It is not documented anywhere what strings to return. Strings below were 173 | // reverse-engineered from the sources of blurdroid. 174 | let mut v = vec!(); 175 | if (flags & cb::CHARACTERISTICPROPERTY_BROADCAST) != 0 { 176 | v.push(String::from("broadcast")); 177 | } 178 | if (flags & cb::CHARACTERISTICPROPERTY_READ) != 0 { 179 | v.push(String::from("read")); 180 | } 181 | if (flags & cb::CHARACTERISTICPROPERTY_WRITEWITHOUTRESPONSE) != 0 { 182 | v.push(String::from("write-without-response")); 183 | } 184 | if (flags & cb::CHARACTERISTICPROPERTY_WRITE) != 0 { 185 | v.push(String::from("write")); 186 | } 187 | if (flags & cb::CHARACTERISTICPROPERTY_NOTIFY) != 0 { 188 | v.push(String::from("notify")); 189 | } 190 | if (flags & cb::CHARACTERISTICPROPERTY_INDICATE) != 0 { 191 | v.push(String::from("indicate")); 192 | } 193 | if (flags & cb::CHARACTERISTICPROPERTY_AUTHENTICATEDSIGNEDWRITES) != 0 { 194 | v.push(String::from("authenticated-signed-writes")); 195 | } 196 | debug!("BluetoothGATTCharacteristic::get_flags -> {:?}", v); 197 | Ok(v) 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /blurmac/src/gatt_descriptor.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Akos Kiss. 2 | // 3 | // Licensed under the BSD 3-Clause License 4 | // . 5 | // This file may not be copied, modified, or distributed except 6 | // according to those terms. 7 | 8 | use std::error::Error; 9 | 10 | use utils::NOT_SUPPORTED_ERROR; 11 | 12 | 13 | #[derive(Clone, Debug)] 14 | pub struct BluetoothGATTDescriptor { } 15 | 16 | impl BluetoothGATTDescriptor { 17 | pub fn new(_descriptor: String) -> BluetoothGATTDescriptor { 18 | BluetoothGATTDescriptor { } 19 | } 20 | 21 | pub fn get_id(&self) -> String { 22 | String::new() 23 | } 24 | 25 | pub fn get_uuid(&self) -> Result> { 26 | Err(Box::from(NOT_SUPPORTED_ERROR)) 27 | } 28 | 29 | pub fn get_value(&self) -> Result, Box> { 30 | Err(Box::from(NOT_SUPPORTED_ERROR)) 31 | } 32 | 33 | pub fn get_flags(&self) -> Result, Box> { 34 | Err(Box::from(NOT_SUPPORTED_ERROR)) 35 | } 36 | 37 | pub fn read_value(&self) -> Result, Box> { 38 | Err(Box::from(NOT_SUPPORTED_ERROR)) 39 | } 40 | 41 | pub fn write_value(&self, _values: Vec) -> Result<(), Box> { 42 | Err(Box::from(NOT_SUPPORTED_ERROR)) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /blurmac/src/gatt_service.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Akos Kiss. 2 | // 3 | // Licensed under the BSD 3-Clause License 4 | // . 5 | // This file may not be copied, modified, or distributed except 6 | // according to those terms. 7 | 8 | use std::error::Error; 9 | use std::sync::Arc; 10 | 11 | use objc::runtime::Object; 12 | 13 | use delegate::bmx; 14 | use device::BluetoothDevice; 15 | use framework::{nil, cb, ns}; 16 | use utils::{NO_SERVICE_FOUND, cbx, wait}; 17 | 18 | 19 | #[derive(Clone, Debug)] 20 | pub struct BluetoothGATTService { 21 | pub(crate) device: Arc, 22 | pub(crate) service: *mut Object, 23 | } 24 | // TODO: implement std::fmt::Debug and/or std::fmt::Display instead of derive? 25 | 26 | impl BluetoothGATTService { 27 | pub fn new(device: Arc, uuid: String) -> BluetoothGATTService { 28 | trace!("BluetoothGATTService::new"); 29 | // NOTE: It can happen that there is no service for the given UUID, in that case 30 | // self.service will be nil and all methods that return a Result will return 31 | // Err(Box::from(NO_SERVICE_FOUND)), while others will return some meaningless value. 32 | let service = Self::service_by_uuid(device.peripheral, &uuid); 33 | 34 | if service == nil { 35 | warn!("BluetoothGATTService::new found no service for UUID {}", uuid); 36 | } 37 | 38 | BluetoothGATTService { device: device.clone(), service: service } 39 | } 40 | 41 | fn service_by_uuid(peripheral: *mut Object, uuid: &String) -> *mut Object { 42 | if peripheral != nil { 43 | // TODO: This function will most probably not find included services. Make it recursively 44 | // descend into included services if first loop did not find what it was looking for. 45 | let services = cb::peripheral_services(peripheral); 46 | for i in 0..ns::array_count(services) { 47 | let s = ns::array_objectatindex(services, i); 48 | if cbx::uuid_to_canonical_uuid_string(cb::attribute_uuid(s)) == *uuid { 49 | return s; 50 | } 51 | } 52 | } 53 | nil 54 | } 55 | 56 | pub fn get_id(&self) -> String { 57 | trace!("BluetoothGATTService::get_id"); 58 | self.get_uuid().unwrap_or(String::new()) 59 | } 60 | 61 | pub fn get_uuid(&self) -> Result> { 62 | trace!("BluetoothGATTService::get_uuid"); 63 | if self.service == nil { 64 | return Err(Box::from(NO_SERVICE_FOUND)); 65 | } 66 | 67 | let uuid_string = cbx::uuid_to_canonical_uuid_string(cb::attribute_uuid(self.service)); 68 | debug!("BluetoothGATTService::get_uuid -> {}", uuid_string); 69 | Ok(uuid_string) 70 | } 71 | 72 | pub fn is_primary(&self) -> Result> { 73 | trace!("BluetoothGATTService::is_primary"); 74 | if self.service == nil { 75 | return Err(Box::from(NO_SERVICE_FOUND)); 76 | } 77 | 78 | // let primary = cb::service_isprimary(self.service); 79 | // debug!("BluetoothGATTService::is_primary -> {}", primary); 80 | // Ok(primary != NO) 81 | // FIXME: dirty hack. no idea why [CBService isPrimary] returns NO for a primary service. 82 | Ok(true) 83 | } 84 | 85 | pub fn get_includes(&self) -> Result, Box> { 86 | trace!("BluetoothGATTService::get_includes"); 87 | if self.service == nil { 88 | return Err(Box::from(NO_SERVICE_FOUND)); 89 | } 90 | 91 | let events = bmx::peripheralevents(self.device.adapter.delegate, self.device.peripheral)?; 92 | let key = bmx::includedservicesdiscoveredkey(self.service); 93 | wait::wait_or_timeout(|| { ns::dictionary_objectforkey(events, key) != nil })?; 94 | 95 | let mut v = vec!(); 96 | let includes = cb::service_includedservices(self.service); 97 | for i in 0..ns::array_count(includes) { 98 | v.push(cbx::uuid_to_canonical_uuid_string(cb::attribute_uuid(ns::array_objectatindex(includes, i)))); 99 | } 100 | Ok(v) 101 | } 102 | 103 | pub fn get_gatt_characteristics(&self) -> Result, Box> { 104 | trace!("BluetoothGATTService::get_gatt_characteristics"); 105 | if self.service == nil { 106 | return Err(Box::from(NO_SERVICE_FOUND)); 107 | } 108 | 109 | let events = bmx::peripheralevents(self.device.adapter.delegate, self.device.peripheral)?; 110 | let key = bmx::characteristicsdiscoveredkey(self.service); 111 | wait::wait_or_timeout(|| { ns::dictionary_objectforkey(events, key) != nil })?; 112 | 113 | let mut v = vec!(); 114 | let chars = cb::service_characteristics(self.service); 115 | for i in 0..ns::array_count(chars) { 116 | v.push(cbx::uuid_to_canonical_uuid_string(cb::attribute_uuid(ns::array_objectatindex(chars, i)))); 117 | } 118 | Ok(v) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /blurmac/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Akos Kiss. 2 | // 3 | // Licensed under the BSD 3-Clause License 4 | // . 5 | // This file may not be copied, modified, or distributed except 6 | // according to those terms. 7 | 8 | #[macro_use] 9 | extern crate log; 10 | #[macro_use] 11 | extern crate objc; 12 | 13 | mod adapter; 14 | mod delegate; 15 | mod discovery_session; 16 | mod device; 17 | mod gatt_service; 18 | mod gatt_characteristic; 19 | mod gatt_descriptor; 20 | mod framework; 21 | mod utils; 22 | 23 | pub use adapter::BluetoothAdapter; 24 | pub use discovery_session::BluetoothDiscoverySession; 25 | pub use device::BluetoothDevice; 26 | pub use gatt_service::BluetoothGATTService; 27 | pub use gatt_characteristic::BluetoothGATTCharacteristic; 28 | pub use gatt_descriptor::BluetoothGATTDescriptor; 29 | -------------------------------------------------------------------------------- /blurmac/src/utils.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Akos Kiss. 2 | // 3 | // Licensed under the BSD 3-Clause License 4 | // . 5 | // This file may not be copied, modified, or distributed except 6 | // according to those terms. 7 | 8 | use std::error::Error; 9 | use std::ffi::{CStr, CString}; 10 | use std::sync::atomic::{AtomicUsize, Ordering}; 11 | use std::time; 12 | use std::thread; 13 | 14 | use objc::runtime::Object; 15 | 16 | use framework::{nil, cb, ns}; 17 | 18 | 19 | pub const NOT_SUPPORTED_ERROR: &'static str = "Error! Not supported by blurmac!"; 20 | pub const NO_PERIPHERAL_FOUND: &'static str = "Error! No peripheral found!"; 21 | pub const NO_SERVICE_FOUND: &'static str = "Error! No service found!"; 22 | pub const NO_CHARACTERISTIC_FOUND: &'static str = "Error! No characteristic found!"; 23 | 24 | pub mod nsx { 25 | use super::*; 26 | 27 | pub fn string_to_string(nsstring: *mut Object) -> String { 28 | if nsstring == nil { return String::from("nil"); } 29 | unsafe { 30 | String::from(CStr::from_ptr(ns::string_utf8string(nsstring)).to_str().unwrap()) 31 | } 32 | } 33 | 34 | pub fn string_from_str(string: &str) -> *mut Object { 35 | let cstring = CString::new(string).unwrap(); 36 | ns::string(cstring.as_ptr()) 37 | } 38 | } 39 | 40 | 41 | pub mod cbx { 42 | use super::*; 43 | 44 | pub fn uuid_to_canonical_uuid_string(cbuuid: *mut Object) -> String { 45 | // NOTE: CoreBluetooth tends to return uppercase UUID strings, and only 4 character long if the 46 | // UUID is short (16 bits). However, WebBluetooth mandates lowercase UUID strings. And Servo 47 | // seems to compare strings, not the binary representation. 48 | let uuid = nsx::string_to_string(cb::uuid_uuidstring(cbuuid)); 49 | let long = if uuid.len() == 4 { format!("0000{}-0000-1000-8000-00805f9b34fb", uuid) } else { uuid }; 50 | long.to_lowercase() 51 | } 52 | 53 | pub fn peripheral_debug(peripheral: *mut Object) -> String { 54 | if peripheral == nil { return String::from("nil"); } 55 | let name = cb::peripheral_name(peripheral); 56 | let uuid = ns::uuid_uuidstring(cb::peer_identifier(peripheral)); 57 | if name != nil { 58 | format!("CBPeripheral({}, {})", nsx::string_to_string(name), nsx::string_to_string(uuid)) 59 | } else { 60 | format!("CBPeripheral({})", nsx::string_to_string(uuid)) 61 | } 62 | } 63 | 64 | pub fn service_debug(service: *mut Object) -> String { 65 | if service == nil { return String::from("nil"); } 66 | let uuid = cb::uuid_uuidstring(cb::attribute_uuid(service)); 67 | format!("CBService({})", nsx::string_to_string(uuid)) 68 | } 69 | 70 | pub fn characteristic_debug(characteristic: *mut Object) -> String { 71 | if characteristic == nil { return String::from("nil"); } 72 | let uuid = cb::uuid_uuidstring(cb::attribute_uuid(characteristic)); 73 | format!("CBCharacteristic({})", nsx::string_to_string(uuid)) 74 | } 75 | } 76 | 77 | pub mod wait { 78 | use super::*; 79 | 80 | pub type Timestamp = u64; 81 | 82 | static TIMESTAMP: AtomicUsize = AtomicUsize::new(0); 83 | 84 | pub fn get_timestamp() -> Timestamp { 85 | TIMESTAMP.fetch_add(1, Ordering::SeqCst) as u64 86 | } 87 | 88 | pub fn now() -> *mut Object { 89 | ns::number_withunsignedlonglong(get_timestamp()) 90 | } 91 | 92 | pub fn wait_or_timeout(mut f: F) -> Result<(), Box> 93 | where F: FnMut() -> bool { 94 | 95 | let now = time::Instant::now(); 96 | 97 | while !f() { 98 | thread::sleep(time::Duration::from_secs(1)); 99 | if now.elapsed().as_secs() > 30 { 100 | return Err(Box::from("timeout")); 101 | } 102 | } 103 | Ok(()) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/adapter.rs: -------------------------------------------------------------------------------- 1 | #[cfg(all(target_os = "linux", feature = "bluetooth"))] 2 | use blurz::bluetooth_adapter::BluetoothAdapter as BluetoothAdapterBluez; 3 | #[cfg(all(target_os = "android", feature = "bluetooth"))] 4 | use blurdroid::bluetooth_adapter::Adapter as BluetoothAdapterAndroid; 5 | #[cfg(all(target_os = "macos", feature = "bluetooth"))] 6 | use blurmac::BluetoothAdapter as BluetoothAdapterMac; 7 | #[cfg(not(any(all(target_os = "linux", feature = "bluetooth"), 8 | all(target_os = "android", feature = "bluetooth"), 9 | all(target_os = "macos", feature = "bluetooth"))))] 10 | use super::empty::EmptyAdapter as BluetoothAdapterEmpty; 11 | #[cfg(feature = "bluetooth-test")] 12 | use blurmock::fake_adapter::FakeBluetoothAdapter; 13 | 14 | #[cfg(all(target_os = "linux", feature = "bluetooth"))] 15 | use blurz::bluetooth_discovery_session::BluetoothDiscoverySession as BluetoothDiscoverySessionBluez; 16 | #[cfg(all(target_os = "android", feature = "bluetooth"))] 17 | use blurdroid::bluetooth_discovery_session::DiscoverySession as BluetoothDiscoverySessionAndroid; 18 | #[cfg(all(target_os = "macos", feature = "bluetooth"))] 19 | use blurmac::BluetoothDiscoverySession as BluetoothDiscoverySessionMac; 20 | #[cfg(not(any(all(target_os = "linux", feature = "bluetooth"), 21 | all(target_os = "android", feature = "bluetooth"), 22 | all(target_os = "macos", feature = "bluetooth"))))] 23 | use super::empty::BluetoothDiscoverySession as BluetoothDiscoverySessionEmpty; 24 | #[cfg(feature = "bluetooth-test")] 25 | use blurmock::fake_discovery_session::FakeBluetoothDiscoverySession; 26 | 27 | #[cfg(all(target_os = "linux", feature = "bluetooth"))] 28 | use blurz::bluetooth_device::BluetoothDevice as BluetoothDeviceBluez; 29 | #[cfg(all(target_os = "android", feature = "bluetooth"))] 30 | use blurdroid::bluetooth_device::Device as BluetoothDeviceAndroid; 31 | #[cfg(all(target_os = "macos", feature = "bluetooth"))] 32 | use blurmac::BluetoothDevice as BluetoothDeviceMac; 33 | #[cfg(not(any(all(target_os = "linux", feature = "bluetooth"), 34 | all(target_os = "android", feature = "bluetooth"), 35 | all(target_os = "macos", feature = "bluetooth"))))] 36 | use super::empty::BluetoothDevice as BluetoothDeviceEmpty; 37 | #[cfg(feature = "bluetooth-test")] 38 | use blurmock::fake_device::FakeBluetoothDevice; 39 | 40 | use super::macros::get_inner_and_call; 41 | #[cfg(feature = "bluetooth-test")] 42 | use super::macros::get_inner_and_call_test_func; 43 | 44 | use std::sync::Arc; 45 | use std::error::Error; 46 | 47 | use super::bluetooth::BluetoothDevice; 48 | use super::bluetooth::BluetoothDiscoverySession; 49 | 50 | #[derive(Clone, Debug)] 51 | pub enum BluetoothAdapter { 52 | #[cfg(all(target_os = "linux", feature = "bluetooth"))] 53 | Bluez(Arc), 54 | #[cfg(all(target_os = "android", feature = "bluetooth"))] 55 | Android(Arc), 56 | #[cfg(all(target_os = "macos", feature = "bluetooth"))] 57 | Mac(Arc), 58 | #[cfg(not(any(all(target_os = "linux", feature = "bluetooth"), 59 | all(target_os = "android", feature = "bluetooth"), 60 | all(target_os = "macos", feature = "bluetooth"))))] 61 | Empty(Arc), 62 | #[cfg(feature = "bluetooth-test")] 63 | Mock(Arc) 64 | } 65 | 66 | impl BluetoothAdapter { 67 | #[cfg(all(target_os = "linux", feature = "bluetooth"))] 68 | pub fn new() -> Result> { 69 | let bluez_adapter = BluetoothAdapterBluez::init()?; 70 | Ok(Self::Bluez(Arc::new(bluez_adapter))) 71 | } 72 | 73 | #[cfg(all(target_os = "android", feature = "bluetooth"))] 74 | pub fn new() -> Result> { 75 | let blurdroid_adapter = BluetoothAdapterAndroid::get_adapter()?; 76 | Ok(Self::Android(Arc::new(blurdroid_adapter))) 77 | } 78 | 79 | #[cfg(all(target_os = "macos", feature = "bluetooth"))] 80 | pub fn new() -> Result> { 81 | let mac_adapter = BluetoothAdapterMac::init()?; 82 | Ok(Self::Mac(Arc::new(mac_adapter))) 83 | } 84 | 85 | #[cfg(not(any(all(target_os = "linux", feature = "bluetooth"), 86 | all(target_os = "android", feature = "bluetooth"), 87 | all(target_os = "macos", feature = "bluetooth"))))] 88 | pub fn new() -> Result> { 89 | let adapter = BluetoothAdapterEmpty::init()?; 90 | Ok(Self::Empty(Arc::new(adapter))) 91 | } 92 | 93 | #[cfg(feature = "bluetooth-test")] 94 | pub fn new_mock() -> Result> { 95 | Ok(Self::Mock(FakeBluetoothAdapter::new_empty())) 96 | } 97 | 98 | pub fn get_id(&self) -> String { 99 | get_inner_and_call!(self, BluetoothAdapter, get_id) 100 | } 101 | 102 | pub fn get_devices(&self)-> Result, Box> { 103 | match self { 104 | #[cfg(all(target_os = "linux", feature = "bluetooth"))] 105 | BluetoothAdapter::Bluez(inner) => { 106 | let device_list = inner.get_device_list()?; 107 | Ok(device_list.into_iter().map(|device| BluetoothDevice::Bluez(BluetoothDeviceBluez::new_empty(self.0.clone(), device))).collect()) 108 | } 109 | #[cfg(all(target_os = "android", feature = "bluetooth"))] 110 | BluetoothAdapter::Android(inner) => { 111 | let device_list = inner.get_device_list()?; 112 | Ok(device_list.into_iter().map(|device| BluetoothDevice::Android(BluetoothDeviceAndroid::new_empty(self.0.clone(), device))).collect()) 113 | } 114 | #[cfg(all(target_os = "macos", feature = "bluetooth"))] 115 | BluetoothAdapter::Mac(inner) => { 116 | let device_list = inner.get_device_list()?; 117 | Ok(device_list.into_iter().map(|device| BluetoothDevice::Mac(Arc::new(BluetoothDeviceMac::new(inner.clone(),device)))).collect()) 118 | 119 | } 120 | #[cfg(not(any(all(target_os = "linux", feature = "bluetooth"), 121 | all(target_os = "android", feature = "bluetooth"), 122 | all(target_os = "macos", feature = "bluetooth"))))] 123 | BluetoothAdapter::Empty(inner) => { 124 | let device_list = inner.get_device_list()?; 125 | Ok(device_list.into_iter().map(|device| BluetoothDevice::Empty(Arc::new(BluetoothDeviceEmpty::new(device)))).collect()) 126 | } 127 | #[cfg(feature = "bluetooth-test")] 128 | BluetoothAdapter::Mock(inner) => { 129 | let device_list = inner.get_device_list()?; 130 | Ok(device_list.into_iter().map(|device| BluetoothDevice::Mock(FakeBluetoothDevice::new_empty(inner.clone(), device))).collect()) 131 | } 132 | } 133 | } 134 | 135 | pub fn get_device(&self, address: String) -> Result, Box> { 136 | let devices = self.get_devices()?; 137 | for device in devices { 138 | if device.get_address()? == address { 139 | return Ok(Some(device)); 140 | } 141 | } 142 | Ok(None) 143 | } 144 | 145 | pub fn create_mock_device(&self, _device: String) -> Result> { 146 | match self { 147 | #[cfg(feature = "bluetooth-test")] 148 | BluetoothAdapter::Mock(inner) => { 149 | Ok(BluetoothDevice::Mock(FakeBluetoothDevice::new_empty(inner.clone(), _device))) 150 | } 151 | _ => { 152 | Err(Box::from("Error! Test functions are not supported on real devices!")) 153 | } 154 | } 155 | } 156 | 157 | pub fn create_discovery_session(&self)-> Result> { 158 | let discovery_session = match self { 159 | #[cfg(all(target_os = "linux", feature = "bluetooth"))] 160 | BluetoothAdapter::Bluez(inner) => { 161 | BluetoothDiscoverySession::Bluez(Arc::new(BluetoothDiscoverySessionBluez::create_session(inner.get_id())?)); 162 | } 163 | #[cfg(all(target_os = "android", feature = "bluetooth"))] 164 | BluetoothAdapter::Android(inner) => { 165 | BluetoothDiscoverySession::Android(Arc::new(BluetoothDiscoverySessionAndroid::create_session(inner.clone())?)) 166 | } 167 | #[cfg(all(target_os = "macos", feature = "bluetooth"))] 168 | BluetoothAdapter::Mac(_) => { 169 | BluetoothDiscoverySession::Mac(Arc::new(BluetoothDiscoverySessionMac{})) 170 | } 171 | #[cfg(not(any(all(target_os = "linux", feature = "bluetooth"), 172 | all(target_os = "android", feature = "bluetooth"), 173 | all(target_os = "macos", feature = "bluetooth"))))] 174 | BluetoothAdapter::Empty(_) => { 175 | BluetoothDiscoverySession::Empty(Arc::new(BluetoothDiscoverySessionEmpty{})) 176 | } 177 | #[cfg(feature = "bluetooth-test")] 178 | BluetoothAdapter::Mock(inner) => { 179 | BluetoothDiscoverySession::Mock(Arc::new(FakeBluetoothDiscoverySession::create_session(inner.clone())?)) 180 | } 181 | }; 182 | Ok(discovery_session) 183 | } 184 | 185 | pub fn get_address(&self) -> Result> { 186 | get_inner_and_call!(self, BluetoothAdapter, get_address) 187 | } 188 | 189 | pub fn get_name(&self)-> Result> { 190 | get_inner_and_call!(self, BluetoothAdapter, get_name) 191 | } 192 | 193 | pub fn get_alias(&self) -> Result> { 194 | get_inner_and_call!(self, BluetoothAdapter, get_alias) 195 | } 196 | 197 | pub fn get_class(&self)-> Result> { 198 | get_inner_and_call!(self, BluetoothAdapter, get_class) 199 | } 200 | 201 | pub fn is_powered(&self)-> Result> { 202 | get_inner_and_call!(self, BluetoothAdapter, is_powered) 203 | } 204 | 205 | pub fn is_discoverable(&self) -> Result> { 206 | get_inner_and_call!(self, BluetoothAdapter, is_discoverable) 207 | } 208 | 209 | pub fn is_pairable(&self)-> Result> { 210 | get_inner_and_call!(self, BluetoothAdapter, is_pairable) 211 | } 212 | 213 | pub fn get_pairable_timeout(&self) -> Result> { 214 | get_inner_and_call!(self, BluetoothAdapter, get_pairable_timeout) 215 | } 216 | 217 | pub fn get_discoverable_timeout(&self)-> Result> { 218 | get_inner_and_call!(self, BluetoothAdapter, get_discoverable_timeout) 219 | } 220 | 221 | pub fn is_discovering(&self)-> Result> { 222 | get_inner_and_call!(self, BluetoothAdapter, is_discovering) 223 | } 224 | 225 | pub fn get_uuids(&self)-> Result, Box> { 226 | get_inner_and_call!(self, BluetoothAdapter, get_uuids) 227 | } 228 | 229 | pub fn get_vendor_id_source(&self)-> Result> { 230 | get_inner_and_call!(self, BluetoothAdapter, get_vendor_id_source) 231 | } 232 | 233 | pub fn get_vendor_id(&self)-> Result> { 234 | get_inner_and_call!(self, BluetoothAdapter, get_vendor_id) 235 | } 236 | 237 | pub fn get_product_id(&self) -> Result> { 238 | get_inner_and_call!(self, BluetoothAdapter, get_product_id) 239 | } 240 | 241 | pub fn get_device_id(&self) -> Result> { 242 | get_inner_and_call!(self, BluetoothAdapter, get_device_id) 243 | } 244 | 245 | pub fn get_modalias(&self) -> Result<(String, u32, u32, u32), Box> { 246 | get_inner_and_call!(self, BluetoothAdapter, get_modalias) 247 | } 248 | 249 | #[cfg(feature = "bluetooth-test")] 250 | pub fn set_id(&self, id: String)-> Result<(), Box> { 251 | match self { 252 | #[cfg(feature = "bluetooth-test")] 253 | BluetoothAdapter::Mock(inner) => Ok(inner.set_id(id)), 254 | _ => Err(Box::from("Error! Test functions are not supported on real devices!")), 255 | } 256 | } 257 | 258 | #[cfg(feature = "bluetooth-test")] 259 | pub fn set_address(&self, address: String) -> Result<(), Box> { 260 | get_inner_and_call_test_func!(self, BluetoothAdapter, set_address, address) 261 | } 262 | 263 | #[cfg(feature = "bluetooth-test")] 264 | pub fn set_name(&self, name: String) -> Result<(), Box> { 265 | get_inner_and_call_test_func!(self, BluetoothAdapter, set_name, name) 266 | } 267 | 268 | #[cfg(feature = "bluetooth-test")] 269 | pub fn set_alias(&self, alias: String) -> Result<(), Box> { 270 | get_inner_and_call_test_func!(self, BluetoothAdapter, set_alias, alias) 271 | } 272 | 273 | #[cfg(feature = "bluetooth-test")] 274 | pub fn set_class(&self, class: u32) -> Result<(), Box> { 275 | get_inner_and_call_test_func!(self, BluetoothAdapter, set_class, class) 276 | } 277 | 278 | #[cfg(feature = "bluetooth-test")] 279 | pub fn set_powered(&self, powered: bool) -> Result<(), Box> { 280 | get_inner_and_call_test_func!(self, BluetoothAdapter, set_powered, powered) 281 | } 282 | 283 | #[cfg(feature = "bluetooth-test")] 284 | pub fn is_present(&self) -> Result> { 285 | get_inner_and_call_test_func!(self, BluetoothAdapter, is_present) 286 | } 287 | 288 | #[cfg(feature = "bluetooth-test")] 289 | pub fn set_present(&self, present: bool) -> Result<(), Box> { 290 | get_inner_and_call_test_func!(self, BluetoothAdapter, set_present, present) 291 | } 292 | 293 | #[cfg(feature = "bluetooth-test")] 294 | pub fn set_discoverable(&self, discoverable: bool) -> Result<(), Box> { 295 | get_inner_and_call_test_func!(self, BluetoothAdapter, set_discoverable, discoverable) 296 | } 297 | 298 | #[cfg(feature = "bluetooth-test")] 299 | pub fn set_pairable(&self, pairable: bool) -> Result<(), Box> { 300 | get_inner_and_call_test_func!(self, BluetoothAdapter, set_pairable, pairable) 301 | } 302 | 303 | #[cfg(feature = "bluetooth-test")] 304 | pub fn set_pairable_timeout(&self, timeout: u32) -> Result<(), Box> { 305 | get_inner_and_call_test_func!(self, BluetoothAdapter, set_pairable_timeout, timeout) 306 | } 307 | 308 | #[cfg(feature = "bluetooth-test")] 309 | pub fn set_can_start_discovery(&self, can_start_discovery: bool) -> Result<(), Box> { 310 | get_inner_and_call_test_func!(self, BluetoothAdapter, set_can_start_discovery, can_start_discovery) 311 | } 312 | 313 | #[cfg(feature = "bluetooth-test")] 314 | pub fn set_discoverable_timeout(&self, timeout: u32) -> Result<(), Box> { 315 | get_inner_and_call_test_func!(self, BluetoothAdapter, set_discoverable_timeout, timeout) 316 | } 317 | 318 | #[cfg(feature = "bluetooth-test")] 319 | pub fn set_discovering(&self, discovering: bool) -> Result<(), Box> { 320 | get_inner_and_call_test_func!(self, BluetoothAdapter, set_discovering, discovering) 321 | } 322 | 323 | #[cfg(feature = "bluetooth-test")] 324 | pub fn set_can_stop_discovery(&self, can_stop_discovery: bool) -> Result<(), Box> { 325 | get_inner_and_call_test_func!(self, BluetoothAdapter, set_can_stop_discovery, can_stop_discovery) 326 | } 327 | 328 | #[cfg(feature = "bluetooth-test")] 329 | pub fn set_uuids(&self, uuids: Vec) -> Result<(), Box> { 330 | get_inner_and_call_test_func!(self, BluetoothAdapter, set_uuids, uuids) 331 | } 332 | 333 | #[cfg(feature = "bluetooth-test")] 334 | pub fn set_modalias(&self, modalias: String) -> Result<(), Box> { 335 | get_inner_and_call_test_func!(self, BluetoothAdapter, set_modalias, modalias) 336 | } 337 | 338 | #[cfg(feature = "bluetooth-test")] 339 | pub fn get_ad_datas(&self) -> Result, Box> { 340 | get_inner_and_call_test_func!(self, BluetoothAdapter, get_ad_datas) 341 | } 342 | 343 | #[cfg(feature = "bluetooth-test")] 344 | pub fn set_ad_datas(&self, ad_datas: Vec) -> Result<(), Box> { 345 | get_inner_and_call_test_func!(self, BluetoothAdapter, set_ad_datas, ad_datas) 346 | } 347 | } -------------------------------------------------------------------------------- /src/bluetooth.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | pub use super::adapter::BluetoothAdapter; 6 | 7 | #[cfg(all(target_os = "linux", feature = "bluetooth"))] 8 | use blurz::bluetooth_device::BluetoothDevice as BluetoothDeviceBluez; 9 | #[cfg(all(target_os = "android", feature = "bluetooth"))] 10 | use blurdroid::bluetooth_device::Device as BluetoothDeviceAndroid; 11 | #[cfg(all(target_os = "macos", feature = "bluetooth"))] 12 | use blurmac::BluetoothDevice as BluetoothDeviceMac; 13 | #[cfg(not(any(all(target_os = "linux", feature = "bluetooth"), 14 | all(target_os = "android", feature = "bluetooth"), 15 | all(target_os = "macos", feature = "bluetooth"))))] 16 | use super::empty::BluetoothDevice as BluetoothDeviceEmpty; 17 | #[cfg(feature = "bluetooth-test")] 18 | use blurmock::fake_device::FakeBluetoothDevice; 19 | 20 | #[cfg(all(target_os = "linux", feature = "bluetooth"))] 21 | use blurz::bluetooth_gatt_characteristic::BluetoothGATTCharacteristic as BluetoothGATTCharacteristicBluez; 22 | #[cfg(all(target_os = "android", feature = "bluetooth"))] 23 | use blurdroid::bluetooth_gatt_characteristic::Characteristic as BluetoothGATTCharacteristicAndroid; 24 | #[cfg(all(target_os = "macos", feature = "bluetooth"))] 25 | use blurmac::BluetoothGATTCharacteristic as BluetoothGATTCharacteristicMac; 26 | #[cfg(not(any(all(target_os = "linux", feature = "bluetooth"), 27 | all(target_os = "android", feature = "bluetooth"), 28 | all(target_os = "macos", feature = "bluetooth"))))] 29 | use super::empty::BluetoothGATTCharacteristic as BluetoothGATTCharacteristicEmpty; 30 | #[cfg(feature = "bluetooth-test")] 31 | use blurmock::fake_characteristic::FakeBluetoothGATTCharacteristic; 32 | 33 | #[cfg(all(target_os = "linux", feature = "bluetooth"))] 34 | use blurz::bluetooth_gatt_descriptor::BluetoothGATTDescriptor as BluetoothGATTDescriptorBluez; 35 | #[cfg(all(target_os = "android", feature = "bluetooth"))] 36 | use blurdroid::bluetooth_gatt_descriptor::Descriptor as BluetoothGATTDescriptorAndroid; 37 | #[cfg(all(target_os = "macos", feature = "bluetooth"))] 38 | use blurmac::BluetoothGATTDescriptor as BluetoothGATTDescriptorMac; 39 | #[cfg(not(any(all(target_os = "linux", feature = "bluetooth"), 40 | all(target_os = "android", feature = "bluetooth"), 41 | all(target_os = "macos", feature = "bluetooth"))))] 42 | use super::empty::BluetoothGATTDescriptor as BluetoothGATTDescriptorEmpty; 43 | #[cfg(feature = "bluetooth-test")] 44 | use blurmock::fake_descriptor::FakeBluetoothGATTDescriptor; 45 | 46 | #[cfg(all(target_os = "linux", feature = "bluetooth"))] 47 | use blurz::bluetooth_gatt_service::BluetoothGATTService as BluetoothGATTServiceBluez; 48 | #[cfg(all(target_os = "android", feature = "bluetooth"))] 49 | use blurdroid::bluetooth_gatt_service::Service as BluetoothGATTServiceAndroid; 50 | #[cfg(all(target_os = "macos", feature = "bluetooth"))] 51 | use blurmac::BluetoothGATTService as BluetoothGATTServiceMac; 52 | #[cfg(not(any(all(target_os = "linux", feature = "bluetooth"), 53 | all(target_os = "android", feature = "bluetooth"), 54 | all(target_os = "macos", feature = "bluetooth"))))] 55 | use super::empty::BluetoothGATTService as BluetoothGATTServiceEmpty; 56 | #[cfg(feature = "bluetooth-test")] 57 | use blurmock::fake_service::FakeBluetoothGATTService; 58 | 59 | #[cfg(all(target_os = "linux", feature = "bluetooth"))] 60 | use blurz::bluetooth_discovery_session::BluetoothDiscoverySession as BluetoothDiscoverySessionBluez; 61 | #[cfg(all(target_os = "android", feature = "bluetooth"))] 62 | use blurdroid::bluetooth_discovery_session::DiscoverySession as BluetoothDiscoverySessionAndroid; 63 | #[cfg(all(target_os = "macos", feature = "bluetooth"))] 64 | use blurmac::BluetoothDiscoverySession as BluetoothDiscoverySessionMac; 65 | #[cfg(not(any(all(target_os = "linux", feature = "bluetooth"), 66 | all(target_os = "android", feature = "bluetooth"), 67 | all(target_os = "macos", feature = "bluetooth"))))] 68 | use super::empty::BluetoothDiscoverySession as BluetoothDiscoverySessionEmpty; 69 | #[cfg(feature = "bluetooth-test")] 70 | use blurmock::fake_discovery_session::FakeBluetoothDiscoverySession; 71 | 72 | use super::macros::get_inner_and_call; 73 | #[cfg(feature = "bluetooth-test")] 74 | use super::macros::get_inner_and_call_test_func; 75 | 76 | use std::collections::HashMap; 77 | use std::sync::Arc; 78 | use std::error::Error; 79 | 80 | #[cfg(feature = "bluetooth-test")] 81 | const NOT_SUPPORTED_ON_MOCK_ERROR: &'static str = "Error! The first parameter must be a mock structure!"; 82 | 83 | 84 | #[derive(Debug)] 85 | pub enum BluetoothDiscoverySession { 86 | #[cfg(all(target_os = "linux", feature = "bluetooth"))] 87 | Bluez(Arc), 88 | #[cfg(all(target_os = "android", feature = "bluetooth"))] 89 | Android(Arc), 90 | #[cfg(all(target_os = "macos", feature = "bluetooth"))] 91 | Mac(Arc), 92 | #[cfg(not(any(all(target_os = "linux", feature = "bluetooth"), 93 | all(target_os = "android", feature = "bluetooth"), 94 | all(target_os = "macos", feature = "bluetooth"))))] 95 | Empty(Arc), 96 | #[cfg(feature = "bluetooth-test")] 97 | Mock(Arc), 98 | } 99 | 100 | #[derive(Clone, Debug)] 101 | pub enum BluetoothDevice { 102 | #[cfg(all(target_os = "linux", feature = "bluetooth"))] 103 | Bluez(Arc), 104 | #[cfg(all(target_os = "android", feature = "bluetooth"))] 105 | Android(Arc), 106 | #[cfg(all(target_os = "macos", feature = "bluetooth"))] 107 | Mac(Arc), 108 | #[cfg(not(any(all(target_os = "linux", feature = "bluetooth"), 109 | all(target_os = "android", feature = "bluetooth"), 110 | all(target_os = "macos", feature = "bluetooth"))))] 111 | Empty(Arc), 112 | #[cfg(feature = "bluetooth-test")] 113 | Mock(Arc), 114 | } 115 | 116 | #[derive(Clone, Debug)] 117 | pub enum BluetoothGATTService { 118 | #[cfg(all(target_os = "linux", feature = "bluetooth"))] 119 | Bluez(Arc), 120 | #[cfg(all(target_os = "android", feature = "bluetooth"))] 121 | Android(Arc), 122 | #[cfg(all(target_os = "macos", feature = "bluetooth"))] 123 | Mac(Arc), 124 | #[cfg(not(any(all(target_os = "linux", feature = "bluetooth"), 125 | all(target_os = "android", feature = "bluetooth"), 126 | all(target_os = "macos", feature = "bluetooth"))))] 127 | Empty(Arc), 128 | #[cfg(feature = "bluetooth-test")] 129 | Mock(Arc), 130 | } 131 | 132 | #[derive(Clone, Debug)] 133 | pub enum BluetoothGATTCharacteristic { 134 | #[cfg(all(target_os = "linux", feature = "bluetooth"))] 135 | Bluez(Arc), 136 | #[cfg(all(target_os = "android", feature = "bluetooth"))] 137 | Android(Arc), 138 | #[cfg(all(target_os = "macos", feature = "bluetooth"))] 139 | Mac(Arc), 140 | #[cfg(not(any(all(target_os = "linux", feature = "bluetooth"), 141 | all(target_os = "android", feature = "bluetooth"), 142 | all(target_os = "macos", feature = "bluetooth"))))] 143 | Empty(Arc), 144 | #[cfg(feature = "bluetooth-test")] 145 | Mock(Arc), 146 | } 147 | 148 | #[derive(Clone, Debug)] 149 | pub enum BluetoothGATTDescriptor { 150 | #[cfg(all(target_os = "linux", feature = "bluetooth"))] 151 | Bluez(Arc), 152 | #[cfg(all(target_os = "android", feature = "bluetooth"))] 153 | Android(Arc), 154 | #[cfg(all(target_os = "macos", feature = "bluetooth"))] 155 | Mac(Arc), 156 | #[cfg(not(any(all(target_os = "linux", feature = "bluetooth"), 157 | all(target_os = "android", feature = "bluetooth"), 158 | all(target_os = "macos", feature = "bluetooth"))))] 159 | Empty(Arc), 160 | #[cfg(feature = "bluetooth-test")] 161 | Mock(Arc), 162 | } 163 | 164 | impl BluetoothDiscoverySession { 165 | pub fn start_discovery(&self) -> Result<(), Box> { 166 | get_inner_and_call!(self, BluetoothDiscoverySession, start_discovery) 167 | } 168 | 169 | pub fn stop_discovery(&self) -> Result<(), Box> { 170 | get_inner_and_call!(self, BluetoothDiscoverySession, stop_discovery) 171 | } 172 | } 173 | 174 | impl BluetoothDevice { 175 | 176 | pub fn get_id(&self) -> String { 177 | get_inner_and_call!(self, BluetoothDevice, get_id) 178 | } 179 | 180 | #[cfg(feature = "bluetooth-test")] 181 | pub fn set_id(&self, id: String) { 182 | match self { 183 | &BluetoothDevice::Mock(ref fake_adapter) => fake_adapter.set_id(id), 184 | _ => (), 185 | } 186 | } 187 | 188 | pub fn get_address(&self) -> Result> { 189 | get_inner_and_call!(self, BluetoothDevice, get_address) 190 | } 191 | 192 | #[cfg(feature = "bluetooth-test")] 193 | pub fn set_address(&self, address: String) -> Result<(), Box> { 194 | get_inner_and_call_test_func!(self, BluetoothDevice, set_address, address) 195 | } 196 | 197 | pub fn get_name(&self) -> Result> { 198 | get_inner_and_call!(self, BluetoothDevice, get_name) 199 | } 200 | 201 | #[cfg(feature = "bluetooth-test")] 202 | pub fn set_name(&self, name: Option) -> Result<(), Box> { 203 | get_inner_and_call_test_func!(self, BluetoothDevice, set_name, name) 204 | } 205 | 206 | pub fn get_icon(&self) -> Result> { 207 | get_inner_and_call!(self, BluetoothDevice, get_icon) 208 | } 209 | 210 | #[cfg(feature = "bluetooth-test")] 211 | pub fn set_icon(&self, icon: String) -> Result<(), Box> { 212 | get_inner_and_call_test_func!(self, BluetoothDevice, set_icon, icon) 213 | } 214 | 215 | pub fn get_class(&self) -> Result> { 216 | get_inner_and_call!(self, BluetoothDevice, get_class) 217 | } 218 | 219 | #[cfg(feature = "bluetooth-test")] 220 | pub fn set_class(&self, class: u32) -> Result<(), Box> { 221 | get_inner_and_call_test_func!(self, BluetoothDevice, set_class, class) 222 | } 223 | 224 | pub fn get_appearance(&self) -> Result> { 225 | get_inner_and_call!(self, BluetoothDevice, get_appearance) 226 | } 227 | 228 | #[cfg(feature = "bluetooth-test")] 229 | pub fn set_appearance(&self, appearance: u16) -> Result<(), Box> { 230 | get_inner_and_call_test_func!(self, BluetoothDevice, set_appearance, Some(appearance)) 231 | } 232 | 233 | pub fn get_uuids(&self) -> Result, Box> { 234 | get_inner_and_call!(self, BluetoothDevice, get_uuids) 235 | } 236 | 237 | #[cfg(feature = "bluetooth-test")] 238 | pub fn set_uuids(&self, uuids: Vec) -> Result<(), Box> { 239 | get_inner_and_call_test_func!(self, BluetoothDevice, set_uuids, uuids) 240 | } 241 | 242 | pub fn is_paired(&self) -> Result> { 243 | get_inner_and_call!(self, BluetoothDevice, is_paired) 244 | } 245 | 246 | #[cfg(feature = "bluetooth-test")] 247 | pub fn set_paired(&self, paired: bool) -> Result<(), Box> { 248 | get_inner_and_call_test_func!(self, BluetoothDevice, set_paired, paired) 249 | } 250 | 251 | pub fn is_connected(&self) -> Result> { 252 | get_inner_and_call!(self, BluetoothDevice, is_connected) 253 | } 254 | 255 | #[cfg(feature = "bluetooth-test")] 256 | pub fn set_connected(&self, connected: bool) -> Result<(), Box> { 257 | get_inner_and_call_test_func!(self, BluetoothDevice, set_connected, connected) 258 | } 259 | 260 | #[cfg(feature = "bluetooth-test")] 261 | pub fn is_connectable(&self) -> Result> { 262 | get_inner_and_call_test_func!(self, BluetoothDevice, is_connectable) 263 | } 264 | 265 | #[cfg(feature = "bluetooth-test")] 266 | pub fn set_connectable(&self, connectable: bool) -> Result<(), Box> { 267 | get_inner_and_call_test_func!(self, BluetoothDevice, set_connectable, connectable) 268 | } 269 | 270 | pub fn is_trusted(&self) -> Result> { 271 | get_inner_and_call!(self, BluetoothDevice, is_trusted) 272 | } 273 | 274 | #[cfg(feature = "bluetooth-test")] 275 | pub fn set_trusted(&self, trusted: bool) -> Result<(), Box> { 276 | get_inner_and_call_test_func!(self, BluetoothDevice, set_trusted, trusted) 277 | } 278 | 279 | pub fn is_blocked(&self) -> Result> { 280 | get_inner_and_call!(self, BluetoothDevice, is_blocked) 281 | } 282 | 283 | #[cfg(feature = "bluetooth-test")] 284 | pub fn set_blocked(&self, blocked: bool) -> Result<(), Box> { 285 | get_inner_and_call_test_func!(self, BluetoothDevice, set_blocked, blocked) 286 | } 287 | 288 | pub fn get_alias(&self) -> Result> { 289 | get_inner_and_call!(self, BluetoothDevice, get_alias) 290 | } 291 | 292 | #[cfg(feature = "bluetooth-test")] 293 | pub fn set_alias(&self, alias: String) -> Result<(), Box> { 294 | get_inner_and_call_test_func!(self, BluetoothDevice, set_alias, alias) 295 | } 296 | 297 | pub fn is_legacy_pairing(&self) -> Result> { 298 | get_inner_and_call!(self, BluetoothDevice, is_legacy_pairing) 299 | } 300 | 301 | #[cfg(feature = "bluetooth-test")] 302 | pub fn set_legacy_pairing(&self, legacy_pairing: bool) -> Result<(), Box> { 303 | get_inner_and_call_test_func!(self, BluetoothDevice, set_legacy_pairing, legacy_pairing) 304 | } 305 | 306 | pub fn get_vendor_id_source(&self) -> Result> { 307 | get_inner_and_call!(self, BluetoothDevice, get_vendor_id_source) 308 | } 309 | 310 | pub fn get_vendor_id(&self) -> Result> { 311 | get_inner_and_call!(self, BluetoothDevice, get_vendor_id) 312 | } 313 | 314 | pub fn get_product_id(&self) -> Result> { 315 | get_inner_and_call!(self, BluetoothDevice, get_product_id) 316 | } 317 | 318 | pub fn get_device_id(&self) -> Result> { 319 | get_inner_and_call!(self, BluetoothDevice, get_device_id) 320 | } 321 | 322 | pub fn get_modalias(&self) -> Result<(String, u32, u32, u32), Box> { 323 | get_inner_and_call!(self, BluetoothDevice, get_modalias) 324 | } 325 | 326 | #[cfg(feature = "bluetooth-test")] 327 | pub fn set_modalias(&self, modalias: String) -> Result<(), Box> { 328 | get_inner_and_call_test_func!(self, BluetoothDevice, set_modalias, modalias) 329 | } 330 | 331 | pub fn get_rssi(&self) -> Result> { 332 | get_inner_and_call!(self, BluetoothDevice, get_rssi) 333 | } 334 | 335 | #[cfg(feature = "bluetooth-test")] 336 | pub fn set_rssi(&self, rssi: i16) -> Result<(), Box> { 337 | get_inner_and_call_test_func!(self, BluetoothDevice, set_rssi, Some(rssi)) 338 | } 339 | 340 | pub fn get_tx_power(&self) -> Result> { 341 | get_inner_and_call!(self, BluetoothDevice, get_tx_power) 342 | } 343 | 344 | #[cfg(feature = "bluetooth-test")] 345 | pub fn set_tx_power(&self, tx_power: i16) -> Result<(), Box> { 346 | get_inner_and_call_test_func!(self, BluetoothDevice, set_tx_power, Some(tx_power)) 347 | } 348 | 349 | pub fn get_manufacturer_data(&self) -> Result>, Box> { 350 | get_inner_and_call!(self, BluetoothDevice, get_manufacturer_data) 351 | } 352 | 353 | #[cfg(feature = "bluetooth-test")] 354 | pub fn set_manufacturer_data(&self, manufacturer_data: HashMap>) -> Result<(), Box> { 355 | get_inner_and_call_test_func!(self, BluetoothDevice, set_manufacturer_data, Some(manufacturer_data)) 356 | } 357 | 358 | pub fn get_service_data(&self) -> Result>, Box> { 359 | get_inner_and_call!(self, BluetoothDevice, get_service_data) 360 | } 361 | 362 | #[cfg(feature = "bluetooth-test")] 363 | pub fn set_service_data(&self, service_data: HashMap>) -> Result<(), Box> { 364 | get_inner_and_call_test_func!(self, BluetoothDevice, set_service_data, Some(service_data)) 365 | } 366 | 367 | pub fn get_gatt_services(&self) -> Result, Box> { 368 | let services = get_inner_and_call!(self, BluetoothDevice, get_gatt_services)?; 369 | Ok(services.into_iter().map(|service| BluetoothGATTService::create_service(self.clone(), service)).collect()) 370 | } 371 | 372 | pub fn connect(&self) -> Result<(), Box> { 373 | get_inner_and_call!(self, BluetoothDevice, connect) 374 | } 375 | 376 | pub fn disconnect(&self) -> Result<(), Box> { 377 | get_inner_and_call!(self, BluetoothDevice, disconnect) 378 | } 379 | 380 | pub fn connect_profile(&self, uuid: String) -> Result<(), Box> { 381 | get_inner_and_call!(self, BluetoothDevice, connect_profile, uuid) 382 | } 383 | 384 | pub fn disconnect_profile(&self, uuid: String) -> Result<(), Box> { 385 | get_inner_and_call!(self, BluetoothDevice, disconnect_profile, uuid) 386 | } 387 | 388 | pub fn pair(&self) -> Result<(), Box> { 389 | get_inner_and_call!(self, BluetoothDevice, pair) 390 | } 391 | 392 | pub fn cancel_pairing(&self) -> Result<(), Box> { 393 | get_inner_and_call!(self, BluetoothDevice, cancel_pairing) 394 | } 395 | } 396 | 397 | impl BluetoothGATTService { 398 | fn create_service(device: BluetoothDevice, service: String) -> BluetoothGATTService { 399 | match device { 400 | #[cfg(all(target_os = "linux", feature = "bluetooth"))] 401 | BluetoothDevice::Bluez(_bluez_device) => { 402 | BluetoothGATTService::Bluez(Arc::new(BluetoothGATTServiceBluez::new(service))) 403 | }, 404 | #[cfg(all(target_os = "android", feature = "bluetooth"))] 405 | BluetoothDevice::Android(android_device) => { 406 | BluetoothGATTService::Android(Arc::new(BluetoothGATTServiceAndroid::new(android_device, service))) 407 | }, 408 | #[cfg(all(target_os = "macos", feature = "bluetooth"))] 409 | BluetoothDevice::Mac(mac_device) => { 410 | BluetoothGATTService::Mac(Arc::new(BluetoothGATTServiceMac::new(mac_device, service))) 411 | }, 412 | #[cfg(not(any(all(target_os = "linux", feature = "bluetooth"), 413 | all(target_os = "android", feature = "bluetooth"), 414 | all(target_os = "macos", feature = "bluetooth"))))] 415 | BluetoothDevice::Empty(_device) => { 416 | BluetoothGATTService::Empty(Arc::new(BluetoothGATTServiceEmpty::new(service))) 417 | }, 418 | #[cfg(feature = "bluetooth-test")] 419 | BluetoothDevice::Mock(fake_device) => { 420 | BluetoothGATTService::Mock(FakeBluetoothGATTService::new_empty(fake_device, service)) 421 | }, 422 | } 423 | } 424 | 425 | #[cfg(feature = "bluetooth-test")] 426 | pub fn create_mock_service(device: BluetoothDevice, service: String) -> Result> { 427 | match device { 428 | BluetoothDevice::Mock(fake_device) => { 429 | Ok(BluetoothGATTService::Mock(FakeBluetoothGATTService::new_empty(fake_device, service))) 430 | }, 431 | _ => { 432 | Err(Box::from("Error! The first parameter must be a mock structure!")) 433 | }, 434 | } 435 | } 436 | 437 | pub fn get_id(&self) -> String { 438 | get_inner_and_call!(self, BluetoothGATTService, get_id) 439 | } 440 | 441 | #[cfg(feature = "bluetooth-test")] 442 | pub fn set_id(&self, id: String) { 443 | match self { 444 | &BluetoothGATTService::Mock(ref fake_service) => fake_service.set_id(id), 445 | _ => (), 446 | } 447 | } 448 | 449 | pub fn get_uuid(&self) -> Result> { 450 | get_inner_and_call!(self, BluetoothGATTService, get_uuid) 451 | } 452 | 453 | #[cfg(feature = "bluetooth-test")] 454 | pub fn set_uuid(&self, uuid: String) -> Result<(), Box> { 455 | get_inner_and_call_test_func!(self, BluetoothGATTService, set_uuid, uuid) 456 | } 457 | 458 | pub fn is_primary(&self) -> Result> { 459 | get_inner_and_call!(self, BluetoothGATTService, is_primary) 460 | } 461 | 462 | #[cfg(feature = "bluetooth-test")] 463 | pub fn set_primary(&self, primary: bool) -> Result<(), Box> { 464 | get_inner_and_call_test_func!(self, BluetoothGATTService, set_is_primary, primary) 465 | } 466 | 467 | pub fn get_includes(&self, device: BluetoothDevice) -> Result, Box> { 468 | let services = get_inner_and_call!(self, BluetoothGATTService, get_includes)?; 469 | Ok(services.into_iter().map(|service| BluetoothGATTService::create_service(device.clone(), service)).collect()) 470 | } 471 | 472 | pub fn get_gatt_characteristics(&self) -> Result, Box> { 473 | let characteristics = get_inner_and_call!(self, BluetoothGATTService, get_gatt_characteristics)?; 474 | Ok(characteristics.into_iter() 475 | .map(|characteristic| 476 | BluetoothGATTCharacteristic::create_characteristic(self.clone(), characteristic)) 477 | .collect()) 478 | } 479 | } 480 | 481 | impl BluetoothGATTCharacteristic { 482 | fn create_characteristic(service: BluetoothGATTService, characteristic: String) -> BluetoothGATTCharacteristic { 483 | match service { 484 | #[cfg(all(target_os = "linux", feature = "bluetooth"))] 485 | BluetoothGATTService::Bluez(_bluez_service) => { 486 | BluetoothGATTCharacteristic::Bluez(Arc::new(BluetoothGATTCharacteristicBluez::new(characteristic))) 487 | }, 488 | #[cfg(all(target_os = "android", feature = "bluetooth"))] 489 | BluetoothGATTService::Android(android_service) => { 490 | BluetoothGATTCharacteristic::Android( 491 | Arc::new(BluetoothGATTCharacteristicAndroid::new(android_service, characteristic))) 492 | }, 493 | #[cfg(all(target_os = "macos", feature = "bluetooth"))] 494 | BluetoothGATTService::Mac(mac_service) => { 495 | BluetoothGATTCharacteristic::Mac(Arc::new(BluetoothGATTCharacteristicMac::new(mac_service, characteristic))) 496 | }, 497 | #[cfg(not(any(all(target_os = "linux", feature = "bluetooth"), 498 | all(target_os = "android", feature = "bluetooth"), 499 | all(target_os = "macos", feature = "bluetooth"))))] 500 | BluetoothGATTService::Empty(_service) => { 501 | BluetoothGATTCharacteristic::Empty(Arc::new(BluetoothGATTCharacteristicEmpty::new(characteristic))) 502 | }, 503 | #[cfg(feature = "bluetooth-test")] 504 | BluetoothGATTService::Mock(fake_service) => { 505 | BluetoothGATTCharacteristic::Mock( 506 | FakeBluetoothGATTCharacteristic::new_empty(fake_service, characteristic)) 507 | }, 508 | } 509 | } 510 | 511 | #[cfg(feature = "bluetooth-test")] 512 | pub fn create_mock_characteristic(service: BluetoothGATTService, 513 | characteristic: String) 514 | -> Result> { 515 | match service { 516 | BluetoothGATTService::Mock(fake_service) => { 517 | Ok(BluetoothGATTCharacteristic::Mock( 518 | FakeBluetoothGATTCharacteristic::new_empty(fake_service, characteristic))) 519 | }, 520 | _ => { 521 | Err(Box::from("Error! The first parameter must be a mock structure!")) 522 | }, 523 | } 524 | } 525 | 526 | pub fn get_id(&self) -> String { 527 | get_inner_and_call!(self, BluetoothGATTCharacteristic, get_id) 528 | } 529 | 530 | #[cfg(feature = "bluetooth-test")] 531 | pub fn set_id(&self, id: String) { 532 | match self { 533 | &BluetoothGATTCharacteristic::Mock(ref fake_characteristic) => fake_characteristic.set_id(id), 534 | _ => (), 535 | } 536 | } 537 | 538 | pub fn get_uuid(&self) -> Result> { 539 | get_inner_and_call!(self, BluetoothGATTCharacteristic, get_uuid) 540 | } 541 | 542 | #[cfg(feature = "bluetooth-test")] 543 | pub fn set_uuid(&self, uuid: String) -> Result<(), Box> { 544 | get_inner_and_call_test_func!(self, BluetoothGATTCharacteristic, set_uuid, uuid) 545 | } 546 | 547 | pub fn get_value(&self) -> Result, Box> { 548 | get_inner_and_call!(self, BluetoothGATTCharacteristic, get_value) 549 | } 550 | 551 | #[cfg(feature = "bluetooth-test")] 552 | pub fn set_value(&self, value: Vec) -> Result<(), Box> { 553 | get_inner_and_call_test_func!(self, BluetoothGATTCharacteristic, set_value, Some(value)) 554 | } 555 | 556 | pub fn is_notifying(&self) -> Result> { 557 | get_inner_and_call!(self, BluetoothGATTCharacteristic, is_notifying) 558 | } 559 | 560 | #[cfg(feature = "bluetooth-test")] 561 | pub fn set_notifying(&self, notifying: bool) -> Result<(), Box> { 562 | get_inner_and_call_test_func!(self, BluetoothGATTCharacteristic, set_notifying, notifying) 563 | } 564 | 565 | pub fn get_flags(&self) -> Result, Box> { 566 | get_inner_and_call!(self, BluetoothGATTCharacteristic, get_flags) 567 | } 568 | 569 | #[cfg(feature = "bluetooth-test")] 570 | pub fn set_flags(&self, flags: Vec) -> Result<(), Box> { 571 | get_inner_and_call_test_func!(self, BluetoothGATTCharacteristic, set_flags, flags) 572 | } 573 | 574 | pub fn get_gatt_descriptors(&self) -> Result, Box> { 575 | let descriptors = get_inner_and_call!(self, BluetoothGATTCharacteristic, get_gatt_descriptors)?; 576 | Ok(descriptors.into_iter() 577 | .map(|descriptor| BluetoothGATTDescriptor::create_descriptor(self.clone(), descriptor)) 578 | .collect()) 579 | } 580 | 581 | pub fn read_value(&self) -> Result, Box> { 582 | get_inner_and_call!(@with_bluez_offset, self, BluetoothGATTCharacteristic, read_value) 583 | } 584 | 585 | pub fn write_value(&self, values: Vec) -> Result<(), Box> { 586 | get_inner_and_call!(@with_bluez_offset, self, BluetoothGATTCharacteristic, write_value, values) 587 | } 588 | 589 | pub fn start_notify(&self) -> Result<(), Box> { 590 | get_inner_and_call!(self, BluetoothGATTCharacteristic, start_notify) 591 | } 592 | 593 | pub fn stop_notify(&self) -> Result<(), Box> { 594 | get_inner_and_call!(self, BluetoothGATTCharacteristic, stop_notify) 595 | } 596 | } 597 | 598 | impl BluetoothGATTDescriptor { 599 | fn create_descriptor(characteristic: BluetoothGATTCharacteristic, descriptor: String) -> BluetoothGATTDescriptor { 600 | match characteristic { 601 | #[cfg(all(target_os = "linux", feature = "bluetooth"))] 602 | BluetoothGATTCharacteristic::Bluez(_bluez_characteristic) => { 603 | BluetoothGATTDescriptor::Bluez(Arc::new(BluetoothGATTDescriptorBluez::new(descriptor))) 604 | }, 605 | #[cfg(all(target_os = "android", feature = "bluetooth"))] 606 | BluetoothGATTCharacteristic::Android(android_characteristic) => { 607 | BluetoothGATTDescriptor::Android( 608 | Arc::new(BluetoothGATTDescriptorAndroid::new(android_characteristic, descriptor))) 609 | }, 610 | #[cfg(all(target_os = "macos", feature = "bluetooth"))] 611 | BluetoothGATTCharacteristic::Mac(_mac_characteristic) => { 612 | BluetoothGATTDescriptor::Mac(Arc::new(BluetoothGATTDescriptorMac::new(descriptor))) 613 | }, 614 | #[cfg(not(any(all(target_os = "linux", feature = "bluetooth"), 615 | all(target_os = "android", feature = "bluetooth"), 616 | all(target_os = "macos", feature = "bluetooth"))))] 617 | BluetoothGATTCharacteristic::Empty(_characteristic) => { 618 | BluetoothGATTDescriptor::Empty(Arc::new(BluetoothGATTDescriptorEmpty::new(descriptor))) 619 | }, 620 | #[cfg(feature = "bluetooth-test")] 621 | BluetoothGATTCharacteristic::Mock(fake_characteristic) => { 622 | BluetoothGATTDescriptor::Mock(FakeBluetoothGATTDescriptor::new_empty(fake_characteristic, descriptor)) 623 | }, 624 | } 625 | } 626 | 627 | #[cfg(feature = "bluetooth-test")] 628 | pub fn create_mock_descriptor(characteristic: BluetoothGATTCharacteristic, 629 | descriptor: String) 630 | -> Result> { 631 | match characteristic { 632 | BluetoothGATTCharacteristic::Mock(fake_characteristic) => { 633 | Ok(BluetoothGATTDescriptor::Mock( 634 | FakeBluetoothGATTDescriptor::new_empty(fake_characteristic, descriptor))) 635 | }, 636 | _ => { 637 | Err(Box::from(NOT_SUPPORTED_ON_MOCK_ERROR)) 638 | }, 639 | } 640 | } 641 | 642 | pub fn get_id(&self) -> String { 643 | get_inner_and_call!(self, BluetoothGATTDescriptor, get_id) 644 | } 645 | 646 | #[cfg(feature = "bluetooth-test")] 647 | pub fn set_id(&self, id: String) { 648 | match self { 649 | &BluetoothGATTDescriptor::Mock(ref fake_descriptor) => fake_descriptor.set_id(id), 650 | _ => (), 651 | } 652 | } 653 | 654 | pub fn get_uuid(&self) -> Result> { 655 | get_inner_and_call!(self, BluetoothGATTDescriptor, get_uuid) 656 | } 657 | 658 | #[cfg(feature = "bluetooth-test")] 659 | pub fn set_uuid(&self, uuid: String) -> Result<(), Box> { 660 | get_inner_and_call_test_func!(self, BluetoothGATTDescriptor, set_uuid, uuid) 661 | } 662 | 663 | pub fn get_value(&self) -> Result, Box> { 664 | get_inner_and_call!(self, BluetoothGATTDescriptor, get_value) 665 | } 666 | 667 | #[cfg(feature = "bluetooth-test")] 668 | pub fn set_value(&self, value: Vec) -> Result<(), Box> { 669 | get_inner_and_call_test_func!(self, BluetoothGATTDescriptor, set_value, Some(value)) 670 | } 671 | 672 | pub fn get_flags(&self) -> Result, Box> { 673 | get_inner_and_call!(self, BluetoothGATTDescriptor, get_flags) 674 | } 675 | 676 | #[cfg(feature = "bluetooth-test")] 677 | pub fn set_flags(&self, flags: Vec) -> Result<(), Box> { 678 | get_inner_and_call_test_func!(self, BluetoothGATTDescriptor, set_flags, flags) 679 | } 680 | 681 | pub fn read_value(&self) -> Result, Box> { 682 | get_inner_and_call!(@with_bluez_offset, self, BluetoothGATTDescriptor, read_value) 683 | } 684 | 685 | pub fn write_value(&self, values: Vec) -> Result<(), Box> { 686 | get_inner_and_call!(@with_bluez_offset, self, BluetoothGATTDescriptor, write_value, values) 687 | } 688 | } 689 | -------------------------------------------------------------------------------- /src/empty.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | use std::collections::HashMap; 6 | use std::error::Error; 7 | use std::sync::Arc; 8 | 9 | const NOT_SUPPORTED_ERROR: &'static str = "Error! Not supported platform!"; 10 | 11 | #[derive(Clone, Debug)] 12 | pub struct EmptyAdapter { } 13 | 14 | impl EmptyAdapter { 15 | pub fn init() -> Result> { 16 | Ok(EmptyAdapter::new()) 17 | } 18 | 19 | fn new() -> EmptyAdapter { 20 | EmptyAdapter{ } 21 | } 22 | 23 | pub fn get_id(&self) -> String { 24 | String::new() 25 | } 26 | 27 | pub fn get_device_list(&self) -> Result, Box> { 28 | Err(Box::from(NOT_SUPPORTED_ERROR)) 29 | } 30 | 31 | pub fn get_address(&self) -> Result> { 32 | Err(Box::from(NOT_SUPPORTED_ERROR)) 33 | } 34 | 35 | pub fn get_name(&self) -> Result> { 36 | Err(Box::from(NOT_SUPPORTED_ERROR)) 37 | } 38 | 39 | pub fn get_alias(&self) -> Result> { 40 | Err(Box::from(NOT_SUPPORTED_ERROR)) 41 | } 42 | 43 | pub fn set_alias(&self, _value: String) -> Result<(), Box> { 44 | Err(Box::from(NOT_SUPPORTED_ERROR)) 45 | } 46 | 47 | pub fn get_class(&self) -> Result> { 48 | Err(Box::from(NOT_SUPPORTED_ERROR)) 49 | } 50 | 51 | pub fn is_powered(&self) -> Result> { 52 | Err(Box::from(NOT_SUPPORTED_ERROR)) 53 | } 54 | 55 | pub fn set_powered(&self, _value: bool) -> Result<(), Box> { 56 | Err(Box::from(NOT_SUPPORTED_ERROR)) 57 | } 58 | 59 | pub fn is_discoverable(&self) -> Result> { 60 | Err(Box::from(NOT_SUPPORTED_ERROR)) 61 | } 62 | 63 | pub fn set_discoverable(&self, _value: bool) -> Result<(), Box> { 64 | Err(Box::from(NOT_SUPPORTED_ERROR)) 65 | } 66 | 67 | pub fn is_pairable(&self) -> Result> { 68 | Err(Box::from(NOT_SUPPORTED_ERROR)) 69 | } 70 | 71 | pub fn set_pairable(&self, _value: bool) -> Result<(), Box> { 72 | Err(Box::from(NOT_SUPPORTED_ERROR)) 73 | } 74 | 75 | pub fn get_pairable_timeout(&self) -> Result> { 76 | Err(Box::from(NOT_SUPPORTED_ERROR)) 77 | } 78 | 79 | pub fn set_pairable_timeout(&self, _value: u32) -> Result<(), Box> { 80 | Err(Box::from(NOT_SUPPORTED_ERROR)) 81 | } 82 | 83 | pub fn get_discoverable_timeout(&self) -> Result> { 84 | Err(Box::from(NOT_SUPPORTED_ERROR)) 85 | } 86 | 87 | pub fn set_discoverable_timeout(&self, _value: u32) -> Result<(), Box> { 88 | Err(Box::from(NOT_SUPPORTED_ERROR)) 89 | } 90 | 91 | pub fn is_discovering(&self) -> Result> { 92 | Err(Box::from(NOT_SUPPORTED_ERROR)) 93 | } 94 | 95 | pub fn get_uuids(&self) -> Result, Box> { 96 | Err(Box::from(NOT_SUPPORTED_ERROR)) 97 | } 98 | 99 | pub fn get_vendor_id_source(&self) -> Result> { 100 | Err(Box::from(NOT_SUPPORTED_ERROR)) 101 | } 102 | 103 | pub fn get_vendor_id(&self) -> Result> { 104 | Err(Box::from(NOT_SUPPORTED_ERROR)) 105 | } 106 | 107 | pub fn get_product_id(&self) -> Result> { 108 | Err(Box::from(NOT_SUPPORTED_ERROR)) 109 | } 110 | 111 | pub fn get_device_id(&self) -> Result> { 112 | Err(Box::from(NOT_SUPPORTED_ERROR)) 113 | } 114 | 115 | pub fn get_modalias(&self) -> Result<(String, u32, u32, u32), Box> { 116 | Err(Box::from(NOT_SUPPORTED_ERROR)) 117 | } 118 | } 119 | 120 | #[derive(Clone, Debug)] 121 | pub struct BluetoothDiscoverySession { } 122 | 123 | impl BluetoothDiscoverySession { 124 | pub fn create_session(_adapter: Arc) -> Result> { 125 | Ok(BluetoothDiscoverySession{ }) 126 | } 127 | 128 | pub fn start_discovery(&self) -> Result<(), Box> { 129 | Err(Box::from(NOT_SUPPORTED_ERROR)) 130 | } 131 | 132 | pub fn stop_discovery(&self) -> Result<(), Box> { 133 | Err(Box::from(NOT_SUPPORTED_ERROR)) 134 | } 135 | } 136 | 137 | #[derive(Clone, Debug)] 138 | pub struct BluetoothDevice { } 139 | 140 | impl BluetoothDevice { 141 | pub fn new(_device: String) -> BluetoothDevice { 142 | BluetoothDevice { } 143 | } 144 | 145 | pub fn get_id(&self) -> String { 146 | String::new() 147 | } 148 | 149 | pub fn get_address(&self) -> Result> { 150 | Err(Box::from(NOT_SUPPORTED_ERROR)) 151 | } 152 | 153 | pub fn get_name(&self) -> Result> { 154 | Err(Box::from(NOT_SUPPORTED_ERROR)) 155 | } 156 | 157 | pub fn get_icon(&self) -> Result> { 158 | Err(Box::from(NOT_SUPPORTED_ERROR)) 159 | } 160 | 161 | pub fn get_class(&self) -> Result> { 162 | Err(Box::from(NOT_SUPPORTED_ERROR)) 163 | } 164 | 165 | pub fn get_appearance(&self) -> Result> { 166 | Err(Box::from(NOT_SUPPORTED_ERROR)) 167 | } 168 | 169 | pub fn get_uuids(&self) -> Result, Box> { 170 | Err(Box::from(NOT_SUPPORTED_ERROR)) 171 | } 172 | 173 | pub fn is_paired(&self) -> Result> { 174 | Err(Box::from(NOT_SUPPORTED_ERROR)) 175 | } 176 | 177 | pub fn is_connected(&self) -> Result> { 178 | Err(Box::from(NOT_SUPPORTED_ERROR)) 179 | } 180 | 181 | pub fn is_trusted(&self) -> Result> { 182 | Err(Box::from(NOT_SUPPORTED_ERROR)) 183 | } 184 | 185 | pub fn is_blocked(&self) -> Result> { 186 | Err(Box::from(NOT_SUPPORTED_ERROR)) 187 | } 188 | 189 | pub fn get_alias(&self) -> Result> { 190 | Err(Box::from(NOT_SUPPORTED_ERROR)) 191 | } 192 | 193 | pub fn set_alias(&self, _value: String) -> Result<(), Box> { 194 | Err(Box::from(NOT_SUPPORTED_ERROR)) 195 | } 196 | 197 | pub fn is_legacy_pairing(&self) -> Result> { 198 | Err(Box::from(NOT_SUPPORTED_ERROR)) 199 | } 200 | 201 | pub fn get_vendor_id_source(&self) -> Result> { 202 | Err(Box::from(NOT_SUPPORTED_ERROR)) 203 | } 204 | 205 | pub fn get_vendor_id(&self) -> Result> { 206 | Err(Box::from(NOT_SUPPORTED_ERROR)) 207 | } 208 | 209 | pub fn get_product_id(&self) -> Result> { 210 | Err(Box::from(NOT_SUPPORTED_ERROR)) 211 | } 212 | 213 | pub fn get_device_id(&self) -> Result> { 214 | Err(Box::from(NOT_SUPPORTED_ERROR)) 215 | } 216 | 217 | pub fn get_modalias(&self) -> Result<(String, u32, u32, u32), Box> { 218 | Err(Box::from(NOT_SUPPORTED_ERROR)) 219 | } 220 | 221 | pub fn get_rssi(&self) -> Result> { 222 | Err(Box::from(NOT_SUPPORTED_ERROR)) 223 | } 224 | 225 | pub fn get_tx_power(&self) -> Result> { 226 | Err(Box::from(NOT_SUPPORTED_ERROR)) 227 | } 228 | 229 | pub fn get_manufacturer_data(&self) -> Result>, Box> { 230 | Err(Box::from(NOT_SUPPORTED_ERROR)) 231 | } 232 | 233 | pub fn get_service_data(&self) -> Result>, Box> { 234 | Err(Box::from(NOT_SUPPORTED_ERROR)) 235 | } 236 | 237 | pub fn get_gatt_services(&self) -> Result, Box> { 238 | Err(Box::from(NOT_SUPPORTED_ERROR)) 239 | } 240 | 241 | pub fn connect(&self) -> Result<(), Box> { 242 | Err(Box::from(NOT_SUPPORTED_ERROR)) 243 | } 244 | 245 | pub fn disconnect(&self) -> Result<(), Box> { 246 | Err(Box::from(NOT_SUPPORTED_ERROR)) 247 | } 248 | 249 | pub fn connect_profile(&self, _uuid: String) -> Result<(), Box> { 250 | Err(Box::from(NOT_SUPPORTED_ERROR)) 251 | } 252 | 253 | pub fn disconnect_profile(&self, _uuid: String) -> Result<(), Box> { 254 | Err(Box::from(NOT_SUPPORTED_ERROR)) 255 | } 256 | 257 | pub fn pair(&self) -> Result<(), Box> { 258 | Err(Box::from(NOT_SUPPORTED_ERROR)) 259 | } 260 | 261 | pub fn cancel_pairing(&self) -> Result<(), Box> { 262 | Err(Box::from(NOT_SUPPORTED_ERROR)) 263 | } 264 | } 265 | 266 | #[derive(Clone, Debug)] 267 | pub struct BluetoothGATTService { } 268 | 269 | impl BluetoothGATTService { 270 | pub fn new(_service: String) -> BluetoothGATTService { 271 | BluetoothGATTService { } 272 | } 273 | 274 | pub fn get_id(&self) -> String { 275 | String::new() 276 | } 277 | 278 | pub fn get_uuid(&self) -> Result> { 279 | Err(Box::from(NOT_SUPPORTED_ERROR)) 280 | } 281 | 282 | pub fn is_primary(&self) -> Result> { 283 | Err(Box::from(NOT_SUPPORTED_ERROR)) 284 | } 285 | 286 | pub fn get_includes(&self) -> Result, Box> { 287 | Err(Box::from(NOT_SUPPORTED_ERROR)) 288 | } 289 | 290 | pub fn get_gatt_characteristics(&self) -> Result, Box> { 291 | Err(Box::from(NOT_SUPPORTED_ERROR)) 292 | } 293 | } 294 | 295 | #[derive(Clone, Debug)] 296 | pub struct BluetoothGATTCharacteristic { } 297 | 298 | impl BluetoothGATTCharacteristic { 299 | pub fn new(_characteristic: String) -> BluetoothGATTCharacteristic { 300 | BluetoothGATTCharacteristic { } 301 | } 302 | 303 | pub fn get_id(&self) -> String { 304 | String::new() 305 | } 306 | 307 | pub fn get_uuid(&self) -> Result> { 308 | Err(Box::from(NOT_SUPPORTED_ERROR)) 309 | } 310 | 311 | pub fn get_value(&self) -> Result, Box> { 312 | Err(Box::from(NOT_SUPPORTED_ERROR)) 313 | } 314 | 315 | pub fn is_notifying(&self) -> Result> { 316 | Err(Box::from(NOT_SUPPORTED_ERROR)) 317 | } 318 | 319 | pub fn get_flags(&self) -> Result, Box> { 320 | Err(Box::from(NOT_SUPPORTED_ERROR)) 321 | } 322 | 323 | pub fn get_gatt_descriptors(&self) -> Result, Box> { 324 | Err(Box::from(NOT_SUPPORTED_ERROR)) 325 | } 326 | 327 | pub fn read_value(&self) -> Result, Box> { 328 | Err(Box::from(NOT_SUPPORTED_ERROR)) 329 | } 330 | 331 | pub fn write_value(&self, _values: Vec) -> Result<(), Box> { 332 | Err(Box::from(NOT_SUPPORTED_ERROR)) 333 | } 334 | 335 | pub fn start_notify(&self) -> Result<(), Box> { 336 | Err(Box::from(NOT_SUPPORTED_ERROR)) 337 | } 338 | 339 | pub fn stop_notify(&self) -> Result<(), Box> { 340 | Err(Box::from(NOT_SUPPORTED_ERROR)) 341 | } 342 | } 343 | 344 | #[derive(Clone, Debug)] 345 | pub struct BluetoothGATTDescriptor { } 346 | 347 | impl BluetoothGATTDescriptor { 348 | pub fn new(_descriptor: String) -> BluetoothGATTDescriptor { 349 | BluetoothGATTDescriptor { } 350 | } 351 | 352 | pub fn get_id(&self) -> String { 353 | String::new() 354 | } 355 | 356 | pub fn get_uuid(&self) -> Result> { 357 | Err(Box::from(NOT_SUPPORTED_ERROR)) 358 | } 359 | 360 | pub fn get_value(&self) -> Result, Box> { 361 | Err(Box::from(NOT_SUPPORTED_ERROR)) 362 | } 363 | 364 | pub fn get_flags(&self) -> Result, Box> { 365 | Err(Box::from(NOT_SUPPORTED_ERROR)) 366 | } 367 | 368 | pub fn read_value(&self) -> Result, Box> { 369 | Err(Box::from(NOT_SUPPORTED_ERROR)) 370 | } 371 | 372 | pub fn write_value(&self, _values: Vec) -> Result<(), Box> { 373 | Err(Box::from(NOT_SUPPORTED_ERROR)) 374 | } 375 | } -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | #[cfg(all(target_os = "linux", feature = "bluetooth"))] 6 | extern crate blurz; 7 | #[cfg(all(target_os = "android", feature = "bluetooth"))] 8 | extern crate blurdroid; 9 | #[cfg(all(target_os = "macos", feature = "bluetooth"))] 10 | extern crate blurmac; 11 | #[cfg(feature = "bluetooth-test")] 12 | extern crate blurmock; 13 | pub mod adapter; 14 | pub mod bluetooth; 15 | #[cfg(not(any(all(target_os = "linux", feature = "bluetooth"), 16 | all(target_os = "android", feature = "bluetooth"), 17 | all(target_os = "macos", feature = "bluetooth"))))] 18 | mod empty; 19 | mod macros; -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | macro_rules! get_inner_and_call( 6 | ($enum_value: expr, $enum_type: ident, $function_name: ident) => { 7 | match $enum_value { 8 | #[cfg(all(target_os = "linux", feature = "bluetooth"))] 9 | &$enum_type::Bluez(ref bluez) => bluez.$function_name(), 10 | #[cfg(all(target_os = "android", feature = "bluetooth"))] 11 | &$enum_type::Android(ref android) => android.$function_name(), 12 | #[cfg(all(target_os = "macos", feature = "bluetooth"))] 13 | &$enum_type::Mac(ref mac) => mac.$function_name(), 14 | #[cfg(not(any(all(target_os = "linux", feature = "bluetooth"), 15 | all(target_os = "android", feature = "bluetooth"), 16 | all(target_os = "macos", feature = "bluetooth"))))] 17 | &$enum_type::Empty(ref empty) => empty.$function_name(), 18 | #[cfg(feature = "bluetooth-test")] 19 | &$enum_type::Mock(ref fake) => fake.$function_name(), 20 | } 21 | }; 22 | 23 | (@with_bluez_offset, $enum_value: expr, $enum_type: ident, $function_name: ident) => { 24 | match $enum_value { 25 | #[cfg(all(target_os = "linux", feature = "bluetooth"))] 26 | &$enum_type::Bluez(ref bluez) => bluez.$function_name(None), 27 | #[cfg(all(target_os = "android", feature = "bluetooth"))] 28 | &$enum_type::Android(ref android) => android.$function_name(), 29 | #[cfg(all(target_os = "macos", feature = "bluetooth"))] 30 | &$enum_type::Mac(ref mac) => mac.$function_name(), 31 | #[cfg(not(any(all(target_os = "linux", feature = "bluetooth"), 32 | all(target_os = "android", feature = "bluetooth"), 33 | all(target_os = "macos", feature = "bluetooth"))))] 34 | &$enum_type::Empty(ref empty) => empty.$function_name(), 35 | #[cfg(feature = "bluetooth-test")] 36 | &$enum_type::Mock(ref fake) => fake.$function_name(), 37 | } 38 | }; 39 | 40 | ($enum_value: expr, $enum_type: ident, $function_name: ident, $value: expr) => { 41 | match $enum_value { 42 | #[cfg(all(target_os = "linux", feature = "bluetooth"))] 43 | &$enum_type::Bluez(ref bluez) => bluez.$function_name($value), 44 | #[cfg(all(target_os = "android", feature = "bluetooth"))] 45 | &$enum_type::Android(ref android) => android.$function_name($value), 46 | #[cfg(all(target_os = "macos", feature = "bluetooth"))] 47 | &$enum_type::Mac(ref mac) => mac.$function_name($value), 48 | #[cfg(not(any(all(target_os = "linux", feature = "bluetooth"), 49 | all(target_os = "android", feature = "bluetooth"), 50 | all(target_os = "macos", feature = "bluetooth"))))] 51 | &$enum_type::Empty(ref empty) => empty.$function_name($value), 52 | #[cfg(feature = "bluetooth-test")] 53 | &$enum_type::Mock(ref fake) => fake.$function_name($value), 54 | } 55 | }; 56 | 57 | (@with_bluez_offset, $enum_value: expr, $enum_type: ident, $function_name: ident, $value: expr) => { 58 | match $enum_value { 59 | #[cfg(all(target_os = "linux", feature = "bluetooth"))] 60 | &$enum_type::Bluez(ref bluez) => bluez.$function_name($value, None), 61 | #[cfg(all(target_os = "android", feature = "bluetooth"))] 62 | &$enum_type::Android(ref android) => android.$function_name($value), 63 | #[cfg(all(target_os = "macos", feature = "bluetooth"))] 64 | &$enum_type::Mac(ref mac) => mac.$function_name($value), 65 | #[cfg(not(any(all(target_os = "linux", feature = "bluetooth"), 66 | all(target_os = "android", feature = "bluetooth"), 67 | all(target_os = "macos", feature = "bluetooth"))))] 68 | &$enum_type::Empty(ref empty) => empty.$function_name($value), 69 | #[cfg(feature = "bluetooth-test")] 70 | &$enum_type::Mock(ref fake) => fake.$function_name($value), 71 | } 72 | }; 73 | ); 74 | 75 | #[cfg(feature = "bluetooth-test")] 76 | macro_rules! get_inner_and_call_test_func { 77 | ($enum_value: expr, $enum_type: ident, $function_name: ident, $value: expr) => { 78 | match $enum_value { 79 | &$enum_type::Mock(ref fake) => fake.$function_name($value), 80 | _ => Err(Box::from("Error! Test functions are not supported on real devices!")), 81 | } 82 | }; 83 | 84 | ($enum_value: expr, $enum_type: ident, $function_name: ident) => { 85 | match $enum_value { 86 | &$enum_type::Mock(ref fake) => fake.$function_name(), 87 | _ => Err(Box::from("Error! Test functions are not supported on real devices!")), 88 | } 89 | }; 90 | } 91 | 92 | pub(crate) use get_inner_and_call; 93 | #[cfg(feature = "bluetooth-test")] 94 | pub(crate) use get_inner_and_call_test_func; --------------------------------------------------------------------------------