├── .github └── workflows │ ├── check.yml │ └── release.yml ├── .gitignore ├── .vscode └── settings.json ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── ci.sh ├── rust-toolchain.toml ├── rustfmt.toml ├── src ├── cmd.rs ├── cmd │ ├── controller_baseband.rs │ ├── info.rs │ ├── le.rs │ ├── link_control.rs │ └── status.rs ├── controller.rs ├── controller │ └── blocking.rs ├── data.rs ├── event.rs ├── event │ └── le.rs ├── fmt.rs ├── lib.rs ├── param.rs ├── param │ ├── cmd_mask.rs │ ├── event_masks.rs │ ├── feature_masks.rs │ ├── le.rs │ ├── macros.rs │ ├── primitives.rs │ └── status.rs ├── transport.rs ├── uuid.rs └── uuid │ ├── appearance │ ├── categories.rs │ └── mod.rs │ ├── browse_group_identifiers.rs │ ├── characteristic.rs │ ├── declarations.rs │ ├── descriptors.rs │ ├── mesh_profile.rs │ ├── object_types.rs │ ├── protocol_identifiers.rs │ ├── service.rs │ ├── service_class.rs │ └── units.rs └── update_uuids ├── .gitignore ├── Cargo.toml └── src ├── gss.rs ├── main.rs ├── utils.rs ├── writer.rs └── yaml.rs /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: Check Rust 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | check: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Install nightly 18 | run: rustup toolchain add --component=rustfmt nightly 19 | - name: Checks 20 | run: ./ci.sh 21 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v[0-9]+.[0-9]+.[0-9]+' 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | # Re Run the Checks 13 | check: 14 | name: Check Rust 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: Install nightly 19 | run: rustup toolchain add --component=rustfmt nightly 20 | - name: Checks 21 | run: ./ci.sh 22 | 23 | release: 24 | # Only run if the checks pass 25 | name: Publish to crates.io 26 | needs: check 27 | runs-on: ubuntu-latest 28 | timeout-minutes: 10 29 | steps: 30 | - name: Checkout code 31 | uses: actions/checkout@v4 32 | 33 | - name: Verify Version 34 | run: | 35 | TAG_VERSION=${GITHUB_REF#refs/tags/v} 36 | CARGO_VERSION=$(grep '^version =' Cargo.toml | sed -E 's/version = "([^"]+)"/\1/') 37 | if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then 38 | echo "Version mismatch: tag is $TAG_VERSION but Cargo.toml is $CARGO_VERSION" 39 | exit 1 # Exits with a non-zero status to fail the workflow 40 | fi 41 | shell: bash 42 | 43 | - name: Set up Rust 44 | uses: dtolnay/rust-toolchain@stable 45 | with: 46 | toolchain: stable 47 | 48 | - name: Build project 49 | run: cargo build --release 50 | 51 | - name: Create GitHub release 52 | env: 53 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 54 | tag: ${{ github.ref_name }} 55 | run: | 56 | gh release create "$tag" \ 57 | --repo="$GITHUB_REPOSITORY" \ 58 | --title="${GITHUB_REPOSITORY#*/} ${tag#v}" \ 59 | --generate-notes 60 | 61 | - name: Publish to crates.io 62 | env: 63 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 64 | # Publish to the main registry 65 | run: | 66 | echo "Publishing to crates.io" 67 | cargo publish 68 | # To publish to the staging/testing registry, uncomment the following lines 69 | # run: | 70 | # echo "Performing dry-run publish to crates.io" 71 | # cargo publish --dry-run 72 | 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.formatOnSaveMode": "file", 4 | } 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## 0.3.0 - 2025-04-07 9 | 10 | * feat: add a way to get the acl header of an acl data by @lulf in https://github.com/embassy-rs/bt-hci/pull/37 11 | * Add missing fields to `LeExtAdvReport` by @korbin in https://github.com/embassy-rs/bt-hci/pull/39 12 | * Added support for serialization of param using serde by @blueluna in https://github.com/embassy-rs/bt-hci/pull/36 13 | * Add 1 to the size of WriteHci-serialized byte slices by @korbin in https://github.com/embassy-rs/bt-hci/pull/38 14 | * Make `secondary_adv_phy` optional for LeExtAdvReport by @korbin in https://github.com/embassy-rs/bt-hci/pull/40 15 | * Add `LeSetExtAdvParamsV2` from BLE5.4 by @korbin in https://github.com/embassy-rs/bt-hci/pull/41 16 | 17 | ## 0.2.1 - 2025-02-20 18 | 19 | - Allow either v0.3.x or v0.4.x versions of `embassy-time`. 20 | - Fix parsing of boolean values in controller-to-host packets. 21 | - Fix parsing of Events when the data slice extends past the end of the event params. 22 | 23 | ## 0.2.0 - 2025-01-02 24 | 25 | - Update `embassy-time` from v0.3 to v0.4. 26 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Embassy project contributors"] 3 | description = "Bluetooth HCI data types" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | name = "bt-hci" 7 | repository = "https://github.com/embassy-rs/bt-hci" 8 | version = "0.3.2" 9 | documentation = "https://docs.rs/bt-hci" 10 | keywords = ["bluetooth", "hci", "BLE"] 11 | categories = ["embedded", "hardware-support", "no-std"] 12 | rust-version = "1.77" 13 | exclude = [ 14 | ".github", 15 | ".vscode", 16 | "ci.sh", 17 | "rust-toolchain.toml", 18 | "rustfmt.toml", 19 | "update_uuids", 20 | ] 21 | 22 | [features] 23 | defmt = ["dep:defmt", "embedded-io/defmt-03", "embedded-io-async/defmt-03"] 24 | serde = ["dep:serde"] 25 | 26 | [dependencies] 27 | defmt = { version = "^1", optional = true } 28 | log = { version = "^0.4", optional = true } 29 | embedded-io = "0.6.0" 30 | embedded-io-async = "0.6.0" 31 | embassy-sync = "0.7" 32 | embassy-time = { version = ">=0.3, <0.5", optional = true } 33 | heapless = "0.8" 34 | futures-intrusive = { version = "0.5.0", default-features = false } 35 | uuid = { version = "^1", default-features = false, optional = true } 36 | serde = { version = "^1", optional = true, features = [ 37 | "derive", 38 | ], default-features = false } 39 | 40 | [dev-dependencies] 41 | postcard = "1.1" 42 | 43 | [workspace] 44 | members = ["update_uuids"] 45 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) Embassy project contributors 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![crates.io][crates-badge]][crates-url] [![docs.rs][docs-badge]][docs-url] 2 | 3 | [crates-badge]: https://img.shields.io/crates/v/bt-hci 4 | [crates-url]: https://crates.io/crates/bt-hci 5 | [docs-badge]: https://docs.rs/bt-hci/badge.svg 6 | [docs-url]: https://docs.rs/bt-hci 7 | 8 | # bt-hci 9 | 10 | Rust types for the Bluetooth HCI (Host Controller Interface) specification, and traits for implementing the `Controller` part of the interface. 11 | 12 | See [Trouble](https://github.com/embassy-rs/trouble) for an example of using this crate. 13 | 14 | ## Bluetooth UUIDs 15 | 16 | The bluetooth specification includes [reference information](https://bitbucket.org/bluetooth-SIG/public/src/main/) for pre-defined UUIDs that can be used to communicate specific services, characteristics, properties, etc of a device. These are also made available as constants from this crate through the [uuid module](./src/uuid/) for users of this crate. 17 | 18 | For crate maintainers, to update these constants run the [update_uuids](./update_uuids/) binary, which will redownload the bluetooth-sig yaml spec and rebuild the uuids module based on the latest version. 19 | 20 | ## License 21 | 22 | bt-hci is licensed under either of 23 | 24 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or 25 | ) 26 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or ) 27 | 28 | at your option. 29 | 30 | ## Contribution 31 | 32 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. 33 | -------------------------------------------------------------------------------- /ci.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euxo pipefail 4 | 5 | export RUSTFLAGS=-Dwarnings 6 | 7 | cargo +nightly fmt -- --check 8 | 9 | cargo clippy 10 | cargo clippy --features embassy-time 11 | 12 | cargo clippy --features defmt 13 | cargo clippy --features defmt,embassy-time 14 | 15 | cargo clippy --features log 16 | cargo clippy --features log,embassy-time 17 | 18 | cargo clippy --features serde 19 | 20 | cargo test --features embassy-time,serde 21 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | # Before upgrading check that everything is available on all tier1 targets here: 2 | # https://rust-lang.github.io/rustup-components-history 3 | [toolchain] 4 | channel = "1.83" 5 | components = ["rust-src", "rustfmt", "llvm-tools-preview", "clippy"] 6 | targets = [ 7 | "thumbv7em-none-eabi", 8 | "thumbv7m-none-eabi", 9 | "thumbv6m-none-eabi", 10 | "thumbv7em-none-eabihf", 11 | "thumbv8m.main-none-eabihf", 12 | "wasm32-unknown-unknown", 13 | ] 14 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | group_imports = "StdExternalCrate" 2 | imports_granularity = "Module" 3 | max_width=120 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /src/cmd/controller_baseband.rs: -------------------------------------------------------------------------------- 1 | //! Controller & Baseband commands [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-5ced811b-a6ce-701a-16b2-70f2d9795c05) 2 | 3 | use crate::cmd; 4 | use crate::param::{ 5 | ConnHandle, ConnHandleCompletedPackets, ControllerToHostFlowControl, Duration, EventMask, EventMaskPage2, 6 | PowerLevelKind, 7 | }; 8 | 9 | cmd! { 10 | /// Set Event Mask command [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-9cf88217-77b4-aeb6-61fb-d1129d48a67c) 11 | SetEventMask(CONTROL_BASEBAND, 0x0001) { 12 | Params = EventMask; 13 | Return = (); 14 | } 15 | } 16 | 17 | cmd! { 18 | /// Reset command [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-b0aaafb1-0601-865c-2703-4f4caa4dee2e) 19 | Reset(CONTROL_BASEBAND, 0x0003) { 20 | Params = (); 21 | Return = (); 22 | } 23 | } 24 | 25 | cmd! { 26 | /// Read Transmit Power Level command [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-7205a3ee-15c7-cc48-c512-a959b4e3f560) 27 | ReadTransmitPowerLevel(CONTROL_BASEBAND, 0x002d) { 28 | ReadTransmitPowerLevelParams { 29 | kind: PowerLevelKind, 30 | } 31 | ReadTransmitPowerLevelReturn { 32 | tx_power_level: i8, 33 | } 34 | Handle = handle: ConnHandle; 35 | } 36 | } 37 | 38 | cmd! { 39 | /// Set Controller To Host Flow Control command [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-64d757fc-e1da-329f-6d6a-16453750f325) 40 | SetControllerToHostFlowControl(CONTROL_BASEBAND, 0x0031) { 41 | Params = ControllerToHostFlowControl; 42 | Return = (); 43 | } 44 | } 45 | 46 | cmd! { 47 | /// Host Buffer Size command [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-99748527-de87-bf9c-935d-baf7e3f35b12) 48 | HostBufferSize(CONTROL_BASEBAND, 0x0033) { 49 | HostBufferSizeParams { 50 | host_acl_data_packet_len: u16, 51 | host_sync_data_packet_len: u8, 52 | host_total_acl_data_packets: u16, 53 | host_total_sync_data_packets: u16, 54 | } 55 | Return = (); 56 | } 57 | } 58 | 59 | cmd! { 60 | /// Host Number Of Completed Packets command [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-14569cb0-16b0-7dc0-a1d4-e5a4ef44d81a) 61 | /// 62 | /// *Note:* This command only returns a [`CommandComplete`](crate::event::CommandComplete) event on error. No event is generated on success. 63 | HostNumberOfCompletedPackets(CONTROL_BASEBAND, 0x0035) { 64 | Params<'a> = &'a [ConnHandleCompletedPackets]; 65 | Return = (); 66 | } 67 | } 68 | 69 | cmd! { 70 | /// Set Event Mask Page 2 command [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-4e91f200-b802-45d6-9282-fd03c0dfefbe) 71 | SetEventMaskPage2(CONTROL_BASEBAND, 0x0063) { 72 | Params = EventMaskPage2; 73 | Return = (); 74 | } 75 | } 76 | 77 | cmd! { 78 | /// Read Authenticated Payload Timeout command [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-36c940e9-a654-f07f-75cd-2cbcf2d6adf6) 79 | ReadAuthenticatedPayloadTimeout(CONTROL_BASEBAND, 0x007b) { 80 | Params = ConnHandle; 81 | ReadAuthenticatedPayloadTimeoutReturn { 82 | timeout: Duration<10_000>, 83 | } 84 | Handle = handle: ConnHandle; 85 | } 86 | } 87 | 88 | cmd! { 89 | /// Write Authenticated Payload Timeout command [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-9bb1bb66-1857-83d8-8954-677f773225f9) 90 | WriteAuthenticatedPayloadTimeout(CONTROL_BASEBAND, 0x007c) { 91 | WriteAuthenticatedPayloadTimeoutParams { 92 | timeout: Duration<10_000>, 93 | } 94 | Return = ConnHandle; 95 | Handle = handle: ConnHandle; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/cmd/info.rs: -------------------------------------------------------------------------------- 1 | //! Informational parameters [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-42372304-c9ef-dcab-6905-4e5b64703d45) 2 | 3 | use super::cmd; 4 | use crate::param::{BdAddr, CmdMask, CoreSpecificationVersion, LmpFeatureMask}; 5 | 6 | cmd! { 7 | /// Read Local Version Information command [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-cf7fef88-faa4-fd2e-7c00-ab1ec7985a19) 8 | ReadLocalVersionInformation(INFO_PARAMS, 0x0001) { 9 | Params = (); 10 | ReadLocalVersionInformationReturn { 11 | hci_version: CoreSpecificationVersion, 12 | hci_subversion: u16, 13 | lmp_version: CoreSpecificationVersion, 14 | company_identifier: u16, 15 | lmp_subversion: u16, 16 | } 17 | } 18 | } 19 | 20 | cmd! { 21 | /// Read Local Supported Commands command [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-d9df0f48-030f-0567-ecf3-8304df5c3eb0) 22 | ReadLocalSupportedCmds(INFO_PARAMS, 0x0002) { 23 | Params = (); 24 | Return = CmdMask; 25 | } 26 | } 27 | 28 | cmd! { 29 | /// Read Local Supported Features command [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-17c54ce9-1772-096f-c512-ba080bd11d04) 30 | ReadLocalSupportedFeatures(INFO_PARAMS, 0x0003) { 31 | Params = (); 32 | Return = LmpFeatureMask; 33 | } 34 | } 35 | 36 | cmd! { 37 | /// Read BD_ADDR command [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-151a8bec-71be-df54-2043-92d366376c53) 38 | ReadBdAddr(INFO_PARAMS, 0x0009) { 39 | Params = (); 40 | Return = BdAddr; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/cmd/link_control.rs: -------------------------------------------------------------------------------- 1 | //! Link Control commands [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-fe2a33d3-28f4-9fd1-4d08-62286985c05e) 2 | 3 | use crate::cmd; 4 | use crate::param::{ConnHandle, DisconnectReason}; 5 | 6 | cmd! { 7 | /// Disconnect command [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-6bb8119e-aa67-d517-db2a-7470c35fbf4a) 8 | Disconnect(LINK_CONTROL, 0x0006) { 9 | DisconnectParams { 10 | handle: ConnHandle, 11 | reason: DisconnectReason, 12 | } 13 | Return = (); 14 | } 15 | } 16 | 17 | cmd! { 18 | /// Read Remote Version Information command [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-ebf3c9ac-0bfa-0ed0-c014-8f8691ea3fe5) 19 | ReadRemoteVersionInformation(LINK_CONTROL, 0x001d) { 20 | Params = ConnHandle; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/cmd/status.rs: -------------------------------------------------------------------------------- 1 | //! Status parameters [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-40e8a930-65b3-c409-007e-388fd48e1041) 2 | 3 | use super::cmd; 4 | use crate::param::ConnHandle; 5 | 6 | cmd! { 7 | /// Read RSSI command [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-97d75ef1-ae03-1164-a6be-61c65dc9fb94) 8 | ReadRssi(STATUS_PARAMS, 0x0005) { 9 | Params = ConnHandle; 10 | ReadRssiReturn { 11 | rssi: i8, 12 | } 13 | Handle = handle: ConnHandle; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/controller.rs: -------------------------------------------------------------------------------- 1 | //! HCI controller 2 | 3 | use core::cell::RefCell; 4 | use core::future::{poll_fn, Future}; 5 | use core::mem::MaybeUninit; 6 | use core::task::Poll; 7 | 8 | use cmd::controller_baseband::Reset; 9 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; 10 | use embassy_sync::signal::Signal; 11 | use embassy_sync::waitqueue::AtomicWaker; 12 | use embedded_io::ErrorType; 13 | use futures_intrusive::sync::LocalSemaphore; 14 | 15 | use crate::cmd::{Cmd, CmdReturnBuf}; 16 | use crate::event::{CommandComplete, Event}; 17 | use crate::param::{RemainingBytes, Status}; 18 | use crate::transport::Transport; 19 | use crate::{cmd, data, ControllerToHostPacket, FixedSizeValue, FromHciBytes}; 20 | 21 | pub mod blocking; 22 | 23 | /// Trait representing a HCI controller which supports async operations. 24 | pub trait Controller: ErrorType { 25 | /// Write ACL data to the controller. 26 | fn write_acl_data(&self, packet: &data::AclPacket) -> impl Future>; 27 | /// Write Sync data to the controller. 28 | fn write_sync_data(&self, packet: &data::SyncPacket) -> impl Future>; 29 | /// Write Iso data to the controller. 30 | fn write_iso_data(&self, packet: &data::IsoPacket) -> impl Future>; 31 | 32 | /// Read a valid HCI packet from the controller. 33 | fn read<'a>(&self, buf: &'a mut [u8]) -> impl Future, Self::Error>>; 34 | } 35 | 36 | /// Marker trait for declaring that a controller supports a given HCI command. 37 | pub trait ControllerCmdSync: Controller { 38 | /// Note: Some implementations may require [`Controller::read()`] to be polled for this to return. 39 | fn exec(&self, cmd: &C) -> impl Future>>; 40 | } 41 | 42 | /// Marker trait for declaring that a controller supports a given async HCI command. 43 | pub trait ControllerCmdAsync: Controller { 44 | /// Note: Some implementations may require [`Controller::read()`] to be polled for this to return. 45 | fn exec(&self, cmd: &C) -> impl Future>>; 46 | } 47 | 48 | /// An external Bluetooth controller with communication via [`Transport`] type `T`. 49 | /// 50 | /// The controller state holds a number of command slots that can be used 51 | /// to issue commands and await responses from an underlying controller. 52 | /// 53 | /// The contract is that before sending a command, a slot is reserved, which 54 | /// returns a signal handle that can be used to await a response. 55 | pub struct ExternalController { 56 | transport: T, 57 | slots: ControllerState, 58 | } 59 | 60 | impl ExternalController { 61 | /// Create a new instance. 62 | pub fn new(transport: T) -> Self { 63 | Self { 64 | slots: ControllerState::new(), 65 | transport, 66 | } 67 | } 68 | } 69 | 70 | impl ErrorType for ExternalController 71 | where 72 | T: ErrorType, 73 | { 74 | type Error = T::Error; 75 | } 76 | 77 | impl Controller for ExternalController 78 | where 79 | T: Transport, 80 | { 81 | async fn write_acl_data(&self, packet: &data::AclPacket<'_>) -> Result<(), Self::Error> { 82 | self.transport.write(packet).await?; 83 | Ok(()) 84 | } 85 | 86 | async fn write_sync_data(&self, packet: &data::SyncPacket<'_>) -> Result<(), Self::Error> { 87 | self.transport.write(packet).await?; 88 | Ok(()) 89 | } 90 | 91 | async fn write_iso_data(&self, packet: &data::IsoPacket<'_>) -> Result<(), Self::Error> { 92 | self.transport.write(packet).await?; 93 | Ok(()) 94 | } 95 | 96 | async fn read<'a>(&self, buf: &'a mut [u8]) -> Result, Self::Error> { 97 | loop { 98 | { 99 | // Safety: we will not hold references across loop iterations. 100 | let buf = unsafe { core::slice::from_raw_parts_mut(buf.as_mut_ptr(), buf.len()) }; 101 | let value = self.transport.read(&mut buf[..]).await?; 102 | match value { 103 | ControllerToHostPacket::Event(ref event) => match &event { 104 | Event::CommandComplete(e) => { 105 | self.slots.complete( 106 | e.cmd_opcode, 107 | e.status, 108 | e.num_hci_cmd_pkts as usize, 109 | e.return_param_bytes.as_ref(), 110 | ); 111 | continue; 112 | } 113 | Event::CommandStatus(e) => { 114 | self.slots 115 | .complete(e.cmd_opcode, e.status, e.num_hci_cmd_pkts as usize, &[]); 116 | continue; 117 | } 118 | _ => return Ok(value), 119 | }, 120 | _ => return Ok(value), 121 | } 122 | } 123 | } 124 | } 125 | } 126 | 127 | impl blocking::Controller for ExternalController 128 | where 129 | T: crate::transport::blocking::Transport, 130 | { 131 | fn write_acl_data(&self, packet: &data::AclPacket<'_>) -> Result<(), Self::Error> { 132 | loop { 133 | match self.try_write_acl_data(packet) { 134 | Err(blocking::TryError::Busy) => {} 135 | Err(blocking::TryError::Error(e)) => return Err(e), 136 | Ok(r) => return Ok(r), 137 | } 138 | } 139 | } 140 | 141 | fn write_sync_data(&self, packet: &data::SyncPacket<'_>) -> Result<(), Self::Error> { 142 | loop { 143 | match self.try_write_sync_data(packet) { 144 | Err(blocking::TryError::Busy) => {} 145 | Err(blocking::TryError::Error(e)) => return Err(e), 146 | Ok(r) => return Ok(r), 147 | } 148 | } 149 | } 150 | 151 | fn write_iso_data(&self, packet: &data::IsoPacket<'_>) -> Result<(), Self::Error> { 152 | loop { 153 | match self.try_write_iso_data(packet) { 154 | Err(blocking::TryError::Busy) => {} 155 | Err(blocking::TryError::Error(e)) => return Err(e), 156 | Ok(r) => return Ok(r), 157 | } 158 | } 159 | } 160 | 161 | fn read<'a>(&self, buf: &'a mut [u8]) -> Result, Self::Error> { 162 | loop { 163 | // Safety: we will not hold references across loop iterations. 164 | let buf = unsafe { core::slice::from_raw_parts_mut(buf.as_mut_ptr(), buf.len()) }; 165 | match self.try_read(buf) { 166 | Err(blocking::TryError::Busy) => {} 167 | Err(blocking::TryError::Error(e)) => return Err(e), 168 | Ok(r) => return Ok(r), 169 | } 170 | } 171 | } 172 | 173 | fn try_write_acl_data(&self, packet: &data::AclPacket<'_>) -> Result<(), blocking::TryError> { 174 | self.transport.write(packet)?; 175 | Ok(()) 176 | } 177 | 178 | fn try_write_sync_data(&self, packet: &data::SyncPacket<'_>) -> Result<(), blocking::TryError> { 179 | self.transport.write(packet)?; 180 | Ok(()) 181 | } 182 | 183 | fn try_write_iso_data(&self, packet: &data::IsoPacket<'_>) -> Result<(), blocking::TryError> { 184 | self.transport.write(packet)?; 185 | Ok(()) 186 | } 187 | 188 | fn try_read<'a>(&self, buf: &'a mut [u8]) -> Result, blocking::TryError> { 189 | loop { 190 | { 191 | // Safety: we will not hold references across loop iterations. 192 | let buf = unsafe { core::slice::from_raw_parts_mut(buf.as_mut_ptr(), buf.len()) }; 193 | let value = self.transport.read(&mut buf[..])?; 194 | match value { 195 | ControllerToHostPacket::Event(ref event) => match &event { 196 | Event::CommandComplete(e) => { 197 | self.slots.complete( 198 | e.cmd_opcode, 199 | e.status, 200 | e.num_hci_cmd_pkts as usize, 201 | e.return_param_bytes.as_ref(), 202 | ); 203 | continue; 204 | } 205 | Event::CommandStatus(e) => { 206 | self.slots 207 | .complete(e.cmd_opcode, e.status, e.num_hci_cmd_pkts as usize, &[]); 208 | continue; 209 | } 210 | _ => return Ok(value), 211 | }, 212 | _ => return Ok(value), 213 | } 214 | } 215 | } 216 | } 217 | } 218 | 219 | impl ControllerCmdSync for ExternalController 220 | where 221 | T: Transport, 222 | C: cmd::SyncCmd, 223 | C::Return: FixedSizeValue, 224 | { 225 | async fn exec(&self, cmd: &C) -> Result> { 226 | let mut retval: C::ReturnBuf = C::ReturnBuf::new(); 227 | 228 | //info!("Executing command with opcode {}", C::OPCODE); 229 | let (slot, idx) = self.slots.acquire(C::OPCODE, retval.as_mut()).await; 230 | let _d = OnDrop::new(|| { 231 | self.slots.release_slot(idx); 232 | }); 233 | 234 | self.transport.write(cmd).await.map_err(cmd::Error::Io)?; 235 | 236 | let result = slot.wait().await; 237 | let return_param_bytes = RemainingBytes::from_hci_bytes_complete(&retval.as_ref()[..result.len]).unwrap(); 238 | let e = CommandComplete { 239 | num_hci_cmd_pkts: 0, 240 | status: result.status, 241 | cmd_opcode: C::OPCODE, 242 | return_param_bytes, 243 | }; 244 | let r = e.to_result::().map_err(cmd::Error::Hci)?; 245 | // info!("Done executing command with opcode {}", C::OPCODE); 246 | Ok(r) 247 | } 248 | } 249 | 250 | impl ControllerCmdAsync for ExternalController 251 | where 252 | T: Transport, 253 | C: cmd::AsyncCmd, 254 | { 255 | async fn exec(&self, cmd: &C) -> Result<(), cmd::Error> { 256 | let (slot, idx) = self.slots.acquire(C::OPCODE, &mut []).await; 257 | let _d = OnDrop::new(|| { 258 | self.slots.release_slot(idx); 259 | }); 260 | 261 | self.transport.write(cmd).await.map_err(cmd::Error::Io)?; 262 | 263 | let result = slot.wait().await; 264 | result.status.to_result()?; 265 | Ok(()) 266 | } 267 | } 268 | 269 | struct ControllerState { 270 | permits: LocalSemaphore, 271 | slots: RefCell<[CommandSlot; SLOTS]>, 272 | signals: [Signal; SLOTS], 273 | waker: AtomicWaker, 274 | } 275 | 276 | struct CommandResponse { 277 | status: Status, 278 | len: usize, 279 | } 280 | 281 | enum CommandSlot { 282 | Empty, 283 | Pending { opcode: u16, event: *mut [u8] }, 284 | } 285 | 286 | impl Default for ControllerState { 287 | fn default() -> Self { 288 | Self::new() 289 | } 290 | } 291 | 292 | impl ControllerState { 293 | const EMPTY_SLOT: CommandSlot = CommandSlot::Empty; 294 | #[allow(clippy::declare_interior_mutable_const)] 295 | const EMPTY_SIGNAL: Signal = Signal::new(); 296 | 297 | fn new() -> Self { 298 | Self { 299 | permits: LocalSemaphore::new(true, 1), 300 | slots: RefCell::new([Self::EMPTY_SLOT; SLOTS]), 301 | signals: [Self::EMPTY_SIGNAL; SLOTS], 302 | waker: AtomicWaker::new(), 303 | } 304 | } 305 | 306 | fn complete(&self, op: cmd::Opcode, status: Status, num_hci_command_packets: usize, data: &[u8]) { 307 | let mut slots = self.slots.borrow_mut(); 308 | for (idx, slot) in slots.iter_mut().enumerate() { 309 | match slot { 310 | CommandSlot::Pending { opcode, event } if *opcode == op.to_raw() => { 311 | if !data.is_empty() { 312 | assert!(!event.is_null()); 313 | // Safety: since the slot is in pending, the caller stack will be valid. 314 | unsafe { (**event)[..data.len()].copy_from_slice(data) }; 315 | } 316 | self.signals[idx].signal(CommandResponse { 317 | status, 318 | len: data.len(), 319 | }); 320 | if op != Reset::OPCODE { 321 | break; 322 | } 323 | } 324 | CommandSlot::Pending { opcode: _, event: _ } if op == Reset::OPCODE => { 325 | // Signal other commands 326 | self.signals[idx].signal(CommandResponse { 327 | status: Status::CONTROLLER_BUSY, 328 | len: 0, 329 | }); 330 | } 331 | _ => {} 332 | } 333 | } 334 | 335 | // Adjust the semaphore permits ensuring we don't grant more than num_hci_cmd_pkts 336 | self.permits 337 | .release(num_hci_command_packets.saturating_sub(self.permits.permits())); 338 | } 339 | 340 | fn release_slot(&self, idx: usize) { 341 | let mut slots = self.slots.borrow_mut(); 342 | slots[idx] = CommandSlot::Empty; 343 | } 344 | 345 | async fn acquire(&self, op: cmd::Opcode, event: *mut [u8]) -> (&Signal, usize) { 346 | let to_acquire = if op == Reset::OPCODE { self.permits.permits() } else { 1 }; 347 | let mut permit = self.permits.acquire(to_acquire).await; 348 | permit.disarm(); 349 | poll_fn(|cx| match self.acquire_slot(op, event) { 350 | Some(ret) => Poll::Ready(ret), 351 | None => { 352 | self.waker.register(cx.waker()); 353 | Poll::Pending 354 | } 355 | }) 356 | .await 357 | } 358 | 359 | fn acquire_slot( 360 | &self, 361 | op: cmd::Opcode, 362 | event: *mut [u8], 363 | ) -> Option<(&Signal, usize)> { 364 | let mut slots = self.slots.borrow_mut(); 365 | // Make sure there are no existing command with this opcode 366 | for slot in slots.iter() { 367 | match slot { 368 | CommandSlot::Pending { opcode, event: _ } if *opcode == op.to_raw() => { 369 | return None; 370 | } 371 | _ => {} 372 | } 373 | } 374 | // Reserve our slot 375 | for (idx, slot) in slots.iter_mut().enumerate() { 376 | if matches!(slot, CommandSlot::Empty) { 377 | *slot = CommandSlot::Pending { 378 | opcode: op.to_raw(), 379 | event, 380 | }; 381 | self.signals[idx].reset(); 382 | return Some((&self.signals[idx], idx)); 383 | } 384 | } 385 | None 386 | } 387 | } 388 | 389 | /// A type to delay the drop handler invocation. 390 | #[must_use = "to delay the drop handler invocation to the end of the scope"] 391 | struct OnDrop { 392 | f: MaybeUninit, 393 | } 394 | 395 | impl OnDrop { 396 | /// Create a new instance. 397 | pub(crate) fn new(f: F) -> Self { 398 | Self { f: MaybeUninit::new(f) } 399 | } 400 | } 401 | 402 | impl Drop for OnDrop { 403 | fn drop(&mut self) { 404 | unsafe { self.f.as_ptr().read()() } 405 | } 406 | } 407 | -------------------------------------------------------------------------------- /src/controller/blocking.rs: -------------------------------------------------------------------------------- 1 | //! Blocking controller types and traits. 2 | use crate::controller::ErrorType; 3 | use crate::{data, ControllerToHostPacket}; 4 | 5 | /// Trait representing a HCI controller which supports blocking and non-blocking operations. 6 | pub trait Controller: ErrorType { 7 | /// Write ACL data to the controller. Blocks until done. 8 | fn write_acl_data(&self, packet: &data::AclPacket) -> Result<(), Self::Error>; 9 | 10 | /// Write Sync data to the controller. Blocks until done. 11 | fn write_sync_data(&self, packet: &data::SyncPacket) -> Result<(), Self::Error>; 12 | 13 | /// Write Iso data to the controller. Blocks until done. 14 | fn write_iso_data(&self, packet: &data::IsoPacket) -> Result<(), Self::Error>; 15 | 16 | /// Attempt to write ACL data to the controller. 17 | /// 18 | /// Returns a TryError if the operation would block. 19 | fn try_write_acl_data(&self, packet: &data::AclPacket) -> Result<(), TryError>; 20 | 21 | /// Attempt to write Sync data to the controller. 22 | /// 23 | /// Returns a TryError if the operation would block. 24 | fn try_write_sync_data(&self, packet: &data::SyncPacket) -> Result<(), TryError>; 25 | 26 | /// Attempt to write Iso data to the controller. 27 | /// 28 | /// Returns a TryError if the operation would block. 29 | fn try_write_iso_data(&self, packet: &data::IsoPacket) -> Result<(), TryError>; 30 | 31 | /// Read a valid HCI packet from the controller. Blocks until done. 32 | fn read<'a>(&self, buf: &'a mut [u8]) -> Result, Self::Error>; 33 | 34 | /// Read a valid HCI packet from the controller. 35 | /// 36 | /// Returns a TryError if the operation would block. 37 | fn try_read<'a>(&self, buf: &'a mut [u8]) -> Result, TryError>; 38 | } 39 | 40 | /// Error for representing an operation that blocks or fails 41 | /// with an error. 42 | #[derive(Debug)] 43 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 44 | pub enum TryError { 45 | /// Underlying controller error. 46 | Error(E), 47 | /// Operation would block. 48 | Busy, 49 | } 50 | -------------------------------------------------------------------------------- /src/event.rs: -------------------------------------------------------------------------------- 1 | //! HCI events [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-d21276b6-83d0-cbc3-8295-6ff23b70a0c5) 2 | 3 | use crate::cmd::{Opcode, SyncCmd}; 4 | use crate::param::{ 5 | param, ConnHandle, ConnHandleCompletedPackets, CoreSpecificationVersion, Error, LinkType, RemainingBytes, Status, 6 | }; 7 | use crate::{FromHciBytes, FromHciBytesError, ReadHci, ReadHciError}; 8 | 9 | pub mod le; 10 | 11 | use le::LeEvent; 12 | 13 | /// A trait for objects which contain the parameters for a specific HCI event 14 | pub trait EventParams<'a>: FromHciBytes<'a> { 15 | /// The event code these parameters are for 16 | const EVENT_CODE: u8; 17 | } 18 | 19 | param! { 20 | /// The header of an HCI event packet. 21 | struct EventPacketHeader { 22 | code: u8, 23 | params_len: u8, 24 | } 25 | } 26 | 27 | macro_rules! events { 28 | ( 29 | $( 30 | $(#[$attrs:meta])* 31 | struct $name:ident$(<$life:lifetime>)?($code:expr) { 32 | $( 33 | $(#[$field_attrs:meta])* 34 | $field:ident: $ty:ty 35 | ),* 36 | $(,)? 37 | } 38 | )+ 39 | ) => { 40 | /// An Event HCI packet 41 | #[non_exhaustive] 42 | #[derive(Debug, Clone, Hash)] 43 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 44 | pub enum Event<'a> { 45 | $( 46 | #[allow(missing_docs)] 47 | $name($name$(<$life>)?), 48 | )+ 49 | #[allow(missing_docs)] 50 | Le(LeEvent<'a>), 51 | /// An event with an unknown code value 52 | Unknown { 53 | /// The event code 54 | code: u8, 55 | /// The bytes of the event parameters 56 | params: &'a [u8] 57 | }, 58 | } 59 | 60 | impl<'a> Event<'a> { 61 | fn from_header_hci_bytes(header: EventPacketHeader, data: &'a [u8]) -> Result<(Self, &'a [u8]), FromHciBytesError> { 62 | let (data, rest) = if data.len() < usize::from(header.params_len) { 63 | return Err(FromHciBytesError::InvalidSize); 64 | } else { 65 | data.split_at(usize::from(header.params_len)) 66 | }; 67 | 68 | match header.code { 69 | $($code => $name::from_hci_bytes_complete(data).map(|x| (Self::$name(x), rest)),)+ 70 | 0x3e => LeEvent::from_hci_bytes_complete(data).map(|x| (Self::Le(x), rest)), 71 | _ => { 72 | Ok((Self::Unknown { code: header.code, params: data }, rest)) 73 | } 74 | } 75 | } 76 | } 77 | 78 | $( 79 | $(#[$attrs])* 80 | #[derive(Debug, Clone, Copy, Hash)] 81 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 82 | /// $name 83 | pub struct $name$(<$life>)? { 84 | $( 85 | /// $field 86 | $(#[$field_attrs])* 87 | pub $field: $ty, 88 | )* 89 | } 90 | 91 | #[automatically_derived] 92 | impl<'a> $crate::FromHciBytes<'a> for $name$(<$life>)? { 93 | #[allow(unused_variables)] 94 | fn from_hci_bytes(data: &'a [u8]) -> Result<(Self, &'a [u8]), $crate::FromHciBytesError> { 95 | let total = 0; 96 | $( 97 | let ($field, data) = <$ty as $crate::FromHciBytes>::from_hci_bytes(data)?; 98 | )* 99 | Ok((Self { 100 | $($field,)* 101 | }, data)) 102 | } 103 | } 104 | 105 | #[automatically_derived] 106 | impl<'a> $crate::event::EventParams<'a> for $name$(<$life>)? { 107 | const EVENT_CODE: u8 = $code; 108 | } 109 | )+ 110 | }; 111 | } 112 | 113 | events! { 114 | /// Disconnection Complete event [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-332adb1f-b5ac-5289-82a2-c51a59d533e7) 115 | struct DisconnectionComplete(0x05) { 116 | status: Status, 117 | handle: ConnHandle, 118 | reason: Status, 119 | } 120 | 121 | /// Encryption Change (v1) event [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-7b7d27f0-1a33-ff57-5b97-7d49a04cea26) 122 | struct EncryptionChangeV1(0x08) { 123 | status: Status, 124 | handle: ConnHandle, 125 | enabled: bool, 126 | } 127 | 128 | /// Encryption Change (v2) event [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-7b7d27f0-1a33-ff57-5b97-7d49a04cea26) 129 | struct EncryptionChangeV2(0x59) { 130 | status: Status, 131 | handle: ConnHandle, 132 | encryption_enabled: bool, 133 | encryption_key_size: u8, 134 | } 135 | 136 | /// Read Remote Version Information Complete event [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-81ed98a1-98b1-dae5-a3f5-bb7bc69d39b7) 137 | struct ReadRemoteVersionInformationComplete(0x0c) { 138 | status: Status, 139 | handle: ConnHandle, 140 | version: CoreSpecificationVersion, 141 | company_id: u16, 142 | subversion: u16, 143 | } 144 | 145 | /// Command Complete event [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-76d31a33-1a9e-07bc-87c4-8ebffee065fd) 146 | struct CommandComplete<'a>(0x0e) { 147 | num_hci_cmd_pkts: u8, 148 | cmd_opcode: Opcode, 149 | status: Status, // All return parameters have status as the first field 150 | return_param_bytes: RemainingBytes<'a>, 151 | } 152 | 153 | /// Command Status event [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-4d87067c-be74-d2ff-d5c4-86416bf7af91) 154 | struct CommandStatus(0x0f) { 155 | status: Status, 156 | num_hci_cmd_pkts: u8, 157 | cmd_opcode: Opcode, 158 | } 159 | 160 | /// Hardware Error event [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-2479a101-ae3b-5b5d-f3d4-4776af39a377) 161 | struct HardwareError(0x10) { 162 | hardware_code: u8, 163 | } 164 | 165 | /// Number Of Completed Packets event [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-9ccbff85-45ce-9c0d-6d0c-2e6e5af52b0e) 166 | struct NumberOfCompletedPackets<'a>(0x13) { 167 | completed_packets: &'a [ConnHandleCompletedPackets], 168 | } 169 | 170 | /// Data Buffer Overflow event [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-e15e12c7-d29a-8c25-349f-af6206c2ae57) 171 | struct DataBufferOverflow(0x1a) { 172 | link_type: LinkType, 173 | } 174 | 175 | /// Encryption Key Refresh Complete event [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-a321123c-83a5-7baf-6971-05edd1241357) 176 | struct EncryptionKeyRefreshComplete(0x30) { 177 | status: Status, 178 | handle: ConnHandle, 179 | } 180 | 181 | /// Authenticated Payload Timeout Expired event [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-6cfdff94-ace8-294c-6af9-d90d94653e19) 182 | struct AuthenticatedPayloadTimeoutExpired(0x57) { 183 | handle: ConnHandle, 184 | } 185 | 186 | /// HCI Event packet [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-f209cdf7-0496-8bcd-b7e1-500831511378) 187 | struct Vendor<'a>(0xff) { 188 | params: RemainingBytes<'a>, 189 | } 190 | } 191 | 192 | impl<'de> FromHciBytes<'de> for Event<'de> { 193 | fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), FromHciBytesError> { 194 | let (header, data) = EventPacketHeader::from_hci_bytes(data)?; 195 | Self::from_header_hci_bytes(header, data) 196 | } 197 | } 198 | 199 | impl<'de> ReadHci<'de> for Event<'de> { 200 | const MAX_LEN: usize = 257; 201 | 202 | fn read_hci(mut reader: R, buf: &'de mut [u8]) -> Result> { 203 | let mut header = [0; 2]; 204 | reader.read_exact(&mut header)?; 205 | let (header, _) = EventPacketHeader::from_hci_bytes(&header)?; 206 | let params_len = usize::from(header.params_len); 207 | if buf.len() < params_len { 208 | Err(ReadHciError::BufferTooSmall) 209 | } else { 210 | let (buf, _) = buf.split_at_mut(params_len); 211 | reader.read_exact(buf)?; 212 | let (pkt, _) = Self::from_header_hci_bytes(header, buf)?; 213 | Ok(pkt) 214 | } 215 | } 216 | 217 | async fn read_hci_async( 218 | mut reader: R, 219 | buf: &'de mut [u8], 220 | ) -> Result> { 221 | let mut header = [0; 2]; 222 | reader.read_exact(&mut header).await?; 223 | let (header, _) = EventPacketHeader::from_hci_bytes(&header)?; 224 | let params_len = usize::from(header.params_len); 225 | if buf.len() < params_len { 226 | Err(ReadHciError::BufferTooSmall) 227 | } else { 228 | let (buf, _) = buf.split_at_mut(params_len); 229 | reader.read_exact(buf).await?; 230 | let (pkt, _) = Self::from_header_hci_bytes(header, buf)?; 231 | Ok(pkt) 232 | } 233 | } 234 | } 235 | 236 | impl CommandComplete<'_> { 237 | /// Gets the connection handle associated with the command that has completed. 238 | /// 239 | /// For commands that return the connection handle provided as a parameter as 240 | /// their first return parameter, this will be valid even if `status` is an error. 241 | pub fn handle(&self) -> Result { 242 | C::return_handle(&self.return_param_bytes) 243 | } 244 | 245 | /// Gets a result with the return parameters for `C` or an `Error` if `status` is 246 | /// an error. 247 | /// 248 | /// # Panics 249 | /// 250 | /// May panic if `C::OPCODE` is not equal to `self.cmd_opcode`. 251 | pub fn to_result(&self) -> Result { 252 | self.status 253 | .to_result() 254 | .and_then(|_| self.return_params::().or(Err(Error::INVALID_HCI_PARAMETERS))) 255 | } 256 | 257 | /// Parses the return parameters for `C` from this event. This may fail if `status` 258 | /// is an error. 259 | /// 260 | /// # Panics 261 | /// 262 | /// May panic if `C::OPCODE` is not equal to `self.cmd_opcode`. 263 | pub fn return_params(&self) -> Result { 264 | assert_eq!(self.cmd_opcode, C::OPCODE); 265 | C::Return::from_hci_bytes(&self.return_param_bytes).and_then(|(params, rest)| { 266 | if rest.is_empty() { 267 | Ok(params) 268 | } else { 269 | Err(FromHciBytesError::InvalidSize) 270 | } 271 | }) 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /src/fmt.rs: -------------------------------------------------------------------------------- 1 | #![macro_use] 2 | #![allow(unused)] 3 | 4 | use core::fmt::{Debug, Display, LowerHex}; 5 | 6 | #[cfg(all(feature = "defmt", feature = "log"))] 7 | compile_error!("You may not enable both `defmt` and `log` features."); 8 | 9 | #[collapse_debuginfo(yes)] 10 | macro_rules! assert { 11 | ($($x:tt)*) => { 12 | { 13 | #[cfg(not(feature = "defmt"))] 14 | ::core::assert!($($x)*); 15 | #[cfg(feature = "defmt")] 16 | ::defmt::assert!($($x)*); 17 | } 18 | }; 19 | } 20 | 21 | #[collapse_debuginfo(yes)] 22 | macro_rules! assert_eq { 23 | ($($x:tt)*) => { 24 | { 25 | #[cfg(not(feature = "defmt"))] 26 | ::core::assert_eq!($($x)*); 27 | #[cfg(feature = "defmt")] 28 | ::defmt::assert_eq!($($x)*); 29 | } 30 | }; 31 | } 32 | 33 | #[collapse_debuginfo(yes)] 34 | macro_rules! assert_ne { 35 | ($($x:tt)*) => { 36 | { 37 | #[cfg(not(feature = "defmt"))] 38 | ::core::assert_ne!($($x)*); 39 | #[cfg(feature = "defmt")] 40 | ::defmt::assert_ne!($($x)*); 41 | } 42 | }; 43 | } 44 | 45 | #[collapse_debuginfo(yes)] 46 | macro_rules! debug_assert { 47 | ($($x:tt)*) => { 48 | { 49 | #[cfg(not(feature = "defmt"))] 50 | ::core::debug_assert!($($x)*); 51 | #[cfg(feature = "defmt")] 52 | ::defmt::debug_assert!($($x)*); 53 | } 54 | }; 55 | } 56 | 57 | #[collapse_debuginfo(yes)] 58 | macro_rules! debug_assert_eq { 59 | ($($x:tt)*) => { 60 | { 61 | #[cfg(not(feature = "defmt"))] 62 | ::core::debug_assert_eq!($($x)*); 63 | #[cfg(feature = "defmt")] 64 | ::defmt::debug_assert_eq!($($x)*); 65 | } 66 | }; 67 | } 68 | 69 | #[collapse_debuginfo(yes)] 70 | macro_rules! debug_assert_ne { 71 | ($($x:tt)*) => { 72 | { 73 | #[cfg(not(feature = "defmt"))] 74 | ::core::debug_assert_ne!($($x)*); 75 | #[cfg(feature = "defmt")] 76 | ::defmt::debug_assert_ne!($($x)*); 77 | } 78 | }; 79 | } 80 | 81 | #[collapse_debuginfo(yes)] 82 | macro_rules! todo { 83 | ($($x:tt)*) => { 84 | { 85 | #[cfg(not(feature = "defmt"))] 86 | ::core::todo!($($x)*); 87 | #[cfg(feature = "defmt")] 88 | ::defmt::todo!($($x)*); 89 | } 90 | }; 91 | } 92 | 93 | #[collapse_debuginfo(yes)] 94 | macro_rules! unreachable { 95 | ($($x:tt)*) => { 96 | { 97 | #[cfg(not(feature = "defmt"))] 98 | ::core::unreachable!($($x)*); 99 | #[cfg(feature = "defmt")] 100 | ::defmt::unreachable!($($x)*); 101 | } 102 | }; 103 | } 104 | 105 | #[collapse_debuginfo(yes)] 106 | macro_rules! panic { 107 | ($($x:tt)*) => { 108 | { 109 | #[cfg(not(feature = "defmt"))] 110 | ::core::panic!($($x)*); 111 | #[cfg(feature = "defmt")] 112 | ::defmt::panic!($($x)*); 113 | } 114 | }; 115 | } 116 | 117 | #[collapse_debuginfo(yes)] 118 | macro_rules! trace { 119 | ($s:literal $(, $x:expr)* $(,)?) => { 120 | { 121 | #[cfg(feature = "log")] 122 | ::log::trace!($s $(, $x)*); 123 | #[cfg(feature = "defmt")] 124 | ::defmt::trace!($s $(, $x)*); 125 | #[cfg(not(any(feature = "log", feature="defmt")))] 126 | let _ = ($( & $x ),*); 127 | } 128 | }; 129 | } 130 | 131 | #[collapse_debuginfo(yes)] 132 | macro_rules! debug { 133 | ($s:literal $(, $x:expr)* $(,)?) => { 134 | { 135 | #[cfg(feature = "log")] 136 | ::log::debug!($s $(, $x)*); 137 | #[cfg(feature = "defmt")] 138 | ::defmt::debug!($s $(, $x)*); 139 | #[cfg(not(any(feature = "log", feature="defmt")))] 140 | let _ = ($( & $x ),*); 141 | } 142 | }; 143 | } 144 | 145 | #[collapse_debuginfo(yes)] 146 | macro_rules! info { 147 | ($s:literal $(, $x:expr)* $(,)?) => { 148 | { 149 | #[cfg(feature = "log")] 150 | ::log::info!($s $(, $x)*); 151 | #[cfg(feature = "defmt")] 152 | ::defmt::info!($s $(, $x)*); 153 | #[cfg(not(any(feature = "log", feature="defmt")))] 154 | let _ = ($( & $x ),*); 155 | } 156 | }; 157 | } 158 | 159 | #[collapse_debuginfo(yes)] 160 | macro_rules! warn { 161 | ($s:literal $(, $x:expr)* $(,)?) => { 162 | { 163 | #[cfg(feature = "log")] 164 | ::log::warn!($s $(, $x)*); 165 | #[cfg(feature = "defmt")] 166 | ::defmt::warn!($s $(, $x)*); 167 | #[cfg(not(any(feature = "log", feature="defmt")))] 168 | let _ = ($( & $x ),*); 169 | } 170 | }; 171 | } 172 | 173 | #[collapse_debuginfo(yes)] 174 | macro_rules! error { 175 | ($s:literal $(, $x:expr)* $(,)?) => { 176 | { 177 | #[cfg(feature = "log")] 178 | ::log::error!($s $(, $x)*); 179 | #[cfg(feature = "defmt")] 180 | ::defmt::error!($s $(, $x)*); 181 | #[cfg(not(any(feature = "log", feature="defmt")))] 182 | let _ = ($( & $x ),*); 183 | } 184 | }; 185 | } 186 | 187 | #[cfg(feature = "defmt")] 188 | #[collapse_debuginfo(yes)] 189 | macro_rules! unwrap { 190 | ($($x:tt)*) => { 191 | ::defmt::unwrap!($($x)*) 192 | }; 193 | } 194 | 195 | #[cfg(not(feature = "defmt"))] 196 | #[collapse_debuginfo(yes)] 197 | macro_rules! unwrap { 198 | ($arg:expr) => { 199 | match $crate::fmt::Try::into_result($arg) { 200 | ::core::result::Result::Ok(t) => t, 201 | ::core::result::Result::Err(e) => { 202 | ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); 203 | } 204 | } 205 | }; 206 | ($arg:expr, $($msg:expr),+ $(,)? ) => { 207 | match $crate::fmt::Try::into_result($arg) { 208 | ::core::result::Result::Ok(t) => t, 209 | ::core::result::Result::Err(e) => { 210 | ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); 211 | } 212 | } 213 | } 214 | } 215 | 216 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 217 | pub struct NoneError; 218 | 219 | pub trait Try { 220 | type Ok; 221 | type Error; 222 | fn into_result(self) -> Result; 223 | } 224 | 225 | impl Try for Option { 226 | type Ok = T; 227 | type Error = NoneError; 228 | 229 | #[inline] 230 | fn into_result(self) -> Result { 231 | self.ok_or(NoneError) 232 | } 233 | } 234 | 235 | impl Try for Result { 236 | type Ok = T; 237 | type Error = E; 238 | 239 | #[inline] 240 | fn into_result(self) -> Self { 241 | self 242 | } 243 | } 244 | 245 | pub(crate) struct Bytes<'a>(pub &'a [u8]); 246 | 247 | impl Debug for Bytes<'_> { 248 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 249 | write!(f, "{:#02x?}", self.0) 250 | } 251 | } 252 | 253 | impl Display for Bytes<'_> { 254 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 255 | write!(f, "{:#02x?}", self.0) 256 | } 257 | } 258 | 259 | impl LowerHex for Bytes<'_> { 260 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 261 | write!(f, "{:#02x?}", self.0) 262 | } 263 | } 264 | 265 | #[cfg(feature = "defmt")] 266 | impl defmt::Format for Bytes<'_> { 267 | fn format(&self, fmt: defmt::Formatter) { 268 | defmt::write!(fmt, "{:02x}", self.0) 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![warn(missing_docs)] 3 | #![no_std] 4 | 5 | use core::future::Future; 6 | 7 | use embedded_io::ReadExactError; 8 | 9 | mod fmt; 10 | 11 | pub mod cmd; 12 | pub mod controller; 13 | pub mod data; 14 | pub mod event; 15 | pub mod param; 16 | pub mod transport; 17 | pub mod uuid; 18 | 19 | /// Errors from parsing HCI data. 20 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 21 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 22 | pub enum FromHciBytesError { 23 | /// Size of input did not match valid size. 24 | InvalidSize, 25 | /// Value of input did not match valid values. 26 | InvalidValue, 27 | } 28 | 29 | /// A HCI type which can be represented as bytes. 30 | pub trait AsHciBytes { 31 | /// Get the byte representation of this type. 32 | fn as_hci_bytes(&self) -> &[u8]; 33 | } 34 | 35 | /// A fixed size HCI type that can be deserialized from bytes. 36 | pub trait FromHciBytes<'de>: Sized { 37 | /// Deserialize bytes into a HCI type, return additional bytes. 38 | fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), FromHciBytesError>; 39 | 40 | /// Deserialize bytes into a HCI type, must consume all bytes. 41 | fn from_hci_bytes_complete(data: &'de [u8]) -> Result { 42 | let (val, buf) = Self::from_hci_bytes(data)?; 43 | if buf.is_empty() { 44 | Ok(val) 45 | } else { 46 | Err(FromHciBytesError::InvalidSize) 47 | } 48 | } 49 | } 50 | 51 | /// Errors from reading HCI data. 52 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 53 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 54 | pub enum ReadHciError { 55 | /// Not enough bytes in buffer for reading. 56 | BufferTooSmall, 57 | /// Value of input did not match valid values. 58 | InvalidValue, 59 | /// Error from underlying embedded-io type. 60 | Read(ReadExactError), 61 | } 62 | 63 | impl embedded_io::Error for ReadHciError { 64 | fn kind(&self) -> embedded_io::ErrorKind { 65 | match self { 66 | Self::BufferTooSmall => embedded_io::ErrorKind::OutOfMemory, 67 | Self::InvalidValue => embedded_io::ErrorKind::InvalidInput, 68 | Self::Read(ReadExactError::Other(e)) => e.kind(), 69 | Self::Read(ReadExactError::UnexpectedEof) => embedded_io::ErrorKind::BrokenPipe, 70 | } 71 | } 72 | } 73 | 74 | impl From> for ReadHciError { 75 | fn from(value: ReadExactError) -> Self { 76 | ReadHciError::Read(value) 77 | } 78 | } 79 | 80 | impl From for ReadHciError { 81 | fn from(value: FromHciBytesError) -> Self { 82 | match value { 83 | FromHciBytesError::InvalidSize => ReadHciError::Read(ReadExactError::UnexpectedEof), 84 | FromHciBytesError::InvalidValue => ReadHciError::InvalidValue, 85 | } 86 | } 87 | } 88 | 89 | /// Adapter trait for deserializing HCI types from embedded-io implementations. 90 | pub trait ReadHci<'de>: FromHciBytes<'de> { 91 | /// Max length read by this type. 92 | const MAX_LEN: usize; 93 | 94 | /// Read this type from the provided reader. 95 | fn read_hci(reader: R, buf: &'de mut [u8]) -> Result>; 96 | 97 | /// Read this type from the provided reader, async version. 98 | fn read_hci_async( 99 | reader: R, 100 | buf: &'de mut [u8], 101 | ) -> impl Future>>; 102 | } 103 | 104 | /// Adapter trait for serializing HCI types to embedded-io implementations. 105 | pub trait WriteHci { 106 | /// The number of bytes this value will write 107 | fn size(&self) -> usize; 108 | 109 | /// Write this value to the provided writer. 110 | fn write_hci(&self, writer: W) -> Result<(), W::Error>; 111 | 112 | /// Write this value to the provided writer, async version. 113 | fn write_hci_async(&self, writer: W) -> impl Future>; 114 | } 115 | 116 | /// Trait representing a HCI packet. 117 | pub trait HostToControllerPacket: WriteHci { 118 | /// Packet kind associated with this HCI packet. 119 | const KIND: PacketKind; 120 | } 121 | 122 | /// Marker trait for HCI values that have a known, fixed size 123 | /// 124 | /// # Safety 125 | /// - Must not contain any padding (uninitialized) bytes (recursively) 126 | /// - structs must be `#[repr(C)]` or `#[repr(transparent)]` 127 | /// - enums must be `#[repr()]` 128 | /// - Must not contain any references, pointers, atomics, or interior mutability 129 | /// - `is_valid()` must return true only if `data` is a valid bit representation of `Self` 130 | pub unsafe trait FixedSizeValue: Copy { 131 | /// Checks if the bit representation in data is valid for Self. 132 | /// 133 | /// May panic if `data.len() != core::mem::size_of::()` 134 | fn is_valid(data: &[u8]) -> bool; 135 | } 136 | 137 | /// Marker trait for [`FixedSizeValue`]s that have byte alignment. 138 | /// 139 | /// # Safety 140 | /// - Must have `core::mem::align_of::() == 1` 141 | pub unsafe trait ByteAlignedValue: FixedSizeValue { 142 | /// Obtain a reference to this type from a byte slice. 143 | /// 144 | /// # Safety 145 | /// - Must have `core::mem::align_of::() == 1` 146 | fn ref_from_hci_bytes(data: &[u8]) -> Result<(&Self, &[u8]), FromHciBytesError> { 147 | if data.len() < core::mem::size_of::() { 148 | Err(FromHciBytesError::InvalidSize) 149 | } else if !Self::is_valid(data) { 150 | Err(FromHciBytesError::InvalidValue) 151 | } else { 152 | let (data, rest) = data.split_at(core::mem::size_of::()); 153 | Ok((unsafe { &*(data.as_ptr() as *const Self) }, rest)) 154 | } 155 | } 156 | } 157 | 158 | impl AsHciBytes for T { 159 | fn as_hci_bytes(&self) -> &[u8] { 160 | unsafe { core::slice::from_raw_parts(self as *const _ as *const u8, core::mem::size_of::()) } 161 | } 162 | } 163 | 164 | impl<'de, T: FixedSizeValue> FromHciBytes<'de> for T { 165 | fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), FromHciBytesError> { 166 | if data.len() < core::mem::size_of::() { 167 | Err(FromHciBytesError::InvalidSize) 168 | } else if !Self::is_valid(data) { 169 | Err(FromHciBytesError::InvalidValue) 170 | } else { 171 | let (data, rest) = data.split_at(core::mem::size_of::()); 172 | Ok((unsafe { core::ptr::read_unaligned(data.as_ptr() as *const Self) }, rest)) 173 | } 174 | } 175 | } 176 | 177 | impl<'de, T: ByteAlignedValue> FromHciBytes<'de> for &'de [T] { 178 | fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), FromHciBytesError> { 179 | let Some((len, data)) = data.split_first() else { 180 | return Err(FromHciBytesError::InvalidSize); 181 | }; 182 | 183 | let len = usize::from(*len); 184 | let byte_len = len * core::mem::size_of::(); 185 | if byte_len > data.len() { 186 | return Err(FromHciBytesError::InvalidSize); 187 | } 188 | 189 | let (data, rest) = data.split_at(byte_len); 190 | 191 | if !data.chunks_exact(core::mem::size_of::()).all(|x| T::is_valid(x)) { 192 | return Err(FromHciBytesError::InvalidValue); 193 | } 194 | 195 | Ok(( 196 | unsafe { core::slice::from_raw_parts(data.as_ptr() as *const T, len) }, 197 | rest, 198 | )) 199 | } 200 | } 201 | 202 | impl<'de, T: FixedSizeValue> ReadHci<'de> for T { 203 | const MAX_LEN: usize = core::mem::size_of::(); 204 | 205 | fn read_hci(mut reader: R, buf: &'de mut [u8]) -> Result> { 206 | if buf.len() < core::mem::size_of::() { 207 | Err(ReadHciError::BufferTooSmall) 208 | } else { 209 | let (buf, _) = buf.split_at_mut(core::mem::size_of::()); 210 | reader.read_exact(buf)?; 211 | Self::from_hci_bytes(buf).map(|(x, _)| x).map_err(Into::into) 212 | } 213 | } 214 | 215 | async fn read_hci_async( 216 | mut reader: R, 217 | buf: &'de mut [u8], 218 | ) -> Result> { 219 | if buf.len() < core::mem::size_of::() { 220 | Err(ReadHciError::BufferTooSmall) 221 | } else { 222 | let (buf, _) = buf.split_at_mut(core::mem::size_of::()); 223 | reader.read_exact(buf).await?; 224 | Self::from_hci_bytes(buf).map(|(x, _)| x).map_err(Into::into) 225 | } 226 | } 227 | } 228 | 229 | impl WriteHci for T { 230 | #[inline(always)] 231 | fn size(&self) -> usize { 232 | core::mem::size_of::() 233 | } 234 | 235 | fn write_hci(&self, mut writer: W) -> Result<(), W::Error> { 236 | writer.write_all(self.as_hci_bytes()) 237 | } 238 | 239 | async fn write_hci_async(&self, mut writer: W) -> Result<(), W::Error> { 240 | writer.write_all(self.as_hci_bytes()).await 241 | } 242 | } 243 | 244 | /// Enum of valid HCI packet types. 245 | #[repr(u8)] 246 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 247 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 248 | pub enum PacketKind { 249 | /// Command. 250 | Cmd = 1, 251 | /// ACL data. 252 | AclData = 2, 253 | /// Sync data. 254 | SyncData = 3, 255 | /// Event. 256 | Event = 4, 257 | /// Isochronous Data. 258 | IsoData = 5, 259 | } 260 | 261 | impl<'de> FromHciBytes<'de> for PacketKind { 262 | fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), FromHciBytesError> { 263 | if data.is_empty() { 264 | Err(FromHciBytesError::InvalidSize) 265 | } else { 266 | let (data, rest) = data.split_at(1); 267 | match data[0] { 268 | 1 => Ok((PacketKind::Cmd, rest)), 269 | 2 => Ok((PacketKind::AclData, rest)), 270 | 3 => Ok((PacketKind::SyncData, rest)), 271 | 4 => Ok((PacketKind::Event, rest)), 272 | 5 => Ok((PacketKind::IsoData, rest)), 273 | _ => Err(FromHciBytesError::InvalidValue), 274 | } 275 | } 276 | } 277 | } 278 | 279 | impl WriteHci for PacketKind { 280 | #[inline(always)] 281 | fn size(&self) -> usize { 282 | 1 283 | } 284 | 285 | #[inline(always)] 286 | fn write_hci(&self, mut writer: W) -> Result<(), W::Error> { 287 | writer.write_all(&(*self as u8).to_le_bytes()) 288 | } 289 | 290 | #[inline(always)] 291 | async fn write_hci_async(&self, mut writer: W) -> Result<(), W::Error> { 292 | writer.write_all(&(*self as u8).to_le_bytes()).await 293 | } 294 | } 295 | 296 | /// Type representing valid deserialized HCI packets. 297 | #[derive(Debug)] 298 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 299 | pub enum ControllerToHostPacket<'a> { 300 | /// ACL packet. 301 | Acl(data::AclPacket<'a>), 302 | /// Sync packet. 303 | Sync(data::SyncPacket<'a>), 304 | /// Event packet. 305 | Event(event::Event<'a>), 306 | /// Isochronous packet. 307 | Iso(data::IsoPacket<'a>), 308 | } 309 | 310 | impl<'a> ControllerToHostPacket<'a> { 311 | /// The packet kind. 312 | pub fn kind(&self) -> PacketKind { 313 | match self { 314 | Self::Acl(_) => PacketKind::AclData, 315 | Self::Sync(_) => PacketKind::SyncData, 316 | Self::Event(_) => PacketKind::Event, 317 | Self::Iso(_) => PacketKind::IsoData, 318 | } 319 | } 320 | 321 | /// Deserialize data assuming a specific kind of packet. 322 | pub fn from_hci_bytes_with_kind( 323 | kind: PacketKind, 324 | data: &'a [u8], 325 | ) -> Result<(ControllerToHostPacket<'a>, &'a [u8]), FromHciBytesError> { 326 | match kind { 327 | PacketKind::Cmd => Err(FromHciBytesError::InvalidValue), 328 | PacketKind::AclData => data::AclPacket::from_hci_bytes(data).map(|(x, y)| (Self::Acl(x), y)), 329 | PacketKind::SyncData => data::SyncPacket::from_hci_bytes(data).map(|(x, y)| (Self::Sync(x), y)), 330 | PacketKind::Event => event::Event::from_hci_bytes(data).map(|(x, y)| (Self::Event(x), y)), 331 | PacketKind::IsoData => data::IsoPacket::from_hci_bytes(data).map(|(x, y)| (Self::Iso(x), y)), 332 | } 333 | } 334 | } 335 | 336 | impl<'de> FromHciBytes<'de> for ControllerToHostPacket<'de> { 337 | fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), FromHciBytesError> { 338 | let (kind, data) = PacketKind::from_hci_bytes(data)?; 339 | match kind { 340 | PacketKind::Cmd => Err(FromHciBytesError::InvalidValue), 341 | PacketKind::AclData => data::AclPacket::from_hci_bytes(data).map(|(x, y)| (Self::Acl(x), y)), 342 | PacketKind::SyncData => data::SyncPacket::from_hci_bytes(data).map(|(x, y)| (Self::Sync(x), y)), 343 | PacketKind::Event => event::Event::from_hci_bytes(data).map(|(x, y)| (Self::Event(x), y)), 344 | PacketKind::IsoData => data::IsoPacket::from_hci_bytes(data).map(|(x, y)| (Self::Iso(x), y)), 345 | } 346 | } 347 | } 348 | 349 | impl<'de> ReadHci<'de> for ControllerToHostPacket<'de> { 350 | const MAX_LEN: usize = 258; 351 | 352 | fn read_hci(mut reader: R, buf: &'de mut [u8]) -> Result> { 353 | let mut kind = [0]; 354 | reader.read_exact(&mut kind)?; 355 | match PacketKind::from_hci_bytes(&kind)?.0 { 356 | PacketKind::Cmd => Err(ReadHciError::InvalidValue), 357 | PacketKind::AclData => data::AclPacket::read_hci(reader, buf).map(Self::Acl), 358 | PacketKind::SyncData => data::SyncPacket::read_hci(reader, buf).map(Self::Sync), 359 | PacketKind::Event => event::Event::read_hci(reader, buf).map(Self::Event), 360 | PacketKind::IsoData => data::IsoPacket::read_hci(reader, buf).map(Self::Iso), 361 | } 362 | } 363 | 364 | async fn read_hci_async( 365 | mut reader: R, 366 | buf: &'de mut [u8], 367 | ) -> Result> { 368 | let mut kind = [0u8]; 369 | reader.read_exact(&mut kind).await?; 370 | match PacketKind::from_hci_bytes(&kind)?.0 { 371 | PacketKind::Cmd => Err(ReadHciError::InvalidValue), 372 | PacketKind::AclData => data::AclPacket::read_hci_async(reader, buf).await.map(Self::Acl), 373 | PacketKind::SyncData => data::SyncPacket::read_hci_async(reader, buf).await.map(Self::Sync), 374 | PacketKind::Event => event::Event::read_hci_async(reader, buf).await.map(Self::Event), 375 | PacketKind::IsoData => data::IsoPacket::read_hci_async(reader, buf).await.map(Self::Iso), 376 | } 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /src/param.rs: -------------------------------------------------------------------------------- 1 | //! Parameter types for HCI command and event packets [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-8af7a4d8-7a08-0895-b041-fdf9e27d6508) 2 | 3 | use crate::{AsHciBytes, ByteAlignedValue, FixedSizeValue, FromHciBytes, FromHciBytesError, WriteHci}; 4 | 5 | mod cmd_mask; 6 | mod event_masks; 7 | mod feature_masks; 8 | mod le; 9 | mod macros; 10 | mod primitives; 11 | mod status; 12 | 13 | pub use cmd_mask::*; 14 | pub use event_masks::*; 15 | pub use feature_masks::*; 16 | pub use le::*; 17 | pub(crate) use macros::{param, param_slice}; 18 | pub use status::*; 19 | 20 | /// A special parameter which takes all remaining bytes in the buffer 21 | #[repr(transparent)] 22 | #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 23 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 24 | pub struct RemainingBytes<'a>(&'a [u8]); 25 | 26 | impl core::ops::Deref for RemainingBytes<'_> { 27 | type Target = [u8]; 28 | 29 | fn deref(&self) -> &Self::Target { 30 | self.0 31 | } 32 | } 33 | 34 | impl WriteHci for RemainingBytes<'_> { 35 | #[inline(always)] 36 | fn size(&self) -> usize { 37 | self.0.len() 38 | } 39 | 40 | #[inline(always)] 41 | fn write_hci(&self, mut writer: W) -> Result<(), W::Error> { 42 | writer.write_all(self.0) 43 | } 44 | 45 | #[inline(always)] 46 | async fn write_hci_async(&self, mut writer: W) -> Result<(), W::Error> { 47 | writer.write_all(self.0).await 48 | } 49 | } 50 | 51 | impl AsHciBytes for RemainingBytes<'_> { 52 | fn as_hci_bytes(&self) -> &[u8] { 53 | self.0 54 | } 55 | } 56 | 57 | impl<'a> FromHciBytes<'a> for RemainingBytes<'a> { 58 | fn from_hci_bytes(data: &'a [u8]) -> Result<(Self, &'a [u8]), FromHciBytesError> { 59 | Ok((RemainingBytes(data), &[])) 60 | } 61 | } 62 | 63 | param!(struct BdAddr([u8; 6])); 64 | 65 | impl BdAddr { 66 | /// Create a new instance. 67 | pub fn new(val: [u8; 6]) -> Self { 68 | Self(val) 69 | } 70 | 71 | /// Get the byte representation. 72 | pub fn raw(&self) -> &[u8] { 73 | &self.0[..] 74 | } 75 | } 76 | 77 | unsafe impl ByteAlignedValue for BdAddr {} 78 | 79 | impl<'de> crate::FromHciBytes<'de> for &'de BdAddr { 80 | #[inline(always)] 81 | fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), crate::FromHciBytesError> { 82 | ::ref_from_hci_bytes(data) 83 | } 84 | } 85 | 86 | param!(struct ConnHandle(u16)); 87 | 88 | impl ConnHandle { 89 | /// Create a new instance. 90 | pub fn new(val: u16) -> Self { 91 | assert!(val <= 0xeff); 92 | Self(val) 93 | } 94 | 95 | /// Get the underlying representation. 96 | pub fn raw(&self) -> u16 { 97 | self.0 98 | } 99 | } 100 | 101 | /// A 16-bit duration. The `US` generic parameter indicates the timebase in µs. 102 | #[repr(transparent)] 103 | #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 104 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 105 | pub struct Duration(u16); 106 | 107 | unsafe impl FixedSizeValue for Duration { 108 | #[inline(always)] 109 | fn is_valid(_data: &[u8]) -> bool { 110 | true 111 | } 112 | } 113 | 114 | impl Duration { 115 | #[inline(always)] 116 | /// Create a new instance from raw value. 117 | pub const fn from_u16(val: u16) -> Self { 118 | Self(val) 119 | } 120 | 121 | /// Create an instance from microseconds. 122 | #[inline(always)] 123 | pub fn from_micros(val: u64) -> Self { 124 | Self::from_u16(unwrap!((val / u64::from(US)).try_into())) 125 | } 126 | 127 | /// Create an instance from milliseconds. 128 | #[inline(always)] 129 | pub fn from_millis(val: u32) -> Self { 130 | Self::from_micros(u64::from(val) * 1000) 131 | } 132 | 133 | /// Create an instance from seconds. 134 | #[inline(always)] 135 | pub fn from_secs(val: u32) -> Self { 136 | Self::from_micros(u64::from(val) * 1_000_000) 137 | } 138 | 139 | /// Get the underlying representation. 140 | #[inline(always)] 141 | pub fn as_u16(&self) -> u16 { 142 | self.0 143 | } 144 | 145 | /// Get value as microseconds. 146 | #[inline(always)] 147 | pub fn as_micros(&self) -> u64 { 148 | u64::from(self.as_u16()) * u64::from(US) 149 | } 150 | 151 | /// Get value as milliseconds. 152 | #[inline(always)] 153 | pub fn as_millis(&self) -> u32 { 154 | unwrap!((self.as_micros() / 1000).try_into()) 155 | } 156 | 157 | /// Get value as seconds. 158 | #[inline(always)] 159 | pub fn as_secs(&self) -> u32 { 160 | // (u16::MAX * u32::MAX / 1_000_000) < u32::MAX so this is safe 161 | (self.as_micros() / 1_000_000) as u32 162 | } 163 | } 164 | 165 | #[cfg(feature = "embassy-time")] 166 | impl From for Duration { 167 | fn from(duration: embassy_time::Duration) -> Self { 168 | Self::from_micros(duration.as_micros()) 169 | } 170 | } 171 | 172 | /// A 24-bit isochronous duration (in microseconds) 173 | #[repr(transparent)] 174 | #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 175 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 176 | pub struct ExtDuration([u8; 3]); 177 | 178 | unsafe impl FixedSizeValue for ExtDuration { 179 | #[inline(always)] 180 | fn is_valid(_data: &[u8]) -> bool { 181 | true 182 | } 183 | } 184 | 185 | unsafe impl ByteAlignedValue for ExtDuration {} 186 | 187 | impl<'de, const US: u16> FromHciBytes<'de> for &'de ExtDuration { 188 | #[inline(always)] 189 | fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), crate::FromHciBytesError> { 190 | as crate::ByteAlignedValue>::ref_from_hci_bytes(data) 191 | } 192 | } 193 | 194 | impl ExtDuration { 195 | /// Create a new instance from raw value. 196 | #[inline(always)] 197 | pub fn from_u32(val: u32) -> Self { 198 | assert!(val < (1 << 24)); 199 | Self(*unwrap!(val.to_le_bytes().first_chunk())) 200 | } 201 | 202 | /// Create an instance from microseconds. 203 | #[inline(always)] 204 | pub fn from_micros(val: u64) -> Self { 205 | Self::from_u32(unwrap!((val / u64::from(US)).try_into())) 206 | } 207 | 208 | /// Create an instance from milliseconds. 209 | #[inline(always)] 210 | pub fn from_millis(val: u32) -> Self { 211 | Self::from_micros(u64::from(val) * 1000) 212 | } 213 | 214 | /// Create an instance from seconds. 215 | #[inline(always)] 216 | pub fn from_secs(val: u32) -> Self { 217 | Self::from_micros(u64::from(val) * 1_000_000) 218 | } 219 | 220 | /// Get value as microseconds. 221 | #[inline(always)] 222 | pub fn as_micros(&self) -> u64 { 223 | u64::from_le_bytes([self.0[0], self.0[1], self.0[2], 0, 0, 0, 0, 0]) * u64::from(US) 224 | } 225 | 226 | /// Get value as milliseconds. 227 | #[inline(always)] 228 | pub fn as_millis(&self) -> u32 { 229 | // ((1 << 24 - 1) * u16::MAX / 1_000) < u32::MAX so this is safe 230 | (self.as_micros() / 1000) as u32 231 | } 232 | 233 | /// Get value as seconds. 234 | #[inline(always)] 235 | pub fn as_secs(&self) -> u32 { 236 | // ((1 << 24 - 1) * u16::MAX / 1_000_000) < u32::MAX so this is safe 237 | (self.as_micros() / 1_000_000) as u32 238 | } 239 | } 240 | 241 | #[cfg(feature = "embassy-time")] 242 | impl From for ExtDuration { 243 | fn from(duration: embassy_time::Duration) -> Self { 244 | Self::from_micros(duration.as_micros()) 245 | } 246 | } 247 | 248 | param!( 249 | enum DisconnectReason { 250 | AuthenticationFailure = 0x05, 251 | RemoteUserTerminatedConn = 0x13, 252 | RemoteDeviceTerminatedConnLowResources = 0x14, 253 | RemoteDeviceTerminatedConnPowerOff = 0x15, 254 | UnsupportedRemoteFeature = 0x1A, 255 | PairingWithUnitKeyNotSupported = 0x29, 256 | UnacceptableConnParameters = 0x3b, 257 | } 258 | ); 259 | 260 | param! { 261 | #[derive(Default)] 262 | enum PowerLevelKind { 263 | #[default] 264 | Current = 0, 265 | Maximum = 1, 266 | } 267 | } 268 | 269 | param! { 270 | #[derive(Default)] 271 | enum ControllerToHostFlowControl { 272 | #[default] 273 | Off = 0, 274 | AclOnSyncOff = 1, 275 | AclOffSyncOn = 2, 276 | BothOn = 3, 277 | } 278 | } 279 | 280 | param!(struct CoreSpecificationVersion(u8)); 281 | 282 | #[allow(missing_docs)] 283 | impl CoreSpecificationVersion { 284 | pub const VERSION_1_0B: CoreSpecificationVersion = CoreSpecificationVersion(0x00); 285 | pub const VERSION_1_1: CoreSpecificationVersion = CoreSpecificationVersion(0x01); 286 | pub const VERSION_1_2: CoreSpecificationVersion = CoreSpecificationVersion(0x02); 287 | pub const VERSION_2_0_EDR: CoreSpecificationVersion = CoreSpecificationVersion(0x03); 288 | pub const VERSION_2_1_EDR: CoreSpecificationVersion = CoreSpecificationVersion(0x04); 289 | pub const VERSION_3_0_HS: CoreSpecificationVersion = CoreSpecificationVersion(0x05); 290 | pub const VERSION_4_0: CoreSpecificationVersion = CoreSpecificationVersion(0x06); 291 | pub const VERSION_4_1: CoreSpecificationVersion = CoreSpecificationVersion(0x07); 292 | pub const VERSION_4_2: CoreSpecificationVersion = CoreSpecificationVersion(0x08); 293 | pub const VERSION_5_0: CoreSpecificationVersion = CoreSpecificationVersion(0x09); 294 | pub const VERSION_5_1: CoreSpecificationVersion = CoreSpecificationVersion(0x0A); 295 | pub const VERSION_5_2: CoreSpecificationVersion = CoreSpecificationVersion(0x0B); 296 | pub const VERSION_5_3: CoreSpecificationVersion = CoreSpecificationVersion(0x0C); 297 | pub const VERSION_5_4: CoreSpecificationVersion = CoreSpecificationVersion(0x0D); 298 | } 299 | 300 | unsafe impl ByteAlignedValue for CoreSpecificationVersion {} 301 | 302 | impl<'de> crate::FromHciBytes<'de> for &'de CoreSpecificationVersion { 303 | #[inline(always)] 304 | fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), crate::FromHciBytesError> { 305 | ::ref_from_hci_bytes(data) 306 | } 307 | } 308 | 309 | param! { 310 | #[derive(Default)] 311 | enum LinkType { 312 | #[default] 313 | SyncData = 0, 314 | AclData = 1, 315 | IsoData = 2, 316 | } 317 | } 318 | 319 | param_slice! { 320 | [ConnHandleCompletedPackets; 4] { 321 | handle[0]: ConnHandle, 322 | num_completed_packets[2]: u16, 323 | } 324 | } 325 | 326 | impl ConnHandleCompletedPackets { 327 | /// Create a new instance. 328 | pub fn new(handle: ConnHandle, num_completed_packets: u16) -> Self { 329 | let mut dest = [0; 4]; 330 | handle.write_hci(&mut dest[0..2]).unwrap(); 331 | num_completed_packets.write_hci(&mut dest[2..4]).unwrap(); 332 | Self(dest) 333 | } 334 | } 335 | 336 | #[cfg(test)] 337 | mod tests { 338 | #[cfg(feature = "serde")] 339 | use postcard; 340 | 341 | use super::*; 342 | 343 | #[test] 344 | fn test_encode_decode_conn_handle_completed_packets() { 345 | let completed = ConnHandleCompletedPackets::new(ConnHandle::new(42), 2334); 346 | 347 | assert_eq!(completed.handle().unwrap(), ConnHandle::new(42)); 348 | assert_eq!(completed.num_completed_packets().unwrap(), 2334); 349 | } 350 | 351 | #[cfg(feature = "serde")] 352 | #[test] 353 | fn test_serialize_bdaddr() { 354 | let bytes = [0x01, 0xaa, 0x55, 0x04, 0x05, 0xfe]; 355 | 356 | let address = BdAddr::new(bytes); 357 | 358 | let mut buffer = [0u8; 32]; 359 | let vykort = postcard::to_slice(&address, &mut buffer).unwrap(); 360 | 361 | assert_eq!(vykort, &bytes); 362 | } 363 | 364 | #[cfg(feature = "serde")] 365 | #[test] 366 | fn test_deserialize_bdaddr() { 367 | let bytes = [0xff, 0x5a, 0xa5, 0x00, 0x05, 0xfe]; 368 | 369 | let address = postcard::from_bytes::(&bytes).unwrap(); 370 | 371 | let expected = BdAddr::new(bytes); 372 | 373 | assert_eq!(address, expected); 374 | } 375 | } 376 | -------------------------------------------------------------------------------- /src/param/cmd_mask.rs: -------------------------------------------------------------------------------- 1 | use crate::{ByteAlignedValue, FixedSizeValue, FromHciBytes}; 2 | 3 | /// A command mask. 4 | #[repr(transparent)] 5 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 6 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 7 | pub struct CmdMask([u8; 64]); 8 | 9 | impl Default for CmdMask { 10 | fn default() -> Self { 11 | Self([0; 64]) 12 | } 13 | } 14 | 15 | impl CmdMask { 16 | /// Get the inner representation. 17 | pub fn into_inner(self) -> [u8; 64] { 18 | self.0 19 | } 20 | } 21 | 22 | unsafe impl FixedSizeValue for CmdMask { 23 | #[inline(always)] 24 | fn is_valid(_data: &[u8]) -> bool { 25 | true 26 | } 27 | } 28 | 29 | unsafe impl ByteAlignedValue for CmdMask {} 30 | 31 | impl<'de> FromHciBytes<'de> for &'de CmdMask { 32 | #[inline(always)] 33 | fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), crate::FromHciBytesError> { 34 | ::ref_from_hci_bytes(data) 35 | } 36 | } 37 | 38 | macro_rules! cmds { 39 | ( 40 | $( 41 | $octet:expr => { 42 | $( 43 | ($bit:expr, $getter:ident); 44 | )+ 45 | } 46 | )+ 47 | ) => { 48 | impl CmdMask { 49 | $( 50 | $( 51 | /// Getter for $getter 52 | pub fn $getter(&self) -> bool { 53 | (self.0[$octet] & (1 << $bit)) != 0 54 | } 55 | )+ 56 | )+ 57 | } 58 | } 59 | } 60 | 61 | cmds! { 62 | 0 => { 63 | (0, inquiry); 64 | (1, inquiry_cancel); 65 | (2, periodic_inquiry_mode); 66 | (3, exit_periodic_inquiry_mode); 67 | (4, create_conn); 68 | (5, disconnect); 69 | (7, create_conn_cancel); 70 | } 71 | 1 => { 72 | (0, accept_conn_request); 73 | (1, reject_conn_request); 74 | (2, link_key_request_reply); 75 | (3, link_key_request_negative_reply); 76 | (4, pin_code_request_reply); 77 | (5, pin_code_request_negative_reply); 78 | (6, change_conn_packet_kind); 79 | (7, authentication_requested); 80 | } 81 | 2 => { 82 | (0, set_conn_encryption); 83 | (1, change_conn_link_key); 84 | (2, link_key_selection); 85 | (3, remote_name_request); 86 | (4, remote_name_request_cancel); 87 | (5, read_remote_supported_features); 88 | (6, read_remote_ext_features); 89 | (7, read_remote_version_information); 90 | } 91 | 3 => { 92 | (0, read_clock_offset); 93 | (1, read_lmp_handle); 94 | } 95 | 4 => { 96 | (1, hold_mode); 97 | (2, sniff_mode); 98 | (3, exit_sniff_mode); 99 | (6, qos_setup); 100 | (7, role_discovery); 101 | } 102 | 5 => { 103 | (0, switch_role); 104 | (1, read_link_policy_settings); 105 | (2, write_link_policy_settings); 106 | (3, read_default_link_policy_settings); 107 | (4, write_default_link_policy_settings); 108 | (5, flow_specification); 109 | (6, set_event_mask); 110 | (7, reset); 111 | } 112 | 6 => { 113 | (0, set_event_filter); 114 | (1, flush); 115 | (2, read_pin_kind); 116 | (3, write_pin_kind); 117 | (5, read_stored_link_key); 118 | (6, write_stored_link_key); 119 | (7, delete_stored_link_key); 120 | } 121 | 7 => { 122 | (0, write_local_name); 123 | (1, read_local_name); 124 | (2, read_conn_accept_timeout); 125 | (3, write_conn_accept_timeout); 126 | (4, read_page_timeout); 127 | (5, write_page_timeout); 128 | (6, read_scan_enable); 129 | (7, write_scan_enable); 130 | } 131 | 8 => { 132 | (0, read_page_scan_activity); 133 | (1, write_page_scan_activity); 134 | (2, read_inquiry_scan_activity); 135 | (3, write_inquiry_scan_activity); 136 | (4, read_authentication_enable); 137 | (5, write_authentication_enable); 138 | } 139 | 9 => { 140 | (0, read_class_of_device); 141 | (1, write_class_of_device); 142 | (2, read_voice_setting); 143 | (3, write_voice_setting); 144 | (4, read_automatic_flush_timeout); 145 | (5, write_automatic_flush_timeout); 146 | (6, read_num_broadcast_retransmissions); 147 | (7, write_num_broadcast_retransmissions); 148 | } 149 | 10 => { 150 | (0, read_hold_mode_activity); 151 | (1, write_hold_mode_activity); 152 | (2, read_transmit_power_level); 153 | (3, read_synchronous_flow_control_enable); 154 | (4, write_synchronous_flow_control_enable); 155 | (5, set_controller_to_host_flow_control); 156 | (6, host_buffer_size); 157 | (7, host_number_of_completed_packets); 158 | } 159 | 11 => { 160 | (0, read_link_supervision_timeout); 161 | (1, write_link_supervision_timeout); 162 | (2, read_number_of_supported_iac); 163 | (3, read_current_iac_lap); 164 | (4, write_current_iac_lap); 165 | } 166 | 12 => { 167 | (1, set_afh_host_channel_classification); 168 | (4, read_inquiry_scan_kind); 169 | (5, write_inquiry_scan_kind); 170 | (6, read_inquiry_mode); 171 | (7, write_inquiry_mode); 172 | } 173 | 13 => { 174 | (0, read_page_scan_kind); 175 | (1, write_page_scan_kind); 176 | (2, read_afh_channel_assessment_mode); 177 | (3, write_afh_channel_assessment_mode); 178 | } 179 | 14 => { 180 | (3, read_local_version_information); 181 | (5, read_local_supported_features); 182 | (6, read_local_ext_features); 183 | (7, read_buffer_size); 184 | } 185 | 15 => { 186 | (1, read_bd_addr); 187 | (2, read_failed_contact_counter); 188 | (3, reset_failed_contact_counter); 189 | (4, read_link_quality); 190 | (5, read_rssi); 191 | (6, read_afh_channel_map); 192 | (7, read_clock); 193 | } 194 | 16 => { 195 | (0, read_loopback_mode); 196 | (1, write_loopback_mode); 197 | (2, enable_device_under_test_mode); 198 | (3, setup_synchronous_conn_request); 199 | (4, accept_synchronous_conn_request); 200 | (5, reject_synchronous_conn_request); 201 | } 202 | 17 => { 203 | (0, read_ext_inquiry_response); 204 | (1, write_ext_inquiry_response); 205 | (2, refresh_encryption_key); 206 | (4, sniff_subrating); 207 | (5, read_simple_pairing_mode); 208 | (6, write_simple_pairing_mode); 209 | (7, read_local_oob_data); 210 | } 211 | 18 => { 212 | (0, read_inquiry_response_transmit_power_level); 213 | (1, write_inquiry_transmit_power_level); 214 | (2, read_default_erroneous_data_reporting); 215 | (3, write_default_erroneous_data_reporting); 216 | (7, io_capability_request_reply); 217 | } 218 | 19 => { 219 | (0, user_confirmation_request_reply); 220 | (1, user_confirmation_request_negative_reply); 221 | (2, user_passkey_request_reply); 222 | (3, user_passkey_request_negative_reply); 223 | (4, remote_oob_data_request_reply); 224 | (5, write_simple_pairing_debug_mode); 225 | (6, enhanced_flush); 226 | (7, remote_oob_data_request_negative_reply); 227 | } 228 | 20 => { 229 | (2, send_keypress_notification); 230 | (3, io_capability_request_negative_reply); 231 | (4, read_encryption_key_size); 232 | } 233 | 22 => { 234 | (2, set_event_mask_page_2); 235 | } 236 | 23 => { 237 | (0, read_flow_control_mode); 238 | (1, write_flow_control_mode); 239 | (2, read_data_block_size); 240 | } 241 | 24 => { 242 | (0, read_enhanced_transmit_power_level); 243 | (5, read_le_host_support); 244 | (6, write_le_host_support); 245 | } 246 | 25 => { 247 | (0, le_set_event_mask); 248 | (1, le_read_buffer_size_v1); 249 | (2, le_read_local_supported_features); 250 | (4, le_set_random_addr); 251 | (5, le_set_adv_parameters); 252 | (6, le_read_adv_physical_channel_tx_power); 253 | (7, le_set_adv_data); 254 | } 255 | 26 => { 256 | (0, le_set_scan_response_data); 257 | (1, le_set_adv_enable); 258 | (2, le_set_scan_parameters); 259 | (3, le_set_scan_enable); 260 | (4, le_create_conn); 261 | (5, le_create_conn_cancel); 262 | (6, le_read_filter_accept_list_size); 263 | (7, le_clear_filter_accept_list); 264 | } 265 | 27 => { 266 | (0, le_add_device_to_filter_accept_list); 267 | (1, le_remove_device_from_filter_accept_list); 268 | (2, le_conn_update); 269 | (3, le_set_host_channel_classification); 270 | (4, le_read_channel_map); 271 | (5, le_read_remote_features); 272 | (6, le_encrypt); 273 | (7, le_rand); 274 | } 275 | 28 => { 276 | (0, le_enable_encryption); 277 | (1, le_long_term_key_request_reply); 278 | (2, le_long_term_key_request_negative_reply); 279 | (3, le_read_supported_states); 280 | (4, le_receiver_test_v1); 281 | (5, le_transmitter_test_v1); 282 | (6, le_test_end); 283 | } 284 | 29 => { 285 | (3, enhanced_setup_synchronous_conn); 286 | (4, enhanced_accept_synchronous_conn); 287 | (5, read_local_supported_codecs); 288 | (6, set_mws_channel_parameters); 289 | (7, set_external_frame_configuration); 290 | } 291 | 30 => { 292 | (0, set_mws_signaling); 293 | (1, set_mws_transport_layer); 294 | (2, set_mws_scan_frequency_table); 295 | (3, get_mws_transport_layer_configuration); 296 | (4, set_mws_pattern_configuration); 297 | (5, set_triggered_clock_capture); 298 | (6, truncated_page); 299 | (7, truncated_page_cancel); 300 | } 301 | 31 => { 302 | (0, set_connectionless_peripheral_broadcast); 303 | (1, set_connectionless_peripheral_broadcast_receive); 304 | (2, start_synchronization_train); 305 | (3, receive_synchronization_train); 306 | (4, set_reserved_lt_addr); 307 | (5, delete_reserved_lt_addr); 308 | (6, set_connectionless_peripheral_broadcast_data); 309 | (7, read_synchronization_train_parameters); 310 | } 311 | 32 => { 312 | (0, write_synchronization_train_parameters); 313 | (1, remote_oob_ext_data_request_reply); 314 | (2, read_secure_conns_host_support); 315 | (3, write_secure_conns_host_support); 316 | (4, read_authenticated_payload_timeout); 317 | (5, write_authenticated_payload_timeout); 318 | (6, read_local_oob_ext_data); 319 | (7, write_secure_conns_test_mode); 320 | } 321 | 33 => { 322 | (0, read_ext_page_timeout); 323 | (1, write_ext_page_timeout); 324 | (2, read_ext_inquiry_length); 325 | (3, write_ext_inquiry_length); 326 | (4, le_remote_conn_parameter_request_reply); 327 | (5, le_remote_conn_parameter_request_negative_reply); 328 | (6, le_set_data_length); 329 | (7, le_read_suggested_default_data_length); 330 | } 331 | 34 => { 332 | (0, le_write_suggested_default_data_length); 333 | (1, le_read_local_p256_public_key); 334 | (2, le_generate_dhkey_v1); 335 | (3, le_add_device_to_resolving_list); 336 | (4, le_remove_device_from_resolving_list); 337 | (5, le_clear_resolving_list); 338 | (6, le_read_resolving_list_size); 339 | (7, le_read_peer_resolvable_addr); 340 | } 341 | 35 => { 342 | (0, le_read_local_resolvable_addr); 343 | (1, le_set_addr_resolution_enable); 344 | (2, le_set_resolvable_private_addr_timeout); 345 | (3, le_read_maximum_data_length); 346 | (4, le_read_phy); 347 | (5, le_set_default_phy); 348 | (6, le_set_phy); 349 | (7, le_receiver_test_v2); 350 | } 351 | 36 => { 352 | (0, le_transmitter_test_v2); 353 | (1, le_set_adv_set_random_addr); 354 | (2, le_set_ext_adv_parameters); 355 | (3, le_set_ext_adv_data); 356 | (4, le_set_ext_scan_response_data); 357 | (5, le_set_ext_adv_enable); 358 | (6, le_read_maximum_adv_data_length); 359 | (7, le_read_number_of_supported_adv_sets); 360 | } 361 | 37 => { 362 | (0, le_remove_adv_set); 363 | (1, le_clear_adv_sets); 364 | (2, le_set_periodic_adv_parameters); 365 | (3, le_set_periodic_adv_data); 366 | (4, le_set_periodic_adv_enable); 367 | (5, le_set_ext_scan_parameters); 368 | (6, le_set_ext_scan_enable); 369 | (7, le_ext_create_conn); 370 | } 371 | 38 => { 372 | (0, le_periodic_adv_create_sync); 373 | (1, le_periodic_adv_create_sync_cancel); 374 | (2, le_periodic_adv_terminate_sync); 375 | (3, le_add_device_to_periodic_adv_list); 376 | (4, le_remove_device_from_periodic_adv_list); 377 | (5, le_clear_periodic_adv_list); 378 | (6, le_read_periodic_adv_list_size); 379 | (7, le_read_transmit_power); 380 | } 381 | 39 => { 382 | (0, le_read_rf_path_compensation); 383 | (1, le_write_rf_path_compensation); 384 | (2, le_set_privacy_mode); 385 | (3, le_receiver_test_v3); 386 | (4, le_transmitter_test_v3); 387 | (5, le_set_connectionless_cte_transmit_parameters); 388 | (6, le_set_connectionless_cte_transmit_enable); 389 | (7, le_set_connectionless_iq_sampling_enable); 390 | } 391 | 40 => { 392 | (0, le_set_conn_cte_receive_parameters); 393 | (1, le_set_conn_cte_transmit_parameters); 394 | (2, le_conn_cte_request_enable); 395 | (3, le_conn_cte_response_enable); 396 | (4, le_read_antenna_information); 397 | (5, le_set_periodic_adv_receive_enable); 398 | (6, le_periodic_adv_sync_transfer); 399 | (7, le_periodic_adv_set_info_transfer); 400 | } 401 | 41 => { 402 | (0, le_set_periodic_adv_sync_transfer_parameters); 403 | (1, le_set_default_periodic_adv_sync_transfer_parameters); 404 | (2, le_generate_dhkey_v2); 405 | (3, read_local_simple_pairing_options); 406 | (4, le_modify_sleep_clock_accuracy); 407 | (5, le_read_buffer_size_v2); 408 | (6, le_read_iso_tx_sync); 409 | (7, le_set_cig_parameters); 410 | } 411 | 42 => { 412 | (0, le_set_cig_parameters_test); 413 | (1, le_create_cis); 414 | (2, le_remove_cig); 415 | (3, le_accept_cis_request); 416 | (4, le_reject_cis_request); 417 | (5, le_create_big); 418 | (6, le_create_big_test); 419 | (7, le_terminate_big); 420 | } 421 | 43 => { 422 | (0, le_big_create_sync); 423 | (1, le_big_terminate_sync); 424 | (2, le_request_peer_sca); 425 | (3, le_setup_iso_data_path); 426 | (4, le_remove_iso_data_path); 427 | (5, le_iso_transmit_test); 428 | (6, le_iso_receive_test); 429 | (7, le_iso_read_test_counters); 430 | } 431 | 44 => { 432 | (0, le_iso_test_end); 433 | (1, le_set_host_feature); 434 | (2, le_read_iso_link_quality); 435 | (3, le_enhanced_read_transmit_power_level); 436 | (4, le_read_remote_transmit_power_level); 437 | (5, le_set_path_loss_reporting_parameters); 438 | (6, le_set_path_loss_reporting_enable); 439 | (7, le_set_transmit_power_reporting_enable); 440 | } 441 | 45 => { 442 | (0, le_transmitter_test_v4); 443 | (1, set_ecosystem_base_interval); 444 | (2, read_local_supported_codecs_v2); 445 | (3, read_local_supported_codec_capabilities); 446 | (4, read_local_supported_controller_delay); 447 | (5, configure_data_path); 448 | (6, le_set_data_related_addr_changes); 449 | (7, set_min_encryption_key_size); 450 | } 451 | 46 => { 452 | (0, le_set_default_subrate); 453 | (1, le_subrate_request); 454 | (2, le_set_ext_adv_parameters_v2); 455 | (5, le_set_periodic_adv_subevent_data); 456 | (6, le_set_periodic_adv_response_data); 457 | (7, le_set_periodic_sync_subevent); 458 | } 459 | 47 => { 460 | (0, le_ext_create_conn_v2); 461 | (1, le_set_periodic_adv_parameters_v2); 462 | } 463 | } 464 | -------------------------------------------------------------------------------- /src/param/event_masks.rs: -------------------------------------------------------------------------------- 1 | use super::param; 2 | 3 | param! { 4 | bitfield EventMask[8] { 5 | (0, is_inquiry_complete_enabled, enable_inquiry_complete); 6 | (1, is_inquiry_result_enabled, enable_inquiry_result); 7 | (2, is_conn_complete_enabled, enable_conn_complete); 8 | (3, is_conn_request_enabled, enable_conn_request); 9 | (4, is_disconnection_complete_enabled, enable_disconnection_complete); 10 | (5, is_authentication_complete_enabled, enable_authentication_complete); 11 | (6, is_remote_name_request_complete_enabled, enable_remote_name_request_complete); 12 | (7, is_encryption_change_v1_enabled, enable_encryption_change_v1); 13 | (8, is_change_conn_link_key_complete_enabled, enable_change_conn_link_key_complete); 14 | (9, is_link_key_kind_changed_enabled, enable_link_key_kind_changed); 15 | (10, supports_read_remote_features_complete_enabled, enable_read_remote_supported_features_complete); 16 | (11, is_read_remote_version_information_complete_enabled, enable_read_remote_version_information_complete); 17 | (12, is_qos_setup_complete_enabled, enable_qos_setup_complete); 18 | (15, is_hardware_error_enabled, enable_hardware_error); 19 | (16, is_flush_occurred_enabled, enable_flush_occurred); 20 | (17, is_role_change_enabled, enable_role_change); 21 | (19, is_mode_change_enabled, enable_mode_change); 22 | (20, is_return_link_keys_enabled, enable_return_link_keys); 23 | (21, is_pin_code_request_enabled, enable_pin_code_request); 24 | (22, is_link_key_request_enabled, enable_link_key_request); 25 | (23, is_link_key_notification_enabled, enable_link_key_notification); 26 | (24, is_loopback_cmd_enabled, enable_loopback_cmd); 27 | (25, is_data_buffer_overflow_enabled, enable_data_buffer_overflow); 28 | (26, is_max_slots_change_enabled, enable_max_slots_change); 29 | (27, is_read_clock_offset_complete_enabled, enable_read_clock_offset_complete); 30 | (28, is_conn_packet_kind_changed_enabled, enable_conn_packet_kind_changed); 31 | (29, is_qos_violation_enabled, enable_qos_violation); 32 | (31, is_page_scan_repetition_mode_change_enabled, enable_page_scan_repetition_mode_change); 33 | (32, is_flow_specification_complete_enabled, enable_flow_specification_complete); 34 | (33, is_inquiry_result_with_rssi_enabled, enable_inquiry_result_with_rssi); 35 | (34, is_read_remote_ext_features_complete_enabled, enable_read_remote_ext_features_complete); 36 | (43, is_synchronous_conn_complete_enabled, enable_synchronous_conn_complete); 37 | (44, is_synchronous_conn_changed_enabled, enable_synchronous_conn_changed); 38 | (45, is_sniff_subrating_enabled, enable_sniff_subrating); 39 | (46, is_ext_inquiry_result_enabled, enable_ext_inquiry_result); 40 | (47, is_encryption_key_refresh_complete_enabled, enable_encryption_key_refresh_complete); 41 | (48, is_io_capability_request_enabled, enable_io_capability_request); 42 | (49, is_io_capability_response_enabled, enable_io_capability_response); 43 | (50, is_user_confirmation_request_enabled, enable_user_confirmation_request); 44 | (51, is_user_passkey_request_enabled, enable_user_passkey_request); 45 | (52, is_remote_oob_data_request_enabled, enable_remote_oob_data_request); 46 | (53, is_simple_pairing_complete_enabled, enable_simple_pairing_complete); 47 | (55, is_link_supervision_timeout_changed_enabled, enable_link_supervision_timeout_changed); 48 | (56, is_enhanced_flush_complete_enabled, enable_enhanced_flush_complete); 49 | (58, is_user_passkey_notification_enabled, enable_user_passkey_notification); 50 | (59, is_keypress_notification_enabled, enable_keypress_notification); 51 | (60, supports_remote_host_features_notification_enabled, enable_remote_host_supported_features_notification); 52 | (61, is_le_meta_enabled, enable_le_meta); 53 | } 54 | } 55 | 56 | param! { 57 | bitfield EventMaskPage2[8] { 58 | (8, is_number_of_completed_data_blocks_enabled, enable_number_of_completed_data_blocks); 59 | (14, is_triggered_clock_capture_enabled, enable_triggered_clock_capture); 60 | (15, is_synchronization_train_complete_enabled, enable_synchronization_train_complete); 61 | (16, is_synchronization_train_received_enabled, enable_synchronization_train_received); 62 | (17, is_connectionless_peripheral_broadcast_receive_enabled, enable_connectionless_peripheral_broadcast_receive); 63 | (18, is_connectionless_peripheral_broadcast_timeout_enabled, enable_connectionless_peripheral_broadcast_timeout); 64 | (19, is_truncated_page_complete_enabled, enable_truncated_page_complete); 65 | (20, is_peripheral_page_response_timeout_enabled, enable_peripheral_page_response_timeout); 66 | (21, is_connectionless_peripheral_broadcast_channel_map_change_enabled, enable_connectionless_peripheral_broadcast_channel_map_change); 67 | (22, is_inquiry_response_notification_enabled, enable_inquiry_response_notification); 68 | (23, is_authenticated_payload_timeout_expired_enabled, enable_authenticated_payload_timeout_expired); 69 | (24, is_sam_status_change_enabled, enable_sam_status_change); 70 | (25, is_encryption_change_v2_enabled, enable_encryption_change_v2); 71 | } 72 | } 73 | 74 | param! { 75 | bitfield LeEventMask[8] { 76 | (0, is_le_conn_complete_enabled, enable_le_conn_complete); 77 | (1, is_le_adv_report_enabled, enable_le_adv_report); 78 | (2, is_le_conn_update_complete_enabled, enable_le_conn_update_complete); 79 | (3, is_le_read_remote_features_complete_enabled, enable_le_read_remote_features_complete); 80 | (4, is_le_long_term_key_request_enabled, enable_le_long_term_key_request); 81 | (5, is_le_remote_conn_parameter_request_enabled, enable_le_remote_conn_parameter_request); 82 | (6, is_le_data_length_change_enabled, enable_le_data_length_change); 83 | (7, is_le_read_local_p256_public_key_complete_enabled, enable_le_read_local_p256_public_key_complete); 84 | (8, is_le_generate_dhkey_complete_enabled, enable_le_generate_dhkey_complete); 85 | (9, is_le_enhanced_conn_complete_enabled, enable_le_enhanced_conn_complete); 86 | (10, is_le_directed_adv_report_enabled, enable_le_directed_adv_report); 87 | (11, is_le_phy_update_complete_enabled, enable_le_phy_update_complete); 88 | (12, is_le_ext_adv_report_enabled, enable_le_ext_adv_report); 89 | (13, is_le_periodic_adv_sync_established_enabled, enable_le_periodic_adv_sync_established); 90 | (14, is_le_periodic_adv_report_enabled, enable_le_periodic_adv_report); 91 | (15, is_le_periodic_adv_sync_lost_enabled, enable_le_periodic_adv_sync_lost); 92 | (16, is_le_scan_timeout_enabled, enable_le_scan_timeout); 93 | (17, is_le_adv_set_terminated_enabled, enable_le_adv_set_terminated); 94 | (18, is_le_scan_request_received_enabled, enable_le_scan_request_received); 95 | (19, is_le_channel_selection_algorithm_enabled, enable_le_channel_selection_algorithm); 96 | (20, is_le_connectionless_iq_report_enabled, enable_le_connectionless_iq_report); 97 | (21, is_le_conn_iq_report_enabled, enable_le_conn_iq_report); 98 | (22, is_le_cte_request_failed_enabled, enable_le_cte_request_failed); 99 | (23, is_le_periodic_adv_sync_transfer_received_enabled, enable_le_periodic_adv_sync_transfer_received); 100 | (24, is_le_cis_established_enabled, enable_le_cis_established); 101 | (25, is_le_cis_request_enabled, enable_le_cis_request); 102 | (26, is_le_create_big_complete_enabled, enable_le_create_big_complete); 103 | (27, is_le_terminate_big_complete_enabled, enable_le_terminate_big_complete); 104 | (28, is_le_big_sync_established_enabled, enable_le_big_sync_established); 105 | (29, is_le_big_sync_lost_enabled, enable_le_big_sync_lost); 106 | (30, is_le_request_peer_sca_complete_enabled, enable_le_request_peer_sca_complete); 107 | (31, is_le_path_loss_threshold_enabled, enable_le_path_loss_threshold); 108 | (32, is_le_transmit_power_reporting_enabled, enable_le_transmit_power_reporting); 109 | (33, is_le_biginfo_adv_report_enabled, enable_le_biginfo_adv_report); 110 | (34, is_le_subrate_change_enabled, enable_le_subrate_change); 111 | (35, is_le_periodic_adv_sync_established_v2_enabled, enable_le_periodic_adv_sync_established_v2); 112 | (36, is_le_periodic_adv_report_v2_enabled, enable_le_periodic_adv_report_v2); 113 | (37, is_le_periodic_adv_sync_transfer_received_v2_enabled, enable_le_periodic_adv_sync_transfer_received_v2); 114 | (38, is_le_periodic_adv_subevent_data_request_enabled, enable_le_periodic_adv_subevent_data_request); 115 | (39, is_le_periodic_adv_response_report_enabled, enable_le_periodic_adv_response_report); 116 | (40, is_le_enhanced_conn_complete_v2_enabled, enable_le_enhanced_conn_complete_v2); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/param/feature_masks.rs: -------------------------------------------------------------------------------- 1 | use super::param; 2 | 3 | param! { 4 | bitfield LmpFeatureMask[8] { 5 | (0, supports_3_slot_packets, set_3_slot_packets); 6 | (1, supports_5_slot_packets, set_5_slot_packets); 7 | (2, supports_encryption, set_encryption); 8 | (3, supports_slot_offset, set_slot_offset); 9 | (4, supports_timing_accuracy, set_timing_accuracy); 10 | (5, supports_role_switch, set_role_switch); 11 | (6, supports_hold_mode, set_hold_mode); 12 | (7, supports_sniff_mode, set_sniff_mode); 13 | (9, supports_power_control_requests, set_power_control_requests); 14 | (10, supports_cqddr, set_cqddr); 15 | (11, supports_sco_link, set_sco_link); 16 | (12, supports_hv2_packets, set_hv2_packets); 17 | (13, supports_hv3_packets, set_hv3_packets); 18 | (14, supports_mu_law_log_synchronous_data, set_mu_law_log_synchronous_data); 19 | (15, supports_a_law_log_synchronous_data, set_a_law_log_synchronous_data); 20 | (16, supports_cvsd_synchronous_data, set_cvsd_synchronous_data); 21 | (17, supports_paging_parameter_negotiation, set_paging_parameter_negotiation); 22 | (18, supports_power_control, set_power_control); 23 | (19, supports_transparent_synchronous_data, set_transparent_synchronous_data); 24 | (20, supports_flow_control_lag_lsb, set_flow_control_lag_lsb); 25 | (21, supports_flow_control_lag_middle_bit, set_flow_control_lag_middle_bit); 26 | (22, supports_flow_control_lag_msb, set_flow_control_lag_msb); 27 | (23, supports_broadcast_encryption, set_broadcast_encryption); 28 | (25, supports_enhanced_data_rate_acl_2mbps_mode, set_enhanced_data_rate_acl_2mbps_mode); 29 | (26, supports_enhanced_data_rate_acl_3mbps_mode, set_enhanced_data_rate_acl_3mbps_mode); 30 | (27, supports_enhanced_inquiry_scan, set_enhanced_inquiry_scan); 31 | (28, supports_interlaced_inquiry_scan, set_interlaced_inquiry_scan); 32 | (29, supports_interlaced_page_scan, set_interlaced_page_scan); 33 | (30, supports_rssi_with_inquiry_results, set_rssi_with_inquiry_results); 34 | (31, supports_ext_sco_link, set_ext_sco_link); 35 | (32, supports_ev4_packets, set_ev4_packets); 36 | (33, supports_ev5_packets, set_ev5_packets); 37 | (35, supports_afh_capable_peripheral, set_afh_capable_peripheral); 38 | (36, supports_afh_classification_peripheral, set_afh_classification_peripheral); 39 | (37, supports_br_edr_not, set_br_edr_not); 40 | (38, supports_le, set_le); 41 | (39, supports_3_slot_enhanced_data_rate_acl_packets, set_3_slot_enhanced_data_rate_acl_packets); 42 | (40, supports_5_slot_enhanced_data_rate_acl_packets, set_5_slot_enhanced_data_rate_acl_packets); 43 | (41, supports_sniff_subrating, set_sniff_subrating); 44 | (42, supports_pause_encryption, set_pause_encryption); 45 | (43, supports_afh_capable_central, set_afh_capable_central); 46 | (44, supports_afh_classification_central, set_afh_classification_central); 47 | (45, supports_enhanced_data_rate_esco_2mbps_mode, set_enhanced_data_rate_esco_2mbps_mode); 48 | (46, supports_enhanced_data_rate_esco_3mbps_mode, set_enhanced_data_rate_esco_3mbps_mode); 49 | (47, supports_3_slot_enhanced_data_rate_esco_packets, set_3_slot_enhanced_data_rate_esco_packets); 50 | (48, supports_ext_inquiry_response, set_ext_inquiry_response); 51 | (49, supports_simultaneous_le_and_br_edr_to_same_devi, set_simultaneous_le_and_br_edr_to_same_devi); 52 | (51, supports_secure_simple_pairing, set_secure_simple_pairing); 53 | (52, supports_encapsulated_pdu, set_encapsulated_pdu); 54 | (53, supports_erroneous_data_reporting, set_erroneous_data_reporting); 55 | (54, supports_non_flushable_packet_boundary_flag, set_non_flushable_packet_boundary_flag); 56 | (56, supports_hci_link_supervision_timeout_changed_event, set_hci_link_supervision_timeout_changed_event); 57 | (57, supports_variable_inquiry_tx_power_level, set_variable_inquiry_tx_power_level); 58 | (58, supports_enhanced_power_control, set_enhanced_power_control); 59 | (63, supports_ext_features, set_ext_features); 60 | } 61 | } 62 | 63 | param! { 64 | bitfield LeFeatureMask[8] { 65 | (0, supports_le_encryption, set_le_encryption); 66 | (1, supports_conn_parameters_request_procedure, set_conn_parameters_request_procedure); 67 | (2, supports_ext_reject_indication, set_ext_reject_indication); 68 | (3, supports_peripheral_initiated_features_exchange, set_peripheral_initiated_features_exchange); 69 | (4, supports_le_ping, set_le_ping); 70 | (5, supports_le_data_packet_length_extension, set_le_data_packet_length_extension); 71 | (6, supports_ll_privacy, set_ll_privacy); 72 | (7, supports_ext_scanner_filter_policies, set_ext_scanner_filter_policies); 73 | (8, supports_le_2m_phy, set_le_2m_phy); 74 | (9, supports_stable_modulation_index_tx, set_stable_modulation_index_tx); 75 | (10, supports_stable_modulation_index_rx, set_stable_modulation_index_rx); 76 | (11, supports_le_coded_phy, set_le_coded_phy); 77 | (12, supports_le_ext_adv, set_le_ext_adv); 78 | (13, supports_le_periodic_adv, set_le_periodic_adv); 79 | (14, supports_channel_selection_algorithm_2, set_channel_selection_algorithm_2); 80 | (15, supports_le_power_class_1, set_le_power_class_1); 81 | (16, supports_min_used_channels_procedure, set_min_used_channels_procedure); 82 | (17, supports_conn_cte_request, set_conn_cte_request); 83 | (18, supports_conn_cte_response, set_conn_cte_response); 84 | (19, supports_connectionless_cte_tx, set_connectionless_cte_tx); 85 | (20, supports_connectionless_cte_rx, set_connectionless_cte_rx); 86 | (21, supports_antenna_switching_during_cte_tx, set_antenna_switching_during_cte_tx); 87 | (22, supports_antenna_switching_during_cte_rx, set_antenna_switching_during_cte_rx); 88 | (23, supports_receiving_constant_tone_extensions, set_receiving_constant_tone_extensions); 89 | (24, supports_periodic_adv_sync_transfer_sender, set_periodic_adv_sync_transfer_sender); 90 | (25, supports_periodic_adv_sync_transfer_recipient, set_periodic_adv_sync_transfer_recipient); 91 | (26, supports_sleep_clock_accuracy_updates, set_sleep_clock_accuracy_updates); 92 | (27, supports_remote_public_key_validation, set_remote_public_key_validation); 93 | (28, supports_connected_isochronous_stream_central, set_connected_isochronous_stream_central); 94 | (29, supports_connected_isochronous_stream_peripheral, set_connected_isochronous_stream_peripheral); 95 | (30, supports_isochronous_broadcaster, set_isochronous_broadcaster); 96 | (31, supports_synchronized_receiver, set_synchronized_receiver); 97 | (32, supports_connected_isochronous_stream, set_connected_isochronous_stream); 98 | (33, supports_le_power_control_request, set_le_power_control_request); 99 | (35, supports_le_path_loss_monitoring, set_le_path_loss_monitoring); 100 | (36, supports_periodic_adv_adi, set_periodic_adv_adi); 101 | (37, supports_conn_subrating, set_conn_subrating); 102 | (38, supports_conn_subrating_host, set_conn_subrating_host); 103 | (39, supports_channel_classification, set_channel_classification); 104 | (40, supports_adv_coding_selection, set_adv_coding_selection); 105 | (41, supports_adv_coding_selection_host_support, set_adv_coding_selection_host_support); 106 | (43, supports_periodic_adv_with_resp_advertiser, set_periodic_adv_with_resp_advertiser); 107 | (44, supports_periodic_adv_with_resp_scanner, set_periodic_adv_with_resp_scanner); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/param/macros.rs: -------------------------------------------------------------------------------- 1 | macro_rules! impl_param_int { 2 | ($($ty:ty),+) => { 3 | $( 4 | #[automatically_derived] 5 | unsafe impl $crate::FixedSizeValue for $ty { 6 | #[inline(always)] 7 | fn is_valid(data: &[u8]) -> bool { 8 | true 9 | } 10 | } 11 | )+ 12 | }; 13 | } 14 | 15 | const _IS_LITTLE_ENDIAN: [u8; 0] = [0; (u32::from_le_bytes(0x01020304u32.to_ne_bytes()) != 0x01020304u32) as usize]; 16 | 17 | impl_param_int!(u8, i8, u16, i16, u32, u64, u128); 18 | 19 | unsafe impl crate::ByteAlignedValue for u8 {} 20 | unsafe impl crate::ByteAlignedValue for i8 {} 21 | 22 | impl<'de> crate::FromHciBytes<'de> for &'de u8 { 23 | #[inline(always)] 24 | fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), crate::FromHciBytesError> { 25 | ::ref_from_hci_bytes(data) 26 | } 27 | } 28 | 29 | impl<'de> crate::FromHciBytes<'de> for &'de i8 { 30 | #[inline(always)] 31 | fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), crate::FromHciBytesError> { 32 | ::ref_from_hci_bytes(data) 33 | } 34 | } 35 | 36 | #[doc(hidden)] 37 | #[macro_export] 38 | macro_rules! param { 39 | ( 40 | $(#[$attrs:meta])* 41 | struct $name:ident($wrapped:ty) 42 | ) => { 43 | $(#[$attrs])* 44 | #[repr(transparent)] 45 | #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 46 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 47 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] 48 | /// $name 49 | pub struct $name($wrapped); 50 | 51 | impl $name { 52 | /// Get inner representation 53 | pub fn into_inner(self) -> $wrapped { 54 | self.0 55 | } 56 | } 57 | 58 | unsafe impl $crate::FixedSizeValue for $name { 59 | #[inline(always)] 60 | fn is_valid(data: &[u8]) -> bool { 61 | <$wrapped as $crate::FixedSizeValue>::is_valid(data) 62 | } 63 | } 64 | }; 65 | 66 | ( 67 | $(#[$attrs:meta])* 68 | struct $name:ident { 69 | $($field:ident: $ty:ty),* 70 | $(,)? 71 | } 72 | ) => { 73 | $(#[$attrs])* 74 | #[repr(C, packed)] 75 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 76 | /// $name parameter 77 | #[allow(missing_docs)] 78 | pub struct $name { 79 | $(pub $field: $ty,)* 80 | } 81 | 82 | #[cfg(feature = "defmt")] 83 | impl defmt::Format for $name { 84 | fn format(&self, f: defmt::Formatter) { 85 | // Copy out the field values since we can't take references to packed fields 86 | let Self { $($field),* } = *self; 87 | 88 | defmt::write!(f, "{} {{ ", stringify!($name)); 89 | $(defmt::write!(f, "{}: {}, ", stringify!($field), $field);)* 90 | defmt::write!(f, "}}"); 91 | } 92 | } 93 | 94 | #[automatically_derived] 95 | unsafe impl $crate::FixedSizeValue for $name { 96 | #[inline(always)] 97 | fn is_valid(data: &[u8]) -> bool { 98 | true 99 | $( 100 | && <$ty as $crate::FixedSizeValue>::is_valid(&data[core::mem::offset_of!(Self, $field)..]) 101 | )* 102 | } 103 | } 104 | 105 | unsafe impl $crate::ByteAlignedValue for $name {} 106 | 107 | impl<'de> $crate::FromHciBytes<'de> for &'de $name { 108 | #[inline(always)] 109 | fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), $crate::FromHciBytesError> { 110 | <$name as $crate::ByteAlignedValue>::ref_from_hci_bytes(data) 111 | } 112 | } 113 | }; 114 | 115 | ( 116 | $(#[$attrs:meta])* 117 | struct $name:ident$(<$life:lifetime>)? { 118 | $($field:ident: $ty:ty),* 119 | $(,)? 120 | } 121 | ) => { 122 | $(#[$attrs])* 123 | #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 124 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 125 | /// $name parameter 126 | #[allow(missing_docs)] 127 | pub struct $name$(<$life>)? { 128 | $(pub $field: $ty,)* 129 | } 130 | 131 | impl$(<$life>)? $crate::WriteHci for $name$(<$life>)? { 132 | #[inline(always)] 133 | fn size(&self) -> usize { 134 | $(<$ty as $crate::WriteHci>::size(&self.$field) +)* 0 135 | } 136 | 137 | #[inline(always)] 138 | fn write_hci(&self, mut writer: W) -> Result<(), W::Error> { 139 | $(<$ty as $crate::WriteHci>::write_hci(&self.$field, &mut writer)?;)* 140 | Ok(()) 141 | } 142 | 143 | #[inline(always)] 144 | async fn write_hci_async(&self, mut writer: W) -> Result<(), W::Error> { 145 | $(<$ty as $crate::WriteHci>::write_hci_async(&self.$field, &mut writer).await?;)* 146 | Ok(()) 147 | } 148 | } 149 | 150 | impl<$($life, )?'de> $crate::FromHciBytes<'de> for $name$(<$life> where 'de: $life, $life: 'de)? { 151 | #[allow(unused_variables)] 152 | fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), $crate::FromHciBytesError> { 153 | let total = 0; 154 | $( 155 | let ($field, data) = <$ty as $crate::FromHciBytes>::from_hci_bytes(data)?; 156 | )* 157 | Ok((Self { 158 | $($field,)* 159 | }, data)) 160 | } 161 | } 162 | }; 163 | 164 | ( 165 | $(#[$attrs:meta])* 166 | enum $name:ident { 167 | $( 168 | $(#[$variant_attrs:meta])* 169 | $variant:ident = $value:expr, 170 | )+ 171 | } 172 | ) => { 173 | $(#[$attrs])* 174 | #[repr(u8)] 175 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 176 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 177 | #[allow(missing_docs)] 178 | /// $name. 179 | pub enum $name { 180 | $( 181 | $(#[$variant_attrs])* 182 | $variant = $value, 183 | )+ 184 | } 185 | 186 | unsafe impl $crate::FixedSizeValue for $name { 187 | #[inline(always)] 188 | fn is_valid(data: &[u8]) -> bool { 189 | $(data[0] == $value ||)* false 190 | } 191 | } 192 | 193 | unsafe impl $crate::ByteAlignedValue for $name {} 194 | 195 | impl<'de> $crate::FromHciBytes<'de> for &'de $name { 196 | #[inline(always)] 197 | fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), $crate::FromHciBytesError> { 198 | <$name as $crate::ByteAlignedValue>::ref_from_hci_bytes(data) 199 | } 200 | } 201 | }; 202 | 203 | ( 204 | $(#[$attrs:meta])* 205 | bitfield $name:ident[1] { 206 | $(($bit:expr, $get:ident, $set:ident);)+ 207 | } 208 | ) => { 209 | $(#[$attrs])* 210 | #[repr(transparent)] 211 | #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 212 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 213 | /// $name. 214 | pub struct $name(u8); 215 | 216 | impl $name { 217 | /// Create a new instance. 218 | pub fn new() -> Self { 219 | Self::default() 220 | } 221 | 222 | /// Get the inner representation. 223 | pub fn into_inner(self) -> u8 { 224 | self.0 225 | } 226 | 227 | $( 228 | #[allow(missing_docs)] 229 | pub const fn $get(&self) -> bool { 230 | (self.0 & (1 << $bit)) != 0 231 | } 232 | 233 | #[allow(missing_docs)] 234 | pub const fn $set(self, val: bool) -> Self { 235 | Self((self.0 & !(1 << $bit)) | ((val as u8) << $bit)) 236 | } 237 | )+ 238 | } 239 | 240 | unsafe impl $crate::FixedSizeValue for $name { 241 | #[inline(always)] 242 | fn is_valid(_data: &[u8]) -> bool { 243 | true 244 | } 245 | } 246 | 247 | unsafe impl $crate::ByteAlignedValue for $name {} 248 | 249 | impl<'de> $crate::FromHciBytes<'de> for &'de $name { 250 | #[inline(always)] 251 | fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), $crate::FromHciBytesError> { 252 | <$name as $crate::ByteAlignedValue>::ref_from_hci_bytes(data) 253 | } 254 | } 255 | }; 256 | ( 257 | $(#[$attrs:meta])* 258 | bitfield $name:ident[$octets:expr] { 259 | $(($bit:expr, $get:ident, $set:ident);)+ 260 | } 261 | ) => { 262 | $(#[$attrs])* 263 | #[repr(transparent)] 264 | #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 265 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 266 | /// $name 267 | pub struct $name([u8; $octets]); 268 | 269 | impl $name { 270 | /// Create a new instance. 271 | pub fn new() -> Self { 272 | Self::default() 273 | } 274 | 275 | /// Get the inner representation. 276 | pub fn into_inner(self) -> [u8; $octets] { 277 | self.0 278 | } 279 | 280 | $( 281 | #[allow(missing_docs)] 282 | pub const fn $get(&self) -> bool { 283 | const OCTET: usize = $bit / 8; 284 | const BIT: usize = $bit % 8; 285 | (self.0[OCTET] & (1 << BIT)) != 0 286 | } 287 | 288 | #[allow(missing_docs)] 289 | pub const fn $set(mut self, val: bool) -> Self { 290 | const OCTET: usize = $bit / 8; 291 | const BIT: usize = $bit % 8; 292 | self.0[OCTET] = (self.0[OCTET] & !(1 << BIT)) | ((val as u8) << BIT); 293 | self 294 | } 295 | )+ 296 | } 297 | 298 | unsafe impl $crate::FixedSizeValue for $name { 299 | #[inline(always)] 300 | fn is_valid(_data: &[u8]) -> bool { 301 | true 302 | } 303 | } 304 | 305 | unsafe impl $crate::ByteAlignedValue for $name {} 306 | 307 | impl<'de> $crate::FromHciBytes<'de> for &'de $name { 308 | #[inline(always)] 309 | fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), $crate::FromHciBytesError> { 310 | <$name as $crate::ByteAlignedValue>::ref_from_hci_bytes(data) 311 | } 312 | } 313 | }; 314 | } 315 | 316 | macro_rules! param_slice { 317 | (&$life:lifetime [$el:ty]) => { 318 | impl<$life> $crate::WriteHci for &$life [$el] { 319 | #[inline(always)] 320 | fn size(&self) -> usize { 321 | 1 + self.iter().map($crate::WriteHci::size).sum::() 322 | } 323 | 324 | #[inline(always)] 325 | fn write_hci(&self, mut writer: W) -> Result<(), W::Error> { 326 | writer.write_all(&[self.len() as u8])?; 327 | for x in self.iter() { 328 | <$el as $crate::WriteHci>::write_hci(x, &mut writer)?; 329 | } 330 | Ok(()) 331 | } 332 | 333 | #[inline(always)] 334 | async fn write_hci_async(&self, mut writer: W) -> Result<(), W::Error> { 335 | writer.write_all(&[self.len() as u8]).await?; 336 | for x in self.iter() { 337 | <$el as $crate::WriteHci>::write_hci_async(x, &mut writer).await?; 338 | } 339 | Ok(()) 340 | } 341 | } 342 | }; 343 | 344 | ( 345 | $(#[$attrs:meta])* 346 | [$name:ident; $octets:expr] { 347 | $($field:ident[$off:expr]: $ty:ty),* 348 | $(,)? 349 | } 350 | ) => { 351 | $(#[$attrs])* 352 | #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 353 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 354 | /// $name 355 | pub struct $name([u8; $octets]); 356 | 357 | impl $name { 358 | $( 359 | /// Get value of $field 360 | pub fn $field(&self) -> Result<$ty, $crate::FromHciBytesError> { 361 | <$ty as $crate::FromHciBytes>::from_hci_bytes(&self.0[$off..]).map(|(x, _)| x) 362 | } 363 | )+ 364 | } 365 | 366 | impl<'a> $crate::WriteHci for &'a [$name] { 367 | #[inline(always)] 368 | fn size(&self) -> usize { 369 | 1 + self.len() * $octets 370 | } 371 | 372 | #[inline(always)] 373 | fn write_hci(&self, mut writer: W) -> Result<(), W::Error> { 374 | writer.write_all(&[self.len() as u8])?; 375 | for x in self.iter() { 376 | <[u8; $octets] as $crate::WriteHci>::write_hci(&x.0, &mut writer)?; 377 | } 378 | Ok(()) 379 | } 380 | 381 | #[inline(always)] 382 | async fn write_hci_async(&self, mut writer: W) -> Result<(), W::Error> { 383 | writer.write_all(&[self.len() as u8]).await?; 384 | for x in self.iter() { 385 | <[u8; $octets] as $crate::WriteHci>::write_hci_async(&x.0, &mut writer).await?; 386 | } 387 | Ok(()) 388 | } 389 | } 390 | 391 | impl<'a, 'de: 'a> $crate::FromHciBytes<'de> for &'a [$name] { 392 | #[allow(unused_variables)] 393 | fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), $crate::FromHciBytesError> { 394 | match data.split_first() { 395 | Some((&len, data)) => { 396 | let len = usize::from(len); 397 | let size = $octets * len; 398 | if data.len() >= size { 399 | let (data, rest) = data.split_at(size); 400 | // Safety: $name has align of 1, no padding, and all bit patterns are valid 401 | let slice = unsafe { core::slice::from_raw_parts(data.as_ptr() as *const _, len) }; 402 | Ok((slice, rest)) 403 | } else { 404 | Err($crate::FromHciBytesError::InvalidSize) 405 | } 406 | } 407 | None => Err($crate::FromHciBytesError::InvalidSize), 408 | } 409 | } 410 | } 411 | }; 412 | } 413 | 414 | pub use param; 415 | pub(crate) use param_slice; 416 | -------------------------------------------------------------------------------- /src/param/primitives.rs: -------------------------------------------------------------------------------- 1 | use crate::{ByteAlignedValue, FixedSizeValue, FromHciBytes, FromHciBytesError, WriteHci}; 2 | 3 | unsafe impl FixedSizeValue for () { 4 | #[inline(always)] 5 | fn is_valid(_data: &[u8]) -> bool { 6 | true 7 | } 8 | } 9 | 10 | unsafe impl ByteAlignedValue for () {} 11 | 12 | impl<'de> FromHciBytes<'de> for &'static () { 13 | #[inline(always)] 14 | fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), crate::FromHciBytesError> { 15 | Ok((&(), data)) 16 | } 17 | } 18 | 19 | unsafe impl FixedSizeValue for bool { 20 | #[inline(always)] 21 | fn is_valid(data: &[u8]) -> bool { 22 | !data.is_empty() && data[0] < 2 23 | } 24 | } 25 | 26 | unsafe impl ByteAlignedValue for bool {} 27 | 28 | impl<'de> FromHciBytes<'de> for &'de bool { 29 | #[inline(always)] 30 | fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), crate::FromHciBytesError> { 31 | ::ref_from_hci_bytes(data) 32 | } 33 | } 34 | 35 | impl WriteHci for &[u8] { 36 | #[inline(always)] 37 | fn size(&self) -> usize { 38 | // Slice length is incremented to account for the prepended length byte 39 | self.len() + 1 40 | } 41 | 42 | #[inline(always)] 43 | fn write_hci(&self, mut writer: W) -> Result<(), W::Error> { 44 | writer.write_all(&[self.len() as u8])?; 45 | writer.write_all(self) 46 | } 47 | 48 | #[inline(always)] 49 | async fn write_hci_async(&self, mut writer: W) -> Result<(), W::Error> { 50 | writer.write_all(&[self.size() as u8]).await?; 51 | writer.write_all(self).await 52 | } 53 | } 54 | 55 | unsafe impl FixedSizeValue for [T; N] { 56 | #[inline(always)] 57 | fn is_valid(_data: &[u8]) -> bool { 58 | true 59 | } 60 | } 61 | 62 | unsafe impl ByteAlignedValue for [T; N] {} 63 | 64 | impl<'de, T: ByteAlignedValue, const N: usize> FromHciBytes<'de> for &'de [T; N] { 65 | #[inline(always)] 66 | fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), crate::FromHciBytesError> { 67 | <[T; N] as crate::ByteAlignedValue>::ref_from_hci_bytes(data) 68 | } 69 | } 70 | 71 | impl WriteHci for Option { 72 | #[inline(always)] 73 | fn size(&self) -> usize { 74 | self.as_ref().map(|x| x.size()).unwrap_or_default() 75 | } 76 | 77 | #[inline(always)] 78 | fn write_hci(&self, writer: W) -> Result<(), W::Error> { 79 | match self { 80 | Some(val) => val.write_hci(writer), 81 | None => Ok(()), 82 | } 83 | } 84 | 85 | #[inline(always)] 86 | async fn write_hci_async(&self, writer: W) -> Result<(), W::Error> { 87 | match self { 88 | Some(val) => val.write_hci_async(writer).await, 89 | None => Ok(()), 90 | } 91 | } 92 | } 93 | 94 | impl<'de, T: FromHciBytes<'de>> FromHciBytes<'de> for Option { 95 | fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), FromHciBytesError> { 96 | if data.is_empty() { 97 | Ok((None, data)) 98 | } else { 99 | T::from_hci_bytes(data).map(|(x, y)| (Some(x), y)) 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/param/status.rs: -------------------------------------------------------------------------------- 1 | use core::num::NonZeroU8; 2 | 3 | use crate::{ByteAlignedValue, FixedSizeValue, FromHciBytes}; 4 | 5 | /// A HCI status value. 6 | #[repr(transparent)] 7 | #[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 8 | pub struct Status(u8); 9 | 10 | impl Status { 11 | /// Get the underlying representation. 12 | pub fn into_inner(self) -> u8 { 13 | self.0 14 | } 15 | } 16 | 17 | unsafe impl FixedSizeValue for Status { 18 | #[inline(always)] 19 | fn is_valid(_data: &[u8]) -> bool { 20 | true 21 | } 22 | } 23 | 24 | unsafe impl ByteAlignedValue for Status {} 25 | 26 | impl<'de> FromHciBytes<'de> for &'de Status { 27 | #[inline(always)] 28 | fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), crate::FromHciBytesError> { 29 | ::ref_from_hci_bytes(data) 30 | } 31 | } 32 | 33 | impl Status { 34 | /// Status for successful operation. 35 | pub const SUCCESS: Status = Status(0); 36 | 37 | /// Create a new instance. 38 | pub const fn new(n: u8) -> Self { 39 | Status(n) 40 | } 41 | 42 | /// Get a result representation of status which will provide an error 43 | /// if not a success. 44 | pub const fn to_result(self) -> Result<(), Error> { 45 | if self.0 == Self::SUCCESS.0 { 46 | Ok(()) 47 | } else { 48 | Err(Error(unsafe { NonZeroU8::new_unchecked(self.0) })) 49 | } 50 | } 51 | } 52 | 53 | #[cfg(feature = "defmt")] 54 | impl defmt::Format for Status { 55 | fn format(&self, fmt: defmt::Formatter) { 56 | defmt::Format::format(&self.to_result(), fmt) 57 | } 58 | } 59 | 60 | impl core::fmt::Debug for Status { 61 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 62 | core::fmt::Debug::fmt(&self.to_result(), f) 63 | } 64 | } 65 | 66 | impl From for Status { 67 | fn from(value: u8) -> Self { 68 | Status(value) 69 | } 70 | } 71 | 72 | impl From for u8 { 73 | fn from(value: Status) -> Self { 74 | value.0 75 | } 76 | } 77 | 78 | /// An error representation for HCI errors. 79 | #[repr(transparent)] 80 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 81 | pub struct Error(NonZeroU8); 82 | 83 | impl Error { 84 | /// Create from the byte value 85 | /// 86 | /// # Safety 87 | /// Must be a valid HCI error code. 88 | const unsafe fn from_u8(err: u8) -> Error { 89 | Error(NonZeroU8::new_unchecked(err)) 90 | } 91 | 92 | /// Get the status representation. 93 | pub const fn to_status(self) -> Status { 94 | Status(self.0.get()) 95 | } 96 | } 97 | 98 | impl core::fmt::Display for Error { 99 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 100 | core::fmt::Debug::fmt(self, f) 101 | } 102 | } 103 | 104 | impl From for u8 { 105 | fn from(value: Error) -> Self { 106 | value.0.get() 107 | } 108 | } 109 | 110 | macro_rules! errnos { 111 | ( 112 | $( 113 | ($val:expr, $konst:ident, $desc:expr); 114 | )+ 115 | ) => { 116 | impl Error { 117 | $( 118 | #[doc = $desc] 119 | pub const $konst: Error = unsafe { Error::from_u8($val) }; 120 | )+ 121 | } 122 | 123 | impl Status { 124 | $( 125 | #[doc = $desc] 126 | pub const $konst: Status = Error::$konst.to_status(); 127 | )+ 128 | } 129 | 130 | #[cfg(feature = "defmt")] 131 | impl defmt::Format for Error { 132 | fn format(&self, fmt: defmt::Formatter) { 133 | match *self { 134 | $( 135 | Self::$konst => defmt::write!(fmt, $desc), 136 | )+ 137 | _ => defmt::write!(fmt, "Unknown error: {}", self.0), 138 | } 139 | } 140 | } 141 | 142 | impl core::fmt::Debug for Error { 143 | fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 144 | match *self { 145 | $( 146 | Self::$konst => core::write!(fmt, $desc), 147 | )+ 148 | _ => core::write!(fmt, "Unknown errno: {}", self.0), 149 | } 150 | } 151 | } 152 | } 153 | } 154 | 155 | errnos! { 156 | (0x01, UNKNOWN_CMD, "Unknown HCI Command"); 157 | (0x02, UNKNOWN_CONN_IDENTIFIER, "Unknown Connection Identifier"); 158 | (0x03, HARDWARE_FAILURE, "Hardware Failure"); 159 | (0x04, PAGE_TIMEOUT, "Page Timeout"); 160 | (0x05, AUTHENTICATION_FAILURE, "Authentication Failure"); 161 | (0x06, PIN_OR_KEY_MISSING, "PIN or Key Missing"); 162 | (0x07, MEMORY_CAPACITY_EXCEEDED, "Memory Capacity Exceeded"); 163 | (0x08, CONN_TIMEOUT, "Connection Timeout"); 164 | (0x09, CONN_LIMIT_EXCEEDED, "Connection Limit Exceeded"); 165 | (0x0A, SYNCHRONOUS_CONN_LIMIT_EXCEEDED, "Synchronous Connection Limit To A Device Exceeded"); 166 | (0x0B, CONN_ALREADY_EXISTS, "Connection Already Exists"); 167 | (0x0C, CMD_DISALLOWED, "Command Disallowed"); 168 | (0x0D, CONN_REJECTED_LIMITED_RESOURCES, "Connection Rejected due to Limited Resources"); 169 | (0x0E, CONN_REJECTED_SECURITY_REASONS, "Connection Rejected Due To Security Reasons"); 170 | (0x0F, CONN_REJECTED_UNACCEPTABLE_BD_ADDR, "Connection Rejected due to Unacceptable BD_ADDR"); 171 | (0x10, CONN_ACCEPT_TIMEOUT_EXCEEDED, "Connection Accept Timeout Exceeded"); 172 | (0x11, UNSUPPORTED, "Unsupported Feature or Parameter Value"); 173 | (0x12, INVALID_HCI_PARAMETERS, "Invalid HCI Command Parameters"); 174 | (0x13, REMOTE_USER_TERMINATED_CONN, "Remote User Terminated Connection"); 175 | (0x14, REMOTE_DEVICE_TERMINATED_CONN_LOW_RESOURCES, "Remote Device Terminated Connection due to Low Resources"); 176 | (0x15, REMOTE_DEVICE_TERMINATED_CONN_POWER_OFF, "Remote Device Terminated Connection due to Power Off"); 177 | (0x16, CONN_TERMINATED_BY_LOCAL_HOST, "Connection Terminated By Local Host"); 178 | (0x17, REPEATED_ATTEMPTS, "Repeated Attempts"); 179 | (0x18, PAIRING_NOT_ALLOWED, "Pairing Not Allowed"); 180 | (0x19, UNKNOWN_LMP_PDU, "Unknown LMP PDU"); 181 | (0x1A, UNSUPPORTED_REMOTE_FEATURE, "Unsupported Remote Feature"); 182 | (0x1B, SCO_OFFSET_REJECTED, "SCO Offset Rejected"); 183 | (0x1C, SCO_INTERVAL_REJECTED, "SCO Interval Rejected"); 184 | (0x1D, SCO_AIR_MODE_REJECTED, "SCO Air Mode Rejected"); 185 | (0x1E, INVALID_LMP_LL_PARAMETERS, "Invalid LMP Parameters / Invalid LL Parameters"); 186 | (0x1F, UNSPECIFIED, "Unspecified Error"); 187 | (0x20, UNSUPPORTED_LMP_LL_PARAMETER_VALUE, "Unsupported LMP Parameter Value / Unsupported LL Parameter Value"); 188 | (0x21, ROLE_CHANGE_NOT_ALLOWED, "Role Change Not Allowed"); 189 | (0x22, LMP_LL_RESPONSE_TIMEOUT, "LMP Response Timeout / LL Response Timeout"); 190 | (0x23, LMP_LL_COLLISION, "LMP Error Transaction Collision / LL Procedure Collision"); 191 | (0x24, LMP_PDU_NOT_ALLOWED, "LMP PDU Not Allowed"); 192 | (0x25, ENCRYPTION_MODE_NOT_ACCEPTABLE, "Encryption Mode Not Acceptable"); 193 | (0x26, LINK_KEY_CANNOT_BE_CHANGED, "Link Key cannot be Changed"); 194 | (0x27, REQUESTED_QOS_NOT_SUPPORTED, "Requested QoS Not Supported"); 195 | (0x28, INSTANT_PASSED, "Instant Passed"); 196 | (0x29, PAIRING_WITH_UNIT_KEY_NOT_SUPPORTED, "Pairing With Unit Key Not Supported"); 197 | (0x2A, DIFFERENT_TRANSACTION_COLLISION, "Different Transaction Collision"); 198 | (0x2C, QOS_UNACCEPTABLE_PARAMETER, "QoS Unacceptable Parameter"); 199 | (0x2D, QOS_REJECTED, "QoS Rejected"); 200 | (0x2E, CHANNEL_CLASSIFICATION_NOT_SUPPORTED, "Channel Classification Not Supported"); 201 | (0x2F, INSUFFICIENT_SECURITY, "Insufficient Security"); 202 | (0x30, PARAMETER_OUT_OF_RANGE, "Parameter Out Of Mandatory Range"); 203 | (0x32, ROLE_SWITCH_PENDING, "Role Switch Pending"); 204 | (0x34, RESERVED_SLOT_VIOLATION, "Reserved Slot Violation"); 205 | (0x35, ROLE_SWITCH_FAILED, "Role Switch Failed"); 206 | (0x36, EXT_INQUIRY_RESPONSE_TOO_LARGE, "Extended Inquiry Response Too Large"); 207 | (0x37, SECURE_SIMPLE_PAIRING_NOT_SUPPORTED_BY_HOST, "Secure Simple Pairing Not Supported By Host"); 208 | (0x38, HOST_BUSY_PAIRING, "Host Busy - Pairing"); 209 | (0x39, CONN_REJECTED_NO_SUITABLE_CHANNEL_FOUND, "Connection Rejected due to No Suitable Channel Found"); 210 | (0x3A, CONTROLLER_BUSY, "Controller Busy"); 211 | (0x3B, UNACCEPTABLE_CONN_PARAMETERS, "Unacceptable Connection Parameters"); 212 | (0x3C, ADV_TIMEOUT, "Advertising Timeout"); 213 | (0x3D, CONN_TERMINATED_DUE_TO_MIC_FAILURE, "Connection Terminated due to MIC Failure"); 214 | (0x3E, CONN_FAILED_SYNCHRONIZATION_TIMEOUT, "Connection Failed to be Established / Synchronization Timeout"); 215 | (0x40, COARSE_CLOCK_ADJUSTMENT_REJECTED, "Coarse Clock Adjustment Rejected but Will Try to Adjust Using Clock Dragging"); 216 | (0x41, TYPE0_SUBMAP_NOT_DEFINED, "Type0 Submap Not Defined"); 217 | (0x42, UNKNOWN_ADV_IDENTIFIER, "Unknown Advertising Identifier"); 218 | (0x43, LIMIT_REACHED, "Limit Reached"); 219 | (0x44, OPERATION_CANCELLED_BY_HOST, "Operation Cancelled by Host"); 220 | (0x45, PACKET_TOO_LONG, "Packet Too Long"); 221 | } 222 | -------------------------------------------------------------------------------- /src/transport.rs: -------------------------------------------------------------------------------- 1 | //! HCI transport layers [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface.html) 2 | 3 | use core::future::Future; 4 | 5 | use embassy_sync::blocking_mutex::raw::RawMutex; 6 | use embassy_sync::mutex::Mutex; 7 | use embedded_io::{ErrorType, ReadExactError}; 8 | 9 | use crate::controller::blocking::TryError; 10 | use crate::{ControllerToHostPacket, FromHciBytesError, HostToControllerPacket, ReadHci, ReadHciError, WriteHci}; 11 | 12 | /// A packet-oriented HCI Transport Layer 13 | pub trait Transport: embedded_io::ErrorType { 14 | /// Read a complete HCI packet into the rx buffer 15 | fn read<'a>(&self, rx: &'a mut [u8]) -> impl Future, Self::Error>>; 16 | /// Write a complete HCI packet from the tx buffer 17 | fn write(&self, val: &T) -> impl Future>; 18 | } 19 | 20 | /// HCI transport layer for a split serial bus using the UART transport layer protocol [📖](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/uart-transport-layer.html) 21 | pub struct SerialTransport { 22 | reader: Mutex, 23 | writer: Mutex, 24 | } 25 | 26 | /// Error type for HCI transport layer communication errors. 27 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 28 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 29 | pub enum Error { 30 | /// Error reading HCI data. 31 | Read(ReadHciError), 32 | /// Error writing data. 33 | Write(E), 34 | } 35 | 36 | impl embedded_io::Error for Error { 37 | fn kind(&self) -> embedded_io::ErrorKind { 38 | match self { 39 | Self::Read(e) => e.kind(), 40 | Self::Write(e) => e.kind(), 41 | } 42 | } 43 | } 44 | 45 | impl From for Error { 46 | fn from(e: E) -> Self { 47 | Self::Write(e) 48 | } 49 | } 50 | 51 | impl From> for Error { 52 | fn from(e: ReadHciError) -> Self { 53 | Self::Read(e) 54 | } 55 | } 56 | 57 | impl From> for Error { 58 | fn from(e: ReadExactError) -> Self { 59 | Self::Read(e.into()) 60 | } 61 | } 62 | 63 | impl From for Error { 64 | fn from(e: FromHciBytesError) -> Self { 65 | Self::Read(e.into()) 66 | } 67 | } 68 | 69 | impl SerialTransport { 70 | /// Create a new instance. 71 | pub fn new(reader: R, writer: W) -> Self { 72 | Self { 73 | reader: Mutex::new(reader), 74 | writer: Mutex::new(writer), 75 | } 76 | } 77 | } 78 | 79 | impl< 80 | M: RawMutex, 81 | R: embedded_io::ErrorType, 82 | W: embedded_io::ErrorType, 83 | E: embedded_io::Error, 84 | > ErrorType for SerialTransport 85 | { 86 | type Error = Error; 87 | } 88 | 89 | impl< 90 | M: RawMutex, 91 | R: embedded_io_async::Read, 92 | W: embedded_io_async::Write, 93 | E: embedded_io::Error, 94 | > Transport for SerialTransport 95 | { 96 | async fn read<'a>(&self, rx: &'a mut [u8]) -> Result, Self::Error> { 97 | let mut r = self.reader.lock().await; 98 | ControllerToHostPacket::read_hci_async(&mut *r, rx) 99 | .await 100 | .map_err(Error::Read) 101 | } 102 | 103 | async fn write(&self, tx: &T) -> Result<(), Self::Error> { 104 | let mut w = self.writer.lock().await; 105 | WithIndicator(tx) 106 | .write_hci_async(&mut *w) 107 | .await 108 | .map_err(|e| Error::Write(e)) 109 | } 110 | } 111 | 112 | impl, W: embedded_io::Write, E: embedded_io::Error> 113 | blocking::Transport for SerialTransport 114 | { 115 | fn read<'a>(&self, rx: &'a mut [u8]) -> Result, TryError> { 116 | let mut r = self.reader.try_lock().map_err(|_| TryError::Busy)?; 117 | ControllerToHostPacket::read_hci(&mut *r, rx) 118 | .map_err(Error::Read) 119 | .map_err(TryError::Error) 120 | } 121 | 122 | fn write(&self, tx: &T) -> Result<(), TryError> { 123 | let mut w = self.writer.try_lock().map_err(|_| TryError::Busy)?; 124 | WithIndicator(tx) 125 | .write_hci(&mut *w) 126 | .map_err(|e| Error::Write(e)) 127 | .map_err(TryError::Error) 128 | } 129 | } 130 | 131 | /// Wrapper for a [`HostToControllerPacket`] that will write the [`PacketKind`](crate::PacketKind) indicator byte before the packet itself 132 | /// when serialized with [`WriteHci`]. 133 | /// 134 | /// This is used for transports where all packets are sent over a common channel, such as the UART transport. 135 | pub struct WithIndicator<'a, T: HostToControllerPacket>(&'a T); 136 | 137 | impl<'a, T: HostToControllerPacket> WithIndicator<'a, T> { 138 | /// Create a new instance. 139 | pub fn new(pkt: &'a T) -> Self { 140 | Self(pkt) 141 | } 142 | } 143 | 144 | impl WriteHci for WithIndicator<'_, T> { 145 | #[inline(always)] 146 | fn size(&self) -> usize { 147 | 1 + self.0.size() 148 | } 149 | 150 | #[inline(always)] 151 | fn write_hci(&self, mut writer: W) -> Result<(), W::Error> { 152 | T::KIND.write_hci(&mut writer)?; 153 | self.0.write_hci(writer) 154 | } 155 | 156 | #[inline(always)] 157 | async fn write_hci_async(&self, mut writer: W) -> Result<(), W::Error> { 158 | T::KIND.write_hci_async(&mut writer).await?; 159 | self.0.write_hci_async(writer).await 160 | } 161 | } 162 | 163 | pub mod blocking { 164 | //! Blocking transport trait. 165 | use super::*; 166 | use crate::controller::blocking::TryError; 167 | 168 | /// A packet-oriented HCI Transport Layer 169 | pub trait Transport: embedded_io::ErrorType { 170 | /// Read a complete HCI packet into the rx buffer 171 | fn read<'a>(&self, rx: &'a mut [u8]) -> Result, TryError>; 172 | /// Write a complete HCI packet from the tx buffer 173 | fn write(&self, val: &T) -> Result<(), TryError>; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/uuid.rs: -------------------------------------------------------------------------------- 1 | //! This module contains the UUIDs for Bluetooth. 2 | 3 | use core::fmt::{Debug, Display}; 4 | 5 | pub mod appearance; 6 | pub mod browse_group_identifiers; 7 | pub mod characteristic; 8 | pub mod declarations; 9 | pub mod descriptors; 10 | pub mod mesh_profile; 11 | pub mod object_types; 12 | pub mod protocol_identifiers; 13 | pub mod service; 14 | pub mod service_class; 15 | pub mod units; 16 | 17 | /// 0000xxxx-0000-1000-8000-00805F9B34FB; 18 | /// 19 | /// BLUETOOTH CORE SPECIFICATION Version 6.0 | Vol 3, Part B | Page 1250 20 | /// [(link)](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host/service-discovery-protocol--sdp--specification.html#UUID-ef710684-4c7e-6793-4350-4a190ea9a7a4) 21 | /// 22 | /// The full 128-bit value of a 16-bit or 32-bit UUID may be computed by a simple arithmetic operation. 23 | /// 24 | /// 128_bit_value = 16_bit_value × 2^96 + Bluetooth_Base_UUID 25 | pub const BLUETOOTH_BASE_UUID: [u8; 16] = [ 26 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB, 27 | ]; 28 | #[cfg(feature = "uuid")] 29 | const BASE_UUID: uuid::Uuid = uuid::Uuid::from_bytes_le(BLUETOOTH_BASE_UUID); 30 | 31 | /// Bluetooth UUID. 32 | #[repr(transparent)] 33 | #[derive(Clone, Copy, PartialEq, Eq)] 34 | pub struct BluetoothUuid16(u16); 35 | 36 | impl BluetoothUuid16 { 37 | /// Create a new `BluetoothUuid16`. 38 | pub const fn new(uuid: u16) -> Self { 39 | Self(uuid) 40 | } 41 | /// Convert the `BluetoothUuid16` to a byte array as a const function. 42 | pub const fn to_le_bytes(self) -> [u8; 2] { 43 | self.0.to_le_bytes() 44 | } 45 | /// Convert from a byte array to a `BluetoothUuid16`. 46 | pub const fn from_le_bytes(bytes: [u8; 2]) -> Self { 47 | Self(u16::from_le_bytes(bytes)) 48 | } 49 | } 50 | 51 | impl From for u16 { 52 | fn from(uuid: BluetoothUuid16) -> u16 { 53 | uuid.0 54 | } 55 | } 56 | 57 | impl From for [u8; 2] { 58 | fn from(uuid: BluetoothUuid16) -> [u8; 2] { 59 | uuid.0.to_le_bytes() 60 | } 61 | } 62 | 63 | impl Debug for BluetoothUuid16 { 64 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 65 | write!(f, "BluetoothUuid16(0x{:04X})", self.0) 66 | } 67 | } 68 | 69 | impl Display for BluetoothUuid16 { 70 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 71 | write!(f, "0x{:04X}", self.0) 72 | } 73 | } 74 | 75 | #[cfg(feature = "defmt")] 76 | impl defmt::Format for BluetoothUuid16 { 77 | fn format(&self, f: defmt::Formatter) { 78 | defmt::write!(f, "BluetoothUuid16(0x{:04X})", self.0) 79 | } 80 | } 81 | 82 | #[cfg(feature = "uuid")] 83 | impl From for uuid::Uuid { 84 | fn from(uuid: BluetoothUuid16) -> uuid::Uuid { 85 | // "0000xxxx-0000-1000-8000-00805F9B34FB" 86 | uuid::Uuid::from_u128(BASE_UUID.as_u128() | (u128::from(uuid.0) << 96)) 87 | } 88 | } 89 | 90 | #[cfg(test)] 91 | mod test { 92 | use super::*; 93 | 94 | #[test] 95 | fn test_ble_uuid() { 96 | const BLE_UUID: BluetoothUuid16 = BluetoothUuid16::new(0x1234); 97 | assert_eq!(u16::from(BLE_UUID), 0x1234); 98 | let uuid: u16 = BLE_UUID.into(); 99 | assert_eq!(uuid, 0x1234); 100 | const UUID: [u8; 2] = BLE_UUID.to_le_bytes(); 101 | assert_eq!(UUID, [0x34, 0x12]); 102 | } 103 | 104 | #[cfg(feature = "uuid")] 105 | #[test] 106 | fn test_uuid_conversion() { 107 | let result = uuid::Uuid::from(BluetoothUuid16::new(0x1234)); 108 | let expected = "00001234-0000-0010-8000-00805f9b34fb".parse::().unwrap(); 109 | 110 | // defmt::Format not implemented on uuid::Uuid 111 | assert_eq!(result.into_bytes(), expected.into_bytes()); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/uuid/appearance/mod.rs: -------------------------------------------------------------------------------- 1 | //! The representation of the external appearance of the device. 2 | //! 3 | //! https://www.bluetooth.com/wp-content/uploads/Files/Specification/Assigned_Numbers.html#bookmark49 4 | 5 | #[allow(dead_code)] 6 | mod categories; 7 | 8 | pub use categories::*; 9 | 10 | use super::BluetoothUuid16; 11 | 12 | /// Construct a new appearance value for the GAP Service. 13 | /// 14 | /// Follow the pattern of the examples below to create new appearance values. 15 | /// Use UUIDs from the [Bluetooth Assigned Numbers list](https://www.bluetooth.com/wp-content/uploads/Files/Specification/Assigned_Numbers.html#bookmark49). 16 | /// 17 | /// ## Example 18 | /// 19 | /// ```rust ignore 20 | /// 21 | /// const GAMEPAD: BluetoothUuid16 = appearance::from_category(0x00F, 0x040); 22 | /// const GAMEPAD_BYTES: &[u8; 2] = &GAMEPAD.to_le_bytes(); 23 | /// ``` 24 | pub const fn from_category(category: u8, subcategory: u8) -> BluetoothUuid16 { 25 | let uuid = ((category as u16) << 6) | (subcategory as u16); 26 | BluetoothUuid16(uuid) 27 | } 28 | 29 | #[cfg(test)] 30 | mod test { 31 | use super::*; 32 | 33 | #[test] 34 | fn test_appearance() { 35 | const CUSTOM_UUID: BluetoothUuid16 = from_category(0x002, 0x007); 36 | assert_eq!(u16::from(CUSTOM_UUID), 0x0087); 37 | let uuid: u16 = aircraft::LARGE_PASSENGER_AIRCRAFT.into(); 38 | assert_eq!(uuid, 0x0984); 39 | const LABEL_BYTES: [u8; 2] = signage::ELECTRONIC_LABEL.to_le_bytes(); 40 | assert_eq!(LABEL_BYTES, [0xc2, 0x0a]); 41 | const GAMEPAD: BluetoothUuid16 = power_device::GENERIC_POWER_DEVICE; 42 | assert_eq!(u16::from(GAMEPAD), 0x780); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/uuid/browse_group_identifiers.rs: -------------------------------------------------------------------------------- 1 | //! UUIDs for the browse group identifiers module. 2 | 3 | // This file is auto-generated by the update_uuids application. 4 | // Based on https://bitbucket.org/bluetooth-SIG/public.git 5 | // Commit hash: 22c4a7a751fe51de707be6d294adb0d115e86897 6 | 7 | use super::BluetoothUuid16; 8 | 9 | /// Bluetooth browse group identifiers UUID. 10 | /// 11 | /// `0x1002` PublicBrowseRoot 12 | pub const PUBLIC_BROWSE_ROOT: BluetoothUuid16 = BluetoothUuid16::new(0x1002); 13 | -------------------------------------------------------------------------------- /src/uuid/declarations.rs: -------------------------------------------------------------------------------- 1 | //! UUIDs for the declarations module. 2 | 3 | // This file is auto-generated by the update_uuids application. 4 | // Based on https://bitbucket.org/bluetooth-SIG/public.git 5 | // Commit hash: 22c4a7a751fe51de707be6d294adb0d115e86897 6 | 7 | use super::BluetoothUuid16; 8 | 9 | /// Bluetooth declarations UUID. 10 | /// 11 | /// `0x2800` Primary Service 12 | pub const PRIMARY_SERVICE: BluetoothUuid16 = BluetoothUuid16::new(0x2800); 13 | 14 | /// Bluetooth declarations UUID. 15 | /// 16 | /// `0x2801` Secondary Service 17 | pub const SECONDARY_SERVICE: BluetoothUuid16 = BluetoothUuid16::new(0x2801); 18 | 19 | /// Bluetooth declarations UUID. 20 | /// 21 | /// `0x2802` Include 22 | pub const INCLUDE: BluetoothUuid16 = BluetoothUuid16::new(0x2802); 23 | 24 | /// Bluetooth declarations UUID. 25 | /// 26 | /// `0x2803` Characteristic 27 | pub const CHARACTERISTIC: BluetoothUuid16 = BluetoothUuid16::new(0x2803); 28 | -------------------------------------------------------------------------------- /src/uuid/descriptors.rs: -------------------------------------------------------------------------------- 1 | //! UUIDs for the descriptors module. 2 | 3 | // This file is auto-generated by the update_uuids application. 4 | // Based on https://bitbucket.org/bluetooth-SIG/public.git 5 | // Commit hash: 22c4a7a751fe51de707be6d294adb0d115e86897 6 | 7 | use super::BluetoothUuid16; 8 | 9 | /// Bluetooth descriptors UUID. 10 | /// 11 | /// `0x2900` Characteristic Extended Properties 12 | pub const CHARACTERISTIC_EXTENDED_PROPERTIES: BluetoothUuid16 = BluetoothUuid16::new(0x2900); 13 | 14 | /// Bluetooth descriptors UUID. 15 | /// 16 | /// `0x2901` Characteristic User Description 17 | pub const CHARACTERISTIC_USER_DESCRIPTION: BluetoothUuid16 = BluetoothUuid16::new(0x2901); 18 | 19 | /// Bluetooth descriptors UUID. 20 | /// 21 | /// `0x2902` Client Characteristic Configuration 22 | pub const CLIENT_CHARACTERISTIC_CONFIGURATION: BluetoothUuid16 = BluetoothUuid16::new(0x2902); 23 | 24 | /// Bluetooth descriptors UUID. 25 | /// 26 | /// `0x2903` Server Characteristic Configuration 27 | pub const SERVER_CHARACTERISTIC_CONFIGURATION: BluetoothUuid16 = BluetoothUuid16::new(0x2903); 28 | 29 | /// Bluetooth descriptors UUID. 30 | /// 31 | /// `0x2904` Characteristic Presentation Format 32 | pub const CHARACTERISTIC_PRESENTATION_FORMAT: BluetoothUuid16 = BluetoothUuid16::new(0x2904); 33 | 34 | /// Bluetooth descriptors UUID. 35 | /// 36 | /// `0x2905` Characteristic Aggregate Format 37 | pub const CHARACTERISTIC_AGGREGATE_FORMAT: BluetoothUuid16 = BluetoothUuid16::new(0x2905); 38 | 39 | /// Bluetooth descriptors UUID. 40 | /// 41 | /// `0x2906` Valid Range 42 | pub const VALID_RANGE: BluetoothUuid16 = BluetoothUuid16::new(0x2906); 43 | 44 | /// Bluetooth descriptors UUID. 45 | /// 46 | /// `0x2907` External Report Reference 47 | pub const EXTERNAL_REPORT_REFERENCE: BluetoothUuid16 = BluetoothUuid16::new(0x2907); 48 | 49 | /// Bluetooth descriptors UUID. 50 | /// 51 | /// `0x2908` Report Reference 52 | pub const REPORT_REFERENCE: BluetoothUuid16 = BluetoothUuid16::new(0x2908); 53 | 54 | /// Bluetooth descriptors UUID. 55 | /// 56 | /// `0x2909` Number of Digitals 57 | pub const NUMBER_OF_DIGITALS: BluetoothUuid16 = BluetoothUuid16::new(0x2909); 58 | 59 | /// Bluetooth descriptors UUID. 60 | /// 61 | /// `0x290a` Value Trigger Setting 62 | pub const VALUE_TRIGGER_SETTING: BluetoothUuid16 = BluetoothUuid16::new(0x290a); 63 | 64 | /// Bluetooth descriptors UUID. 65 | /// 66 | /// `0x290b` Environmental Sensing Configuration 67 | pub const ENVIRONMENTAL_SENSING_CONFIGURATION: BluetoothUuid16 = BluetoothUuid16::new(0x290b); 68 | 69 | /// Bluetooth descriptors UUID. 70 | /// 71 | /// `0x290c` Environmental Sensing Measurement 72 | pub const ENVIRONMENTAL_SENSING_MEASUREMENT: BluetoothUuid16 = BluetoothUuid16::new(0x290c); 73 | 74 | /// Bluetooth descriptors UUID. 75 | /// 76 | /// `0x290d` Environmental Sensing Trigger Setting 77 | pub const ENVIRONMENTAL_SENSING_TRIGGER_SETTING: BluetoothUuid16 = BluetoothUuid16::new(0x290d); 78 | 79 | /// Bluetooth descriptors UUID. 80 | /// 81 | /// `0x290e` Time Trigger Setting 82 | pub const TIME_TRIGGER_SETTING: BluetoothUuid16 = BluetoothUuid16::new(0x290e); 83 | 84 | /// Bluetooth descriptors UUID. 85 | /// 86 | /// `0x290f` Complete BR-EDR Transport Block Data 87 | pub const COMPLETE_BR_EDR_TRANSPORT_BLOCK_DATA: BluetoothUuid16 = BluetoothUuid16::new(0x290f); 88 | 89 | /// Bluetooth descriptors UUID. 90 | /// 91 | /// `0x2910` Observation Schedule 92 | pub const OBSERVATION_SCHEDULE: BluetoothUuid16 = BluetoothUuid16::new(0x2910); 93 | 94 | /// Bluetooth descriptors UUID. 95 | /// 96 | /// `0x2911` Valid Range and Accuracy 97 | pub const VALID_RANGE_AND_ACCURACY: BluetoothUuid16 = BluetoothUuid16::new(0x2911); 98 | 99 | /// Bluetooth descriptors UUID. 100 | /// 101 | /// `0x2912` Measurement Description 102 | pub const MEASUREMENT_DESCRIPTION: BluetoothUuid16 = BluetoothUuid16::new(0x2912); 103 | 104 | /// Bluetooth descriptors UUID. 105 | /// 106 | /// `0x2913` Manufacturer Limits 107 | pub const MANUFACTURER_LIMITS: BluetoothUuid16 = BluetoothUuid16::new(0x2913); 108 | 109 | /// Bluetooth descriptors UUID. 110 | /// 111 | /// `0x2914` Process Tolerances 112 | pub const PROCESS_TOLERANCES: BluetoothUuid16 = BluetoothUuid16::new(0x2914); 113 | 114 | /// Bluetooth descriptors UUID. 115 | /// 116 | /// `0x2915` IMD Trigger Setting 117 | pub const IMD_TRIGGER_SETTING: BluetoothUuid16 = BluetoothUuid16::new(0x2915); 118 | -------------------------------------------------------------------------------- /src/uuid/mesh_profile.rs: -------------------------------------------------------------------------------- 1 | //! UUIDs for the mesh profile module. 2 | 3 | // This file is auto-generated by the update_uuids application. 4 | // Based on https://bitbucket.org/bluetooth-SIG/public.git 5 | // Commit hash: 22c4a7a751fe51de707be6d294adb0d115e86897 6 | 7 | use super::BluetoothUuid16; 8 | 9 | /// Bluetooth mesh profile UUID. 10 | /// 11 | /// `0x1600` Ambient Light Sensor NLC Profile 1.0 12 | pub const AMBIENT_LIGHT_SENSOR_NLC_PROFILE_10: BluetoothUuid16 = BluetoothUuid16::new(0x1600); 13 | 14 | /// Bluetooth mesh profile UUID. 15 | /// 16 | /// `0x1601` Basic Lightness Controller NLC Profile 1.0 17 | pub const BASIC_LIGHTNESS_CONTROLLER_NLC_PROFILE_10: BluetoothUuid16 = BluetoothUuid16::new(0x1601); 18 | 19 | /// Bluetooth mesh profile UUID. 20 | /// 21 | /// `0x1602` Basic Scene Selector NLC Profile 1.0 22 | pub const BASIC_SCENE_SELECTOR_NLC_PROFILE_10: BluetoothUuid16 = BluetoothUuid16::new(0x1602); 23 | 24 | /// Bluetooth mesh profile UUID. 25 | /// 26 | /// `0x1603` Dimming Control NLC Profile 1.0 27 | pub const DIMMING_CONTROL_NLC_PROFILE_10: BluetoothUuid16 = BluetoothUuid16::new(0x1603); 28 | 29 | /// Bluetooth mesh profile UUID. 30 | /// 31 | /// `0x1604` Energy Monitor NLC Profile 1.0 32 | pub const ENERGY_MONITOR_NLC_PROFILE_10: BluetoothUuid16 = BluetoothUuid16::new(0x1604); 33 | 34 | /// Bluetooth mesh profile UUID. 35 | /// 36 | /// `0x1605` Occupancy Sensor NLC Profile 1.0 37 | pub const OCCUPANCY_SENSOR_NLC_PROFILE_10: BluetoothUuid16 = BluetoothUuid16::new(0x1605); 38 | -------------------------------------------------------------------------------- /src/uuid/object_types.rs: -------------------------------------------------------------------------------- 1 | //! UUIDs for the object types module. 2 | 3 | // This file is auto-generated by the update_uuids application. 4 | // Based on https://bitbucket.org/bluetooth-SIG/public.git 5 | // Commit hash: 22c4a7a751fe51de707be6d294adb0d115e86897 6 | 7 | use super::BluetoothUuid16; 8 | 9 | /// Bluetooth object types UUID. 10 | /// 11 | /// `0x2aca` Unspecified 12 | pub const UNSPECIFIED: BluetoothUuid16 = BluetoothUuid16::new(0x2aca); 13 | 14 | /// Bluetooth object types UUID. 15 | /// 16 | /// `0x2acb` Directory Listing 17 | pub const DIRECTORY_LISTING: BluetoothUuid16 = BluetoothUuid16::new(0x2acb); 18 | 19 | /// Bluetooth object types UUID. 20 | /// 21 | /// `0x2ba9` Media Player Icon 22 | pub const MEDIA_PLAYER_ICON: BluetoothUuid16 = BluetoothUuid16::new(0x2ba9); 23 | 24 | /// Bluetooth object types UUID. 25 | /// 26 | /// `0x2baa` Track Segment 27 | pub const TRACK_SEGMENT: BluetoothUuid16 = BluetoothUuid16::new(0x2baa); 28 | 29 | /// Bluetooth object types UUID. 30 | /// 31 | /// `0x2bab` Track 32 | pub const TRACK: BluetoothUuid16 = BluetoothUuid16::new(0x2bab); 33 | 34 | /// Bluetooth object types UUID. 35 | /// 36 | /// `0x2bac` Group 37 | pub const GROUP: BluetoothUuid16 = BluetoothUuid16::new(0x2bac); 38 | -------------------------------------------------------------------------------- /src/uuid/protocol_identifiers.rs: -------------------------------------------------------------------------------- 1 | //! UUIDs for the protocol identifiers module. 2 | 3 | // This file is auto-generated by the update_uuids application. 4 | // Based on https://bitbucket.org/bluetooth-SIG/public.git 5 | // Commit hash: 22c4a7a751fe51de707be6d294adb0d115e86897 6 | 7 | use super::BluetoothUuid16; 8 | 9 | /// Bluetooth protocol identifiers UUID. 10 | /// 11 | /// `0x0001` SDP 12 | pub const SDP: BluetoothUuid16 = BluetoothUuid16::new(0x1); 13 | 14 | /// Bluetooth protocol identifiers UUID. 15 | /// 16 | /// `0x0002` UDP 17 | pub const UDP: BluetoothUuid16 = BluetoothUuid16::new(0x2); 18 | 19 | /// Bluetooth protocol identifiers UUID. 20 | /// 21 | /// `0x0003` RFCOMM 22 | pub const RFCOMM: BluetoothUuid16 = BluetoothUuid16::new(0x3); 23 | 24 | /// Bluetooth protocol identifiers UUID. 25 | /// 26 | /// `0x0004` TCP 27 | pub const TCP: BluetoothUuid16 = BluetoothUuid16::new(0x4); 28 | 29 | /// Bluetooth protocol identifiers UUID. 30 | /// 31 | /// `0x0005` TCS-BIN 32 | pub const TCS_BIN: BluetoothUuid16 = BluetoothUuid16::new(0x5); 33 | 34 | /// Bluetooth protocol identifiers UUID. 35 | /// 36 | /// `0x0006` TCS-AT 37 | pub const TCS_AT: BluetoothUuid16 = BluetoothUuid16::new(0x6); 38 | 39 | /// Bluetooth protocol identifiers UUID. 40 | /// 41 | /// `0x0007` ATT 42 | pub const ATT: BluetoothUuid16 = BluetoothUuid16::new(0x7); 43 | 44 | /// Bluetooth protocol identifiers UUID. 45 | /// 46 | /// `0x0008` OBEX 47 | pub const OBEX: BluetoothUuid16 = BluetoothUuid16::new(0x8); 48 | 49 | /// Bluetooth protocol identifiers UUID. 50 | /// 51 | /// `0x0009` IP 52 | pub const IP: BluetoothUuid16 = BluetoothUuid16::new(0x9); 53 | 54 | /// Bluetooth protocol identifiers UUID. 55 | /// 56 | /// `0x000a` FTP 57 | pub const FTP: BluetoothUuid16 = BluetoothUuid16::new(0xa); 58 | 59 | /// Bluetooth protocol identifiers UUID. 60 | /// 61 | /// `0x000c` HTTP 62 | pub const HTTP: BluetoothUuid16 = BluetoothUuid16::new(0xc); 63 | 64 | /// Bluetooth protocol identifiers UUID. 65 | /// 66 | /// `0x000e` WSP 67 | pub const WSP: BluetoothUuid16 = BluetoothUuid16::new(0xe); 68 | 69 | /// Bluetooth protocol identifiers UUID. 70 | /// 71 | /// `0x000f` BNEP 72 | pub const BNEP: BluetoothUuid16 = BluetoothUuid16::new(0xf); 73 | 74 | /// Bluetooth protocol identifiers UUID. 75 | /// 76 | /// `0x0010` UPNP 77 | pub const UPNP: BluetoothUuid16 = BluetoothUuid16::new(0x10); 78 | 79 | /// Bluetooth protocol identifiers UUID. 80 | /// 81 | /// `0x0011` HID Protocol 82 | pub const HID_PROTOCOL: BluetoothUuid16 = BluetoothUuid16::new(0x11); 83 | 84 | /// Bluetooth protocol identifiers UUID. 85 | /// 86 | /// `0x0012` HardcopyControlChannel 87 | pub const HARDCOPY_CONTROL_CHANNEL: BluetoothUuid16 = BluetoothUuid16::new(0x12); 88 | 89 | /// Bluetooth protocol identifiers UUID. 90 | /// 91 | /// `0x0014` HardcopyDataChannel 92 | pub const HARDCOPY_DATA_CHANNEL: BluetoothUuid16 = BluetoothUuid16::new(0x14); 93 | 94 | /// Bluetooth protocol identifiers UUID. 95 | /// 96 | /// `0x0016` HardcopyNotificationChannel 97 | pub const HARDCOPY_NOTIFICATION_CHANNEL: BluetoothUuid16 = BluetoothUuid16::new(0x16); 98 | 99 | /// Bluetooth protocol identifiers UUID. 100 | /// 101 | /// `0x0017` AVCTP 102 | pub const AVCTP: BluetoothUuid16 = BluetoothUuid16::new(0x17); 103 | 104 | /// Bluetooth protocol identifiers UUID. 105 | /// 106 | /// `0x0019` AVDTP 107 | pub const AVDTP: BluetoothUuid16 = BluetoothUuid16::new(0x19); 108 | 109 | /// Bluetooth protocol identifiers UUID. 110 | /// 111 | /// `0x001b` CMTP 112 | pub const CMTP: BluetoothUuid16 = BluetoothUuid16::new(0x1b); 113 | 114 | /// Bluetooth protocol identifiers UUID. 115 | /// 116 | /// `0x001e` MCAP Control Channel 117 | pub const MCAP_CONTROL_CHANNEL: BluetoothUuid16 = BluetoothUuid16::new(0x1e); 118 | 119 | /// Bluetooth protocol identifiers UUID. 120 | /// 121 | /// `0x001f` MCAP Data Channel 122 | pub const MCAP_DATA_CHANNEL: BluetoothUuid16 = BluetoothUuid16::new(0x1f); 123 | 124 | /// Bluetooth protocol identifiers UUID. 125 | /// 126 | /// `0x0100` L2CAP 127 | pub const L2_CAP: BluetoothUuid16 = BluetoothUuid16::new(0x100); 128 | -------------------------------------------------------------------------------- /src/uuid/service.rs: -------------------------------------------------------------------------------- 1 | //! UUIDs for the service module. 2 | 3 | // This file is auto-generated by the update_uuids application. 4 | // Based on https://bitbucket.org/bluetooth-SIG/public.git 5 | // Commit hash: 22c4a7a751fe51de707be6d294adb0d115e86897 6 | 7 | use super::BluetoothUuid16; 8 | 9 | /// Bluetooth service UUID. 10 | /// 11 | /// `0x1800` GAP 12 | pub const GAP: BluetoothUuid16 = BluetoothUuid16::new(0x1800); 13 | 14 | /// Bluetooth service UUID. 15 | /// 16 | /// `0x1801` GATT 17 | pub const GATT: BluetoothUuid16 = BluetoothUuid16::new(0x1801); 18 | 19 | /// Bluetooth service UUID. 20 | /// 21 | /// `0x1802` Immediate Alert 22 | pub const IMMEDIATE_ALERT: BluetoothUuid16 = BluetoothUuid16::new(0x1802); 23 | 24 | /// Bluetooth service UUID. 25 | /// 26 | /// `0x1803` Link Loss 27 | pub const LINK_LOSS: BluetoothUuid16 = BluetoothUuid16::new(0x1803); 28 | 29 | /// Bluetooth service UUID. 30 | /// 31 | /// `0x1804` Tx Power 32 | pub const TX_POWER: BluetoothUuid16 = BluetoothUuid16::new(0x1804); 33 | 34 | /// Bluetooth service UUID. 35 | /// 36 | /// `0x1805` Current Time 37 | pub const CURRENT_TIME: BluetoothUuid16 = BluetoothUuid16::new(0x1805); 38 | 39 | /// Bluetooth service UUID. 40 | /// 41 | /// `0x1806` Reference Time Update 42 | pub const REFERENCE_TIME_UPDATE: BluetoothUuid16 = BluetoothUuid16::new(0x1806); 43 | 44 | /// Bluetooth service UUID. 45 | /// 46 | /// `0x1807` Next DST Change 47 | pub const NEXT_DST_CHANGE: BluetoothUuid16 = BluetoothUuid16::new(0x1807); 48 | 49 | /// Bluetooth service UUID. 50 | /// 51 | /// `0x1808` Glucose 52 | pub const GLUCOSE: BluetoothUuid16 = BluetoothUuid16::new(0x1808); 53 | 54 | /// Bluetooth service UUID. 55 | /// 56 | /// `0x1809` Health Thermometer 57 | pub const HEALTH_THERMOMETER: BluetoothUuid16 = BluetoothUuid16::new(0x1809); 58 | 59 | /// Bluetooth service UUID. 60 | /// 61 | /// `0x180a` Device Information 62 | pub const DEVICE_INFORMATION: BluetoothUuid16 = BluetoothUuid16::new(0x180a); 63 | 64 | /// Bluetooth service UUID. 65 | /// 66 | /// `0x180d` Heart Rate 67 | pub const HEART_RATE: BluetoothUuid16 = BluetoothUuid16::new(0x180d); 68 | 69 | /// Bluetooth service UUID. 70 | /// 71 | /// `0x180e` Phone Alert Status 72 | pub const PHONE_ALERT_STATUS: BluetoothUuid16 = BluetoothUuid16::new(0x180e); 73 | 74 | /// Bluetooth service UUID. 75 | /// 76 | /// `0x180f` Battery 77 | pub const BATTERY: BluetoothUuid16 = BluetoothUuid16::new(0x180f); 78 | 79 | /// Bluetooth service UUID. 80 | /// 81 | /// `0x1810` Blood Pressure 82 | pub const BLOOD_PRESSURE: BluetoothUuid16 = BluetoothUuid16::new(0x1810); 83 | 84 | /// Bluetooth service UUID. 85 | /// 86 | /// `0x1811` Alert Notification 87 | pub const ALERT_NOTIFICATION: BluetoothUuid16 = BluetoothUuid16::new(0x1811); 88 | 89 | /// Bluetooth service UUID. 90 | /// 91 | /// `0x1812` Human Interface Device 92 | pub const HUMAN_INTERFACE_DEVICE: BluetoothUuid16 = BluetoothUuid16::new(0x1812); 93 | 94 | /// Bluetooth service UUID. 95 | /// 96 | /// `0x1813` Scan Parameters 97 | pub const SCAN_PARAMETERS: BluetoothUuid16 = BluetoothUuid16::new(0x1813); 98 | 99 | /// Bluetooth service UUID. 100 | /// 101 | /// `0x1814` Running Speed and Cadence 102 | pub const RUNNING_SPEED_AND_CADENCE: BluetoothUuid16 = BluetoothUuid16::new(0x1814); 103 | 104 | /// Bluetooth service UUID. 105 | /// 106 | /// `0x1815` Automation IO 107 | pub const AUTOMATION_IO: BluetoothUuid16 = BluetoothUuid16::new(0x1815); 108 | 109 | /// Bluetooth service UUID. 110 | /// 111 | /// `0x1816` Cycling Speed and Cadence 112 | pub const CYCLING_SPEED_AND_CADENCE: BluetoothUuid16 = BluetoothUuid16::new(0x1816); 113 | 114 | /// Bluetooth service UUID. 115 | /// 116 | /// `0x1818` Cycling Power 117 | pub const CYCLING_POWER: BluetoothUuid16 = BluetoothUuid16::new(0x1818); 118 | 119 | /// Bluetooth service UUID. 120 | /// 121 | /// `0x1819` Location and Navigation 122 | pub const LOCATION_AND_NAVIGATION: BluetoothUuid16 = BluetoothUuid16::new(0x1819); 123 | 124 | /// Bluetooth service UUID. 125 | /// 126 | /// `0x181a` Environmental Sensing 127 | pub const ENVIRONMENTAL_SENSING: BluetoothUuid16 = BluetoothUuid16::new(0x181a); 128 | 129 | /// Bluetooth service UUID. 130 | /// 131 | /// `0x181b` Body Composition 132 | pub const BODY_COMPOSITION: BluetoothUuid16 = BluetoothUuid16::new(0x181b); 133 | 134 | /// Bluetooth service UUID. 135 | /// 136 | /// `0x181c` User Data 137 | pub const USER_DATA: BluetoothUuid16 = BluetoothUuid16::new(0x181c); 138 | 139 | /// Bluetooth service UUID. 140 | /// 141 | /// `0x181d` Weight Scale 142 | pub const WEIGHT_SCALE: BluetoothUuid16 = BluetoothUuid16::new(0x181d); 143 | 144 | /// Bluetooth service UUID. 145 | /// 146 | /// `0x181e` Bond Management 147 | pub const BOND_MANAGEMENT: BluetoothUuid16 = BluetoothUuid16::new(0x181e); 148 | 149 | /// Bluetooth service UUID. 150 | /// 151 | /// `0x181f` Continuous Glucose Monitoring 152 | pub const CONTINUOUS_GLUCOSE_MONITORING: BluetoothUuid16 = BluetoothUuid16::new(0x181f); 153 | 154 | /// Bluetooth service UUID. 155 | /// 156 | /// `0x1820` Internet Protocol Support 157 | pub const INTERNET_PROTOCOL_SUPPORT: BluetoothUuid16 = BluetoothUuid16::new(0x1820); 158 | 159 | /// Bluetooth service UUID. 160 | /// 161 | /// `0x1821` Indoor Positioning 162 | pub const INDOOR_POSITIONING: BluetoothUuid16 = BluetoothUuid16::new(0x1821); 163 | 164 | /// Bluetooth service UUID. 165 | /// 166 | /// `0x1822` Pulse Oximeter 167 | pub const PULSE_OXIMETER: BluetoothUuid16 = BluetoothUuid16::new(0x1822); 168 | 169 | /// Bluetooth service UUID. 170 | /// 171 | /// `0x1823` HTTP Proxy 172 | pub const HTTP_PROXY: BluetoothUuid16 = BluetoothUuid16::new(0x1823); 173 | 174 | /// Bluetooth service UUID. 175 | /// 176 | /// `0x1824` Transport Discovery 177 | pub const TRANSPORT_DISCOVERY: BluetoothUuid16 = BluetoothUuid16::new(0x1824); 178 | 179 | /// Bluetooth service UUID. 180 | /// 181 | /// `0x1825` Object Transfer 182 | pub const OBJECT_TRANSFER: BluetoothUuid16 = BluetoothUuid16::new(0x1825); 183 | 184 | /// Bluetooth service UUID. 185 | /// 186 | /// `0x1826` Fitness Machine 187 | pub const FITNESS_MACHINE: BluetoothUuid16 = BluetoothUuid16::new(0x1826); 188 | 189 | /// Bluetooth service UUID. 190 | /// 191 | /// `0x1827` Mesh Provisioning 192 | pub const MESH_PROVISIONING: BluetoothUuid16 = BluetoothUuid16::new(0x1827); 193 | 194 | /// Bluetooth service UUID. 195 | /// 196 | /// `0x1828` Mesh Proxy 197 | pub const MESH_PROXY: BluetoothUuid16 = BluetoothUuid16::new(0x1828); 198 | 199 | /// Bluetooth service UUID. 200 | /// 201 | /// `0x1829` Reconnection Configuration 202 | pub const RECONNECTION_CONFIGURATION: BluetoothUuid16 = BluetoothUuid16::new(0x1829); 203 | 204 | /// Bluetooth service UUID. 205 | /// 206 | /// `0x183a` Insulin Delivery 207 | pub const INSULIN_DELIVERY: BluetoothUuid16 = BluetoothUuid16::new(0x183a); 208 | 209 | /// Bluetooth service UUID. 210 | /// 211 | /// `0x183b` Binary Sensor 212 | pub const BINARY_SENSOR: BluetoothUuid16 = BluetoothUuid16::new(0x183b); 213 | 214 | /// Bluetooth service UUID. 215 | /// 216 | /// `0x183c` Emergency Configuration 217 | pub const EMERGENCY_CONFIGURATION: BluetoothUuid16 = BluetoothUuid16::new(0x183c); 218 | 219 | /// Bluetooth service UUID. 220 | /// 221 | /// `0x183d` Authorization Control 222 | pub const AUTHORIZATION_CONTROL: BluetoothUuid16 = BluetoothUuid16::new(0x183d); 223 | 224 | /// Bluetooth service UUID. 225 | /// 226 | /// `0x183e` Physical Activity Monitor 227 | pub const PHYSICAL_ACTIVITY_MONITOR: BluetoothUuid16 = BluetoothUuid16::new(0x183e); 228 | 229 | /// Bluetooth service UUID. 230 | /// 231 | /// `0x183f` Elapsed Time 232 | pub const ELAPSED_TIME: BluetoothUuid16 = BluetoothUuid16::new(0x183f); 233 | 234 | /// Bluetooth service UUID. 235 | /// 236 | /// `0x1840` Generic Health Sensor 237 | pub const GENERIC_HEALTH_SENSOR: BluetoothUuid16 = BluetoothUuid16::new(0x1840); 238 | 239 | /// Bluetooth service UUID. 240 | /// 241 | /// `0x1843` Audio Input Control 242 | pub const AUDIO_INPUT_CONTROL: BluetoothUuid16 = BluetoothUuid16::new(0x1843); 243 | 244 | /// Bluetooth service UUID. 245 | /// 246 | /// `0x1844` Volume Control 247 | pub const VOLUME_CONTROL: BluetoothUuid16 = BluetoothUuid16::new(0x1844); 248 | 249 | /// Bluetooth service UUID. 250 | /// 251 | /// `0x1845` Volume Offset Control 252 | pub const VOLUME_OFFSET_CONTROL: BluetoothUuid16 = BluetoothUuid16::new(0x1845); 253 | 254 | /// Bluetooth service UUID. 255 | /// 256 | /// `0x1846` Coordinated Set Identification 257 | pub const COORDINATED_SET_IDENTIFICATION: BluetoothUuid16 = BluetoothUuid16::new(0x1846); 258 | 259 | /// Bluetooth service UUID. 260 | /// 261 | /// `0x1847` Device Time 262 | pub const DEVICE_TIME: BluetoothUuid16 = BluetoothUuid16::new(0x1847); 263 | 264 | /// Bluetooth service UUID. 265 | /// 266 | /// `0x1848` Media Control 267 | pub const MEDIA_CONTROL: BluetoothUuid16 = BluetoothUuid16::new(0x1848); 268 | 269 | /// Bluetooth service UUID. 270 | /// 271 | /// `0x1849` Generic Media Control 272 | pub const GENERIC_MEDIA_CONTROL: BluetoothUuid16 = BluetoothUuid16::new(0x1849); 273 | 274 | /// Bluetooth service UUID. 275 | /// 276 | /// `0x184a` Constant Tone Extension 277 | pub const CONSTANT_TONE_EXTENSION: BluetoothUuid16 = BluetoothUuid16::new(0x184a); 278 | 279 | /// Bluetooth service UUID. 280 | /// 281 | /// `0x184b` Telephone Bearer 282 | pub const TELEPHONE_BEARER: BluetoothUuid16 = BluetoothUuid16::new(0x184b); 283 | 284 | /// Bluetooth service UUID. 285 | /// 286 | /// `0x184c` Generic Telephone Bearer 287 | pub const GENERIC_TELEPHONE_BEARER: BluetoothUuid16 = BluetoothUuid16::new(0x184c); 288 | 289 | /// Bluetooth service UUID. 290 | /// 291 | /// `0x184d` Microphone Control 292 | pub const MICROPHONE_CONTROL: BluetoothUuid16 = BluetoothUuid16::new(0x184d); 293 | 294 | /// Bluetooth service UUID. 295 | /// 296 | /// `0x184e` Audio Stream Control 297 | pub const AUDIO_STREAM_CONTROL: BluetoothUuid16 = BluetoothUuid16::new(0x184e); 298 | 299 | /// Bluetooth service UUID. 300 | /// 301 | /// `0x184f` Broadcast Audio Scan 302 | pub const BROADCAST_AUDIO_SCAN: BluetoothUuid16 = BluetoothUuid16::new(0x184f); 303 | 304 | /// Bluetooth service UUID. 305 | /// 306 | /// `0x1850` Published Audio Capabilities 307 | pub const PUBLISHED_AUDIO_CAPABILITIES: BluetoothUuid16 = BluetoothUuid16::new(0x1850); 308 | 309 | /// Bluetooth service UUID. 310 | /// 311 | /// `0x1851` Basic Audio Announcement 312 | pub const BASIC_AUDIO_ANNOUNCEMENT: BluetoothUuid16 = BluetoothUuid16::new(0x1851); 313 | 314 | /// Bluetooth service UUID. 315 | /// 316 | /// `0x1852` Broadcast Audio Announcement 317 | pub const BROADCAST_AUDIO_ANNOUNCEMENT: BluetoothUuid16 = BluetoothUuid16::new(0x1852); 318 | 319 | /// Bluetooth service UUID. 320 | /// 321 | /// `0x1853` Common Audio 322 | pub const COMMON_AUDIO: BluetoothUuid16 = BluetoothUuid16::new(0x1853); 323 | 324 | /// Bluetooth service UUID. 325 | /// 326 | /// `0x1854` Hearing Access 327 | pub const HEARING_ACCESS: BluetoothUuid16 = BluetoothUuid16::new(0x1854); 328 | 329 | /// Bluetooth service UUID. 330 | /// 331 | /// `0x1855` Telephony and Media Audio 332 | pub const TELEPHONY_AND_MEDIA_AUDIO: BluetoothUuid16 = BluetoothUuid16::new(0x1855); 333 | 334 | /// Bluetooth service UUID. 335 | /// 336 | /// `0x1856` Public Broadcast Announcement 337 | pub const PUBLIC_BROADCAST_ANNOUNCEMENT: BluetoothUuid16 = BluetoothUuid16::new(0x1856); 338 | 339 | /// Bluetooth service UUID. 340 | /// 341 | /// `0x1857` Electronic Shelf Label 342 | pub const ELECTRONIC_SHELF_LABEL: BluetoothUuid16 = BluetoothUuid16::new(0x1857); 343 | 344 | /// Bluetooth service UUID. 345 | /// 346 | /// `0x1858` Gaming Audio 347 | pub const GAMING_AUDIO: BluetoothUuid16 = BluetoothUuid16::new(0x1858); 348 | 349 | /// Bluetooth service UUID. 350 | /// 351 | /// `0x1859` Mesh Proxy Solicitation 352 | pub const MESH_PROXY_SOLICITATION: BluetoothUuid16 = BluetoothUuid16::new(0x1859); 353 | 354 | /// Bluetooth service UUID. 355 | /// 356 | /// `0x185a` Industrial Measurement Device 357 | pub const INDUSTRIAL_MEASUREMENT_DEVICE: BluetoothUuid16 = BluetoothUuid16::new(0x185a); 358 | 359 | /// Bluetooth service UUID. 360 | /// 361 | /// `0x185b` Ranging 362 | pub const RANGING: BluetoothUuid16 = BluetoothUuid16::new(0x185b); 363 | -------------------------------------------------------------------------------- /src/uuid/service_class.rs: -------------------------------------------------------------------------------- 1 | //! UUIDs for the service class module. 2 | 3 | // This file is auto-generated by the update_uuids application. 4 | // Based on https://bitbucket.org/bluetooth-SIG/public.git 5 | // Commit hash: 22c4a7a751fe51de707be6d294adb0d115e86897 6 | 7 | use super::BluetoothUuid16; 8 | 9 | /// Bluetooth service class UUID. 10 | /// 11 | /// `0x1000` ServiceDiscoveryServerServiceClassID 12 | pub const SERVICE_DISCOVERY_SERVER_SERVICE_CLASS_ID: BluetoothUuid16 = BluetoothUuid16::new(0x1000); 13 | 14 | /// Bluetooth service class UUID. 15 | /// 16 | /// `0x1001` BrowseGroupDescriptorServiceClassID 17 | pub const BROWSE_GROUP_DESCRIPTOR_SERVICE_CLASS_ID: BluetoothUuid16 = BluetoothUuid16::new(0x1001); 18 | 19 | /// Bluetooth service class UUID. 20 | /// 21 | /// `0x1101` SerialPort 22 | pub const SERIAL_PORT: BluetoothUuid16 = BluetoothUuid16::new(0x1101); 23 | 24 | /// Bluetooth service class UUID. 25 | /// 26 | /// `0x1102` LANAccessUsingPPP 27 | pub const LAN_ACCESS_USING_PPP: BluetoothUuid16 = BluetoothUuid16::new(0x1102); 28 | 29 | /// Bluetooth service class UUID. 30 | /// 31 | /// `0x1103` Dial-Up Networking 32 | pub const DIAL_UP_NETWORKING: BluetoothUuid16 = BluetoothUuid16::new(0x1103); 33 | 34 | /// Bluetooth service class UUID. 35 | /// 36 | /// `0x1104` IrMCSync 37 | pub const IR_MC_SYNC: BluetoothUuid16 = BluetoothUuid16::new(0x1104); 38 | 39 | /// Bluetooth service class UUID. 40 | /// 41 | /// `0x1105` OBEXObjectPush 42 | pub const OBEX_OBJECT_PUSH: BluetoothUuid16 = BluetoothUuid16::new(0x1105); 43 | 44 | /// Bluetooth service class UUID. 45 | /// 46 | /// `0x1106` OBEX File Transfer 47 | pub const OBEX_FILE_TRANSFER: BluetoothUuid16 = BluetoothUuid16::new(0x1106); 48 | 49 | /// Bluetooth service class UUID. 50 | /// 51 | /// `0x1107` IrMCSyncCommand 52 | pub const IR_MC_SYNC_COMMAND: BluetoothUuid16 = BluetoothUuid16::new(0x1107); 53 | 54 | /// Bluetooth service class UUID. 55 | /// 56 | /// `0x1108` Headset 57 | pub const HEADSET: BluetoothUuid16 = BluetoothUuid16::new(0x1108); 58 | 59 | /// Bluetooth service class UUID. 60 | /// 61 | /// `0x1109` CordlessTelephony 62 | pub const CORDLESS_TELEPHONY: BluetoothUuid16 = BluetoothUuid16::new(0x1109); 63 | 64 | /// Bluetooth service class UUID. 65 | /// 66 | /// `0x110a` Audio Source 67 | pub const AUDIO_SOURCE: BluetoothUuid16 = BluetoothUuid16::new(0x110a); 68 | 69 | /// Bluetooth service class UUID. 70 | /// 71 | /// `0x110b` Audio Sink 72 | pub const AUDIO_SINK: BluetoothUuid16 = BluetoothUuid16::new(0x110b); 73 | 74 | /// Bluetooth service class UUID. 75 | /// 76 | /// `0x110c` A/V Remote Control Target 77 | pub const AV_REMOTE_CONTROL_TARGET: BluetoothUuid16 = BluetoothUuid16::new(0x110c); 78 | 79 | /// Bluetooth service class UUID. 80 | /// 81 | /// `0x110d` Advanced Audio Distribution 82 | pub const ADVANCED_AUDIO_DISTRIBUTION: BluetoothUuid16 = BluetoothUuid16::new(0x110d); 83 | 84 | /// Bluetooth service class UUID. 85 | /// 86 | /// `0x110e` A/V Remote Control 87 | pub const AV_REMOTE_CONTROL: BluetoothUuid16 = BluetoothUuid16::new(0x110e); 88 | 89 | /// Bluetooth service class UUID. 90 | /// 91 | /// `0x110f` A/V Remote Control Controller 92 | pub const AV_REMOTE_CONTROL_CONTROLLER: BluetoothUuid16 = BluetoothUuid16::new(0x110f); 93 | 94 | /// Bluetooth service class UUID. 95 | /// 96 | /// `0x1110` Intercom 97 | pub const INTERCOM: BluetoothUuid16 = BluetoothUuid16::new(0x1110); 98 | 99 | /// Bluetooth service class UUID. 100 | /// 101 | /// `0x1111` Fax 102 | pub const FAX: BluetoothUuid16 = BluetoothUuid16::new(0x1111); 103 | 104 | /// Bluetooth service class UUID. 105 | /// 106 | /// `0x1112` Headset Audio Gateway 107 | pub const HEADSET_AUDIO_GATEWAY: BluetoothUuid16 = BluetoothUuid16::new(0x1112); 108 | 109 | /// Bluetooth service class UUID. 110 | /// 111 | /// `0x1113` WAP 112 | pub const WAP: BluetoothUuid16 = BluetoothUuid16::new(0x1113); 113 | 114 | /// Bluetooth service class UUID. 115 | /// 116 | /// `0x1114` WAP_CLIENT 117 | pub const WAP_CLIENT: BluetoothUuid16 = BluetoothUuid16::new(0x1114); 118 | 119 | /// Bluetooth service class UUID. 120 | /// 121 | /// `0x1115` PANU 122 | pub const PANU: BluetoothUuid16 = BluetoothUuid16::new(0x1115); 123 | 124 | /// Bluetooth service class UUID. 125 | /// 126 | /// `0x1116` NAP 127 | pub const NAP: BluetoothUuid16 = BluetoothUuid16::new(0x1116); 128 | 129 | /// Bluetooth service class UUID. 130 | /// 131 | /// `0x1117` GN 132 | pub const GN: BluetoothUuid16 = BluetoothUuid16::new(0x1117); 133 | 134 | /// Bluetooth service class UUID. 135 | /// 136 | /// `0x1118` DirectPrinting 137 | pub const DIRECT_PRINTING: BluetoothUuid16 = BluetoothUuid16::new(0x1118); 138 | 139 | /// Bluetooth service class UUID. 140 | /// 141 | /// `0x1119` ReferencePrinting 142 | pub const REFERENCE_PRINTING: BluetoothUuid16 = BluetoothUuid16::new(0x1119); 143 | 144 | /// Bluetooth service class UUID. 145 | /// 146 | /// `0x111a` Imaging 147 | pub const IMAGING: BluetoothUuid16 = BluetoothUuid16::new(0x111a); 148 | 149 | /// Bluetooth service class UUID. 150 | /// 151 | /// `0x111b` Imaging Responder 152 | pub const IMAGING_RESPONDER: BluetoothUuid16 = BluetoothUuid16::new(0x111b); 153 | 154 | /// Bluetooth service class UUID. 155 | /// 156 | /// `0x111c` Imaging Automatic Archive 157 | pub const IMAGING_AUTOMATIC_ARCHIVE: BluetoothUuid16 = BluetoothUuid16::new(0x111c); 158 | 159 | /// Bluetooth service class UUID. 160 | /// 161 | /// `0x111d` Imaging Referenced Objects 162 | pub const IMAGING_REFERENCED_OBJECTS: BluetoothUuid16 = BluetoothUuid16::new(0x111d); 163 | 164 | /// Bluetooth service class UUID. 165 | /// 166 | /// `0x111e` Hands-Free 167 | pub const HANDS_FREE: BluetoothUuid16 = BluetoothUuid16::new(0x111e); 168 | 169 | /// Bluetooth service class UUID. 170 | /// 171 | /// `0x111f` AG Hands-Free 172 | pub const AG_HANDS_FREE: BluetoothUuid16 = BluetoothUuid16::new(0x111f); 173 | 174 | /// Bluetooth service class UUID. 175 | /// 176 | /// `0x1120` DirectPrintingReferencedObjectsService 177 | pub const DIRECT_PRINTING_REFERENCED_OBJECTS_SERVICE: BluetoothUuid16 = BluetoothUuid16::new(0x1120); 178 | 179 | /// Bluetooth service class UUID. 180 | /// 181 | /// `0x1121` ReflectedUI 182 | pub const REFLECTED_UI: BluetoothUuid16 = BluetoothUuid16::new(0x1121); 183 | 184 | /// Bluetooth service class UUID. 185 | /// 186 | /// `0x1122` BasicPrinting 187 | pub const BASIC_PRINTING: BluetoothUuid16 = BluetoothUuid16::new(0x1122); 188 | 189 | /// Bluetooth service class UUID. 190 | /// 191 | /// `0x1123` PrintingStatus 192 | pub const PRINTING_STATUS: BluetoothUuid16 = BluetoothUuid16::new(0x1123); 193 | 194 | /// Bluetooth service class UUID. 195 | /// 196 | /// `0x1124` HID 197 | pub const HID: BluetoothUuid16 = BluetoothUuid16::new(0x1124); 198 | 199 | /// Bluetooth service class UUID. 200 | /// 201 | /// `0x1125` HardcopyCableReplacement 202 | pub const HARDCOPY_CABLE_REPLACEMENT: BluetoothUuid16 = BluetoothUuid16::new(0x1125); 203 | 204 | /// Bluetooth service class UUID. 205 | /// 206 | /// `0x1126` HCR_Print 207 | pub const HCR_PRINT: BluetoothUuid16 = BluetoothUuid16::new(0x1126); 208 | 209 | /// Bluetooth service class UUID. 210 | /// 211 | /// `0x1127` HCR_Scan 212 | pub const HCR_SCAN: BluetoothUuid16 = BluetoothUuid16::new(0x1127); 213 | 214 | /// Bluetooth service class UUID. 215 | /// 216 | /// `0x1128` Common_ISDN_Access 217 | pub const COMMON_ISDN_ACCESS: BluetoothUuid16 = BluetoothUuid16::new(0x1128); 218 | 219 | /// Bluetooth service class UUID. 220 | /// 221 | /// `0x112d` SIM Access 222 | pub const SIM_ACCESS: BluetoothUuid16 = BluetoothUuid16::new(0x112d); 223 | 224 | /// Bluetooth service class UUID. 225 | /// 226 | /// `0x112e` Phonebook Access Client 227 | pub const PHONEBOOK_ACCESS_CLIENT: BluetoothUuid16 = BluetoothUuid16::new(0x112e); 228 | 229 | /// Bluetooth service class UUID. 230 | /// 231 | /// `0x112f` Phonebook Access Server 232 | pub const PHONEBOOK_ACCESS_SERVER: BluetoothUuid16 = BluetoothUuid16::new(0x112f); 233 | 234 | /// Bluetooth service class UUID. 235 | /// 236 | /// `0x1130` Phonebook Access Profile 237 | pub const PHONEBOOK_ACCESS_PROFILE: BluetoothUuid16 = BluetoothUuid16::new(0x1130); 238 | 239 | /// Bluetooth service class UUID. 240 | /// 241 | /// `0x1131` Headset - HS 242 | pub const HEADSET_HS: BluetoothUuid16 = BluetoothUuid16::new(0x1131); 243 | 244 | /// Bluetooth service class UUID. 245 | /// 246 | /// `0x1132` Message Access Server 247 | pub const MESSAGE_ACCESS_SERVER: BluetoothUuid16 = BluetoothUuid16::new(0x1132); 248 | 249 | /// Bluetooth service class UUID. 250 | /// 251 | /// `0x1133` Message Notification Server 252 | pub const MESSAGE_NOTIFICATION_SERVER: BluetoothUuid16 = BluetoothUuid16::new(0x1133); 253 | 254 | /// Bluetooth service class UUID. 255 | /// 256 | /// `0x1134` Message Access Profile 257 | pub const MESSAGE_ACCESS_PROFILE: BluetoothUuid16 = BluetoothUuid16::new(0x1134); 258 | 259 | /// Bluetooth service class UUID. 260 | /// 261 | /// `0x1135` GNSS 262 | pub const GNSS: BluetoothUuid16 = BluetoothUuid16::new(0x1135); 263 | 264 | /// Bluetooth service class UUID. 265 | /// 266 | /// `0x1136` GNSS_Server 267 | pub const GNSS_SERVER: BluetoothUuid16 = BluetoothUuid16::new(0x1136); 268 | 269 | /// Bluetooth service class UUID. 270 | /// 271 | /// `0x1137` 3D Display 272 | pub const _3D_DISPLAY: BluetoothUuid16 = BluetoothUuid16::new(0x1137); 273 | 274 | /// Bluetooth service class UUID. 275 | /// 276 | /// `0x1138` 3D Glasses 277 | pub const _3D_GLASSES: BluetoothUuid16 = BluetoothUuid16::new(0x1138); 278 | 279 | /// Bluetooth service class UUID. 280 | /// 281 | /// `0x1139` 3D Synch Profile 282 | pub const _3D_SYNCH_PROFILE: BluetoothUuid16 = BluetoothUuid16::new(0x1139); 283 | 284 | /// Bluetooth service class UUID. 285 | /// 286 | /// `0x113a` Multi Profile Specification 287 | pub const MULTI_PROFILE_SPECIFICATION: BluetoothUuid16 = BluetoothUuid16::new(0x113a); 288 | 289 | /// Bluetooth service class UUID. 290 | /// 291 | /// `0x113b` MPS 292 | pub const MPS: BluetoothUuid16 = BluetoothUuid16::new(0x113b); 293 | 294 | /// Bluetooth service class UUID. 295 | /// 296 | /// `0x113c` CTN Access Service 297 | pub const CTN_ACCESS_SERVICE: BluetoothUuid16 = BluetoothUuid16::new(0x113c); 298 | 299 | /// Bluetooth service class UUID. 300 | /// 301 | /// `0x113d` CTN Notification Service 302 | pub const CTN_NOTIFICATION_SERVICE: BluetoothUuid16 = BluetoothUuid16::new(0x113d); 303 | 304 | /// Bluetooth service class UUID. 305 | /// 306 | /// `0x113e` Calendar Tasks and Notes Profile 307 | pub const CALENDAR_TASKS_AND_NOTES_PROFILE: BluetoothUuid16 = BluetoothUuid16::new(0x113e); 308 | 309 | /// Bluetooth service class UUID. 310 | /// 311 | /// `0x1200` PnPInformation 312 | pub const PN_P_INFORMATION: BluetoothUuid16 = BluetoothUuid16::new(0x1200); 313 | 314 | /// Bluetooth service class UUID. 315 | /// 316 | /// `0x1201` Generic Networking 317 | pub const GENERIC_NETWORKING: BluetoothUuid16 = BluetoothUuid16::new(0x1201); 318 | 319 | /// Bluetooth service class UUID. 320 | /// 321 | /// `0x1202` GenericFileTransfer 322 | pub const GENERIC_FILE_TRANSFER: BluetoothUuid16 = BluetoothUuid16::new(0x1202); 323 | 324 | /// Bluetooth service class UUID. 325 | /// 326 | /// `0x1203` Generic Audio 327 | pub const GENERIC_AUDIO: BluetoothUuid16 = BluetoothUuid16::new(0x1203); 328 | 329 | /// Bluetooth service class UUID. 330 | /// 331 | /// `0x1204` GenericTelephony 332 | pub const GENERIC_TELEPHONY: BluetoothUuid16 = BluetoothUuid16::new(0x1204); 333 | 334 | /// Bluetooth service class UUID. 335 | /// 336 | /// `0x1205` UPNP_Service 337 | pub const UPNP_SERVICE: BluetoothUuid16 = BluetoothUuid16::new(0x1205); 338 | 339 | /// Bluetooth service class UUID. 340 | /// 341 | /// `0x1206` UPNP_IP_Service 342 | pub const UPNP_IP_SERVICE: BluetoothUuid16 = BluetoothUuid16::new(0x1206); 343 | 344 | /// Bluetooth service class UUID. 345 | /// 346 | /// `0x1300` ESDP_UPNP_IP_PAN 347 | pub const ESDP_UPNP_IP_PAN: BluetoothUuid16 = BluetoothUuid16::new(0x1300); 348 | 349 | /// Bluetooth service class UUID. 350 | /// 351 | /// `0x1301` ESDP_UPNP_IP_LAP 352 | pub const ESDP_UPNP_IP_LAP: BluetoothUuid16 = BluetoothUuid16::new(0x1301); 353 | 354 | /// Bluetooth service class UUID. 355 | /// 356 | /// `0x1302` ESDP_UPNP_L2CAP 357 | pub const ESDP_UPNP_L2_CAP: BluetoothUuid16 = BluetoothUuid16::new(0x1302); 358 | 359 | /// Bluetooth service class UUID. 360 | /// 361 | /// `0x1303` Video Source 362 | pub const VIDEO_SOURCE: BluetoothUuid16 = BluetoothUuid16::new(0x1303); 363 | 364 | /// Bluetooth service class UUID. 365 | /// 366 | /// `0x1304` Video Sink 367 | pub const VIDEO_SINK: BluetoothUuid16 = BluetoothUuid16::new(0x1304); 368 | 369 | /// Bluetooth service class UUID. 370 | /// 371 | /// `0x1305` Video Distribution 372 | pub const VIDEO_DISTRIBUTION: BluetoothUuid16 = BluetoothUuid16::new(0x1305); 373 | 374 | /// Bluetooth service class UUID. 375 | /// 376 | /// `0x1400` HDP 377 | pub const HDP: BluetoothUuid16 = BluetoothUuid16::new(0x1400); 378 | 379 | /// Bluetooth service class UUID. 380 | /// 381 | /// `0x1401` HDP Source 382 | pub const HDP_SOURCE: BluetoothUuid16 = BluetoothUuid16::new(0x1401); 383 | 384 | /// Bluetooth service class UUID. 385 | /// 386 | /// `0x1402` HDP Sink 387 | pub const HDP_SINK: BluetoothUuid16 = BluetoothUuid16::new(0x1402); 388 | -------------------------------------------------------------------------------- /update_uuids/.gitignore: -------------------------------------------------------------------------------- 1 | /bluetooth-sig -------------------------------------------------------------------------------- /update_uuids/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "update_uuids" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | git2 = "0.20.2" 10 | serde = { version = "1.0.214", features = ["derive"] } 11 | serde_yaml = "0.9.34" 12 | walkdir = "2.5.0" 13 | -------------------------------------------------------------------------------- /update_uuids/src/gss.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | use std::collections::HashMap; 4 | 5 | use serde::{Deserialize, Deserializer}; 6 | 7 | #[derive(Debug, Deserialize)] 8 | pub struct GssCharacteristic { 9 | pub characteristic: GattSpecSupplement, 10 | } 11 | 12 | #[derive(Debug, Deserialize, Clone)] 13 | /// The official identifier, e.g., "org.bluetooth.characteristic.activity_goal" 14 | pub struct GattSpecSupplement { 15 | pub identifier: String, // Primary Key 16 | pub name: String, 17 | pub description: String, 18 | #[serde(deserialize_with = "deserialize_structure_list_to_map")] 19 | pub structure: Vec, 20 | /// The 'fields' key in the YAML provides detailed descriptions for certain 21 | /// fields defined in 'structure', often for flags. 22 | #[serde(default)] // Use default if 'fields' is not present 23 | pub fields: Vec, 24 | } 25 | 26 | #[derive(Debug, Deserialize, Clone)] 27 | pub struct GattSpecStructure { 28 | field: String, 29 | #[serde(rename = "type")] 30 | pub ty: String, 31 | pub size: String, 32 | pub description: String, 33 | pub unit: Option, 34 | } 35 | 36 | /// Custom deserializer function to convert the YAML 'structure' list 37 | /// into a HashMap. 38 | fn deserialize_structure_list_to_map<'de, D>(deserializer: D) -> Result, D::Error> 39 | where 40 | D: Deserializer<'de>, 41 | { 42 | let list_items: Vec = Vec::deserialize(deserializer)?; 43 | Ok(list_items) 44 | } 45 | 46 | /// Represents the detailed information for a field, often used for flags. 47 | /// Corresponds to items in the 'fields' list in the YAML. 48 | #[derive(Debug, Deserialize, Default, Clone)] 49 | pub struct FieldInformation { 50 | pub name: String, // Name of the field being described (e.g., "Flags", "Presence Flags") 51 | pub description: String, 52 | pub section_title: Option, 53 | pub table_caption: Option, 54 | #[serde(default)] 55 | pub values: Vec, 56 | } 57 | 58 | #[derive(Debug, Deserialize, Clone)] 59 | #[serde(untagged)] 60 | pub enum FieldValueDefinition { 61 | Bit { 62 | bit: String, 63 | description: String, 64 | }, 65 | Value { 66 | value: String, 67 | description: String, 68 | }, 69 | Field { 70 | field: String, 71 | data_type: String, 72 | size: String, 73 | description: String, 74 | }, 75 | OpCodeParameter { 76 | op_code_value: String, 77 | definition: String, 78 | parameter: String, 79 | parameter_type: String, 80 | description: String, 81 | }, 82 | OpCodeOperand { 83 | op_code_value: String, 84 | definition: String, 85 | operand: String, 86 | operator: Option, 87 | operand_data_type: Option, 88 | description: String, 89 | }, 90 | ResponseCode { 91 | response_code_value: String, 92 | definition: String, 93 | response_parameter: Option, 94 | description: String, 95 | }, 96 | } 97 | 98 | impl GattSpecSupplement { 99 | pub fn print_docstring(&self) -> String { 100 | let description: String = self 101 | .description 102 | .lines() 103 | .map(|line| format!("/// {}", line.replace("\\autoref{", "`").replace("}", "`").trim_end())) 104 | .filter(|line| !line.contains("The structure of this characteristic is defined below.")) 105 | .collect::>() 106 | .join("\n"); 107 | let structure: String = self.structure.iter().fold(String::new(), |mut acc, v| { 108 | let field_string: String = format!( 109 | "/// 110 | /// ### Data Type 111 | /// 112 | /// | | | 113 | /// |---|---| 114 | /// | **Field** | {} | 115 | /// | **Type** | {} | 116 | /// | **Size** | {} | 117 | /// 118 | /// ### Description 119 | /// 120 | {} 121 | /// 122 | /// ----", 123 | v.field, 124 | v.ty.replace("[", "").replace("]", ""), 125 | v.size.replace("\n", " - "), 126 | v.description 127 | .lines() 128 | .map(|line| { format!("/// {}", line.replace("\\autoref{", "`").replace("}", "`").trim_end()) }) 129 | .collect::>() 130 | .join("\n") 131 | ); 132 | acc.push_str(&field_string); 133 | acc 134 | }); 135 | format!( 136 | " 137 | /// 138 | {} 139 | /// 140 | /// ---- 141 | /// ## Structure 142 | {} 143 | /// 144 | /// [more information](https://bitbucket.org/bluetooth-SIG/public/src/main/gss/{}.yaml)", 145 | description, structure, self.identifier 146 | ) 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /update_uuids/src/main.rs: -------------------------------------------------------------------------------- 1 | //! This Application pulls the latest assigned numbers from 2 | //! the Bluetooth SIG and updates the assigned numbers constants. 3 | //! 4 | //! The assigned numbers are used in the Bluetooth specification 5 | //! to define the UUIDs for services, characteristics, and other 6 | //! Bluetooth related values. 7 | 8 | mod gss; 9 | mod utils; 10 | mod writer; 11 | mod yaml; 12 | 13 | use std::collections::HashMap; 14 | use std::error::Error; 15 | use std::path::Path; 16 | 17 | use git2::Repository; 18 | use gss::GattSpecSupplement; 19 | use yaml::load_gss; 20 | 21 | fn main() -> Result<(), Box> { 22 | // Download the latest assigned numbers from the Bluetooth SIG 23 | const SIG_URL: &str = "https://bitbucket.org/bluetooth-SIG/public.git"; 24 | println!("Downloading the latest assigned numbers from the Bluetooth SIG..."); 25 | 26 | let local_folder = Path::new("bluetooth-sig"); 27 | let output_folder = Path::new("../src/uuid"); 28 | let commit_hash = fetch_repo(SIG_URL, local_folder)?; 29 | 30 | // load the Gatt Spec Supplement information 31 | let gss = load_gss(&local_folder.join("gss"))?; 32 | 33 | write_uuids(local_folder, output_folder, &commit_hash, gss)?; // assigned_numbers/uuids 34 | 35 | write_appearance(local_folder, output_folder, &commit_hash)?; // assigned_numbers/core/appearance 36 | 37 | Ok(()) 38 | } 39 | 40 | /// Parse and write the UUIDs to the source code 41 | /// The UUIDs are loaded from the YAML files in the assigned_numbers/uuids folder 42 | fn write_uuids( 43 | local_folder: &Path, 44 | output_folder: &Path, 45 | commit_hash: &str, 46 | gss: HashMap, 47 | ) -> Result<(), Box> { 48 | // Load the YAML data from ./bluetooth-sig/assigned_numbers/uuids* 49 | let path = local_folder.join("assigned_numbers").join("uuids"); 50 | let mut uuid_map = yaml::load_uuid_data(&path)?; 51 | 52 | // Add the Gatt Spec Supplement information to the UUIDs 53 | uuid_map.iter_mut().for_each(|(_uuid_group, uuids)| { 54 | uuids.iter_mut().for_each(|uuid| { 55 | if let Some(id) = &uuid.id { 56 | if let Some(supplement) = gss.get(id) { 57 | uuid.gss = Some(supplement.clone()); 58 | } 59 | } 60 | }); 61 | }); 62 | 63 | // Update the assigned numbers in the source code 64 | writer::update_uuids(output_folder, uuid_map, commit_hash)?; 65 | Ok(()) 66 | } 67 | 68 | /// Parse and write the Appearance values to the source code 69 | /// The Appearance values are loaded from the YAML files in the assigned_numbers/core/appearance folder 70 | fn write_appearance(local_folder: &Path, output_folder: &Path, commit_hash: &str) -> Result<(), Box> { 71 | // Load the YAML data from ./bluetooth-sig/assigned_numbers/core/appearance_values.yaml 72 | let file_name = "appearance_values.yaml"; 73 | let path = local_folder.join("assigned_numbers").join("core").join(file_name); 74 | let appearance_data = yaml::load_appearance_data(&path)?; 75 | 76 | // Update the appearance values in the source code 77 | writer::update_appearance(output_folder, &appearance_data, commit_hash)?; 78 | Ok(()) 79 | } 80 | 81 | fn fetch_repo(repo_url: &str, local_path: &Path) -> Result> { 82 | // Fetch the repository from the given URL 83 | if local_path.exists() { 84 | // Pull the latest changes 85 | let repo = Repository::open(local_path)?; 86 | let mut remote = repo.find_remote("origin")?; 87 | remote.fetch(&["main"], None, None)?; 88 | let obj = repo.revparse_single("main")?; 89 | let latest_commit_hash = obj.id(); 90 | Ok(latest_commit_hash.to_string()) 91 | } else { 92 | // Clone the repository 93 | let repo = Repository::clone(repo_url, local_path)?; 94 | let obj = repo.revparse_single("main")?; 95 | let latest_commit_hash = obj.id(); 96 | Ok(latest_commit_hash.to_string()) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /update_uuids/src/utils.rs: -------------------------------------------------------------------------------- 1 | /// Convert from "PascalCase" or "snake_case" or "IE mixed case" to "SCREAMING_SNAKE_CASE". 2 | pub fn screaming_snake_case(s: &str) -> String { 3 | let mut result = String::new(); 4 | let mut s = s.to_string(); 5 | if s.contains(' ') { 6 | s = s.to_lowercase().replace(' ', "_"); 7 | } 8 | // if whole string is uppercase, return it as is 9 | if s.chars().all(|c| c.is_ascii_uppercase()) { 10 | return s; 11 | } 12 | let mut chars = s.chars(); 13 | if let Some(first) = chars.next() { 14 | if first.is_numeric() { 15 | result.push('_'); // insert underscore before number 16 | result.push(first); 17 | } else { 18 | // otherwise deal with first letter by itself 19 | result.push(first.to_ascii_uppercase()); 20 | } 21 | } 22 | let mut prev: Option = None; 23 | for (index, c) in chars.enumerate() { 24 | if c.is_ascii_uppercase() { 25 | // handle existing uppercase characters that might be part of an acronym 26 | // as well as PascalCase 27 | // if previous character was not uppercase, insert underscore 28 | if let Some(prev) = prev { 29 | if !prev.is_ascii_uppercase() { 30 | result.push('_'); 31 | } else if let Some(next) = s.chars().nth(index + 2) { 32 | // if next character is lowercase, insert underscore 33 | if next.is_ascii_lowercase() { 34 | result.push('_'); 35 | } 36 | } 37 | } 38 | } else if c.is_whitespace() { 39 | result.push('_'); // replace whitespace with underscore 40 | } else if c == '-' { 41 | result.push('_'); // replace hyphen with underscore 42 | } else if c == '_' { 43 | result.push('_'); // keep existing underscore 44 | } 45 | if c.is_alphanumeric() { 46 | result.push(c.to_ascii_uppercase()); 47 | } // ignore other characters 48 | prev = Some(c); 49 | } 50 | result = result // special cases 51 | .replace("TEXTSUBSCRIPT", "") 52 | .replace("___", "_") 53 | .replace("__", "_") 54 | .replace('å', "A") 55 | .replace('ö', "O"); 56 | result 57 | } 58 | 59 | #[cfg(test)] 60 | mod test { 61 | use super::*; 62 | 63 | #[test] 64 | fn test_screaming_snake_case() { 65 | assert_eq!(screaming_snake_case("PascalCase"), "PASCAL_CASE"); 66 | assert_eq!(screaming_snake_case("snake_case"), "SNAKE_CASE"); 67 | assert_eq!(screaming_snake_case("DST Offset"), "DST_OFFSET"); 68 | assert_eq!(screaming_snake_case("Headset - HS"), "HEADSET_HS"); 69 | assert_eq!(screaming_snake_case("GAP"), "GAP"); 70 | assert_eq!(screaming_snake_case("length (ångström)"), "LENGTH_ANGSTROM"); 71 | assert_eq!(screaming_snake_case("LANAccessUsingPPP"), "LAN_ACCESS_USING_PPP"); 72 | assert_eq!(screaming_snake_case("pressure (pound-force)"), "PRESSURE_POUND_FORCE"); 73 | assert_eq!(screaming_snake_case("CIE 13.3-1995 Color"), "CIE_133_1995_COLOR"); 74 | assert_eq!(screaming_snake_case("CO\\\\textsubscript{2} Conc"), "CO2_CONC"); 75 | assert_eq!(screaming_snake_case("3D Display"), "_3D_DISPLAY"); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /update_uuids/src/writer.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::error::Error; 3 | use std::fs::File; 4 | use std::io::Write; 5 | use std::path::{Path, PathBuf}; 6 | 7 | use crate::utils::screaming_snake_case; 8 | use crate::yaml::{Category, UuidData}; 9 | 10 | /// Update the UUIDs in the source code 11 | pub fn update_uuids( 12 | output_folder: &Path, 13 | mut input: HashMap>, 14 | commit_hash: &str, 15 | ) -> Result<(), Box> { 16 | // each key in the map is a module name 17 | // each value is a list of UUIDs, which will be written as constants 18 | // in the form `pub const UUID_NAME: BluetoothUuid16 = BluetoothUuid16::new(uuid);` 19 | for (file_name, uuids) in input.iter_mut() { 20 | let output_name = file_name.replace("_uuids", "").replace(".yaml", ""); 21 | if output_name == "member" || output_name == "sdo" { 22 | continue; // skip the member and sdo modules 23 | } 24 | let (module_name, mut file) = setup_rust_file(&output_name, output_folder.to_path_buf())?; 25 | 26 | let constants: Vec<_> = uuids 27 | .iter() 28 | .map(|uuid| { 29 | let supplement = match &uuid.gss { 30 | Some(gss) => gss.print_docstring(), 31 | None => "".to_string(), 32 | }; 33 | format!( 34 | "/// Bluetooth {} UUID. 35 | /// 36 | /// `0x{:04x}` {}{} 37 | pub const {}: BluetoothUuid16 = BluetoothUuid16::new(0x{:x});", 38 | module_name, 39 | uuid.uuid, 40 | uuid.name, 41 | supplement, 42 | screaming_snake_case(&uuid.name), 43 | uuid.uuid, 44 | ) 45 | }) 46 | .collect(); 47 | let tokens = constants.join("\n\n"); 48 | 49 | write_rust_file(&mut file, &module_name, tokens, commit_hash)?; 50 | } 51 | Ok(()) 52 | } 53 | 54 | /// Update the Appearance values in the source code 55 | /// 56 | /// Subcategories are dealt with as submodules. 57 | pub fn update_appearance(output_folder: &Path, input: &[Category], commit_hash: &str) -> Result<(), Box> { 58 | let output_folder = output_folder.join("appearance"); 59 | let (module_name, mut file) = setup_rust_file("categories", output_folder)?; 60 | let mut tokens = String::new(); 61 | let modules: Vec<_> = input 62 | .iter() 63 | .map(|cat| { 64 | let module_name = cat.name.replace(' ', "_").to_lowercase(); 65 | if cat.subcategory.is_none() { 66 | format!( 67 | "/// Bluetooth Appearance UUID. 68 | /// 69 | /// `0x{:04x}` Generic {} 70 | pub const {}: BluetoothUuid16 = super::from_category(0x{:04x}, 0x{:04x});", 71 | appearance(cat.category, 0x000), 72 | cat.name, 73 | screaming_snake_case(&cat.name), 74 | cat.category, 75 | 0x000 // generic subcategory 76 | ) 77 | } else { 78 | format!( 79 | "pub mod {} {{ 80 | //! Appearance {} with subcategories. 81 | //! 82 | //! Generic variant named `GENERIC_{}`.\n 83 | use super::super::{{from_category, BluetoothUuid16}};\n 84 | {} 85 | }}", 86 | module_name, 87 | module_name, 88 | screaming_snake_case(&cat.name), 89 | appearance_subcategory(cat) 90 | ) 91 | } 92 | }) 93 | .collect(); 94 | tokens.push_str(&modules.join("\n\n")); 95 | write_rust_file(&mut file, &module_name, tokens, commit_hash)?; 96 | Ok(()) 97 | } 98 | 99 | /// If the category has a subcategory, create a submodule for it 100 | fn appearance_subcategory(cat: &Category) -> String { 101 | let mut constants: Vec<_> = Vec::new(); 102 | // add generic subcategory first 103 | constants.push(format!( 104 | "/// Bluetooth Appearance UUID. 105 | /// 106 | /// `0x{:04x}` Generic {} 107 | pub const GENERIC_{}: BluetoothUuid16 = from_category(0x{:04x}, 0x{:04x});", 108 | appearance(cat.category, 0x000), 109 | cat.name, 110 | screaming_snake_case(&cat.name), 111 | cat.category, 112 | 0x000 // generic subcategory 113 | )); 114 | if let Some(subcats) = &cat.subcategory { 115 | for subcat in subcats { 116 | constants.push(format!( 117 | " /// Bluetooth Appearance UUID. 118 | /// 119 | /// `0x{:04x}` {} | {} 120 | pub const {}: BluetoothUuid16 = from_category(0x{:04x}, 0x{:04x});", 121 | appearance(cat.category, subcat.value), 122 | cat.name, 123 | subcat.name, 124 | screaming_snake_case(&subcat.name), 125 | cat.category, 126 | subcat.value 127 | )); 128 | } 129 | } 130 | constants.join("\n\n") 131 | } 132 | 133 | fn setup_rust_file(output_name: &str, output_folder: PathBuf) -> Result<(String, File), Box> { 134 | let file_path = output_folder.join(format!("{output_name}.rs")); 135 | let module_name = output_name.replace('_', " "); 136 | // clear the file if it exists 137 | if file_path.exists() { 138 | std::fs::remove_file(&file_path)?; 139 | } 140 | let file = File::create(file_path)?; 141 | Ok((module_name, file)) 142 | } 143 | 144 | fn write_rust_file(file: &mut File, name: &str, tokens: String, commit_hash: &str) -> Result<(), Box> { 145 | // construct the header docstrings 146 | writeln!(file, "//! UUIDs for the {} module.\n", name)?; 147 | writeln!(file, "// This file is auto-generated by the update_uuids application.")?; 148 | writeln!(file, "// Based on https://bitbucket.org/bluetooth-SIG/public.git")?; 149 | writeln!(file, "// Commit hash: {}\n", commit_hash,)?; 150 | 151 | writeln!(file, "use super::BluetoothUuid16;\n")?; 152 | 153 | write!(file, "{}", tokens)?; // write the file contents 154 | write!(file, "\n")?; // add a newline at the end 155 | Ok(()) 156 | } 157 | 158 | fn appearance(cat: u8, subcat: u8) -> u16 { 159 | ((cat as u16) << 6) | (subcat as u16) 160 | } 161 | -------------------------------------------------------------------------------- /update_uuids/src/yaml.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::error::Error; 3 | use std::path::{Path, PathBuf}; 4 | 5 | use serde::Deserialize; 6 | use walkdir::WalkDir; 7 | 8 | use crate::gss::{GattSpecSupplement, GssCharacteristic}; 9 | 10 | // Data structure for the UUIDs 11 | #[derive(Debug, Deserialize)] 12 | pub struct UuidData { 13 | /// short UUID for this predefined value 14 | pub uuid: u16, 15 | /// human readable name associated to this UUID 16 | pub name: String, 17 | /// reference id such as: org.bluetooth.characteristic.acceleration 18 | pub id: Option, 19 | /// Additional information about this Uuid 20 | pub gss: Option, 21 | } 22 | 23 | #[derive(Debug, Deserialize)] 24 | struct Uuids { 25 | uuids: Vec, 26 | } 27 | 28 | /// Load UUID data from a directory of YAML files. 29 | /// matches files in the bluetooth-sig/assigned_numbers/uuids folder. 30 | pub fn load_uuid_data(path: &PathBuf) -> Result>, Box> { 31 | let mut map = HashMap::new(); 32 | for entry in WalkDir::new(path) { 33 | let entry = entry?; 34 | let path = entry.path(); 35 | if path.extension().is_some_and(|ext| ext == "yaml") { 36 | let file_name = get_file_name(path).expect("Filename should exist"); 37 | let data = std::fs::read_to_string(path)?; 38 | let uuid_data: Uuids = serde_yaml::from_str(&data)?; 39 | map.insert(file_name, uuid_data.uuids); 40 | }; 41 | } 42 | Ok(map) 43 | } 44 | 45 | #[derive(Debug, Deserialize)] 46 | struct AppearanceValues { 47 | appearance_values: Vec, 48 | } 49 | 50 | #[derive(Debug, Deserialize)] 51 | pub struct Category { 52 | pub category: u8, 53 | pub name: String, 54 | pub subcategory: Option>, 55 | } 56 | 57 | #[derive(Debug, Deserialize)] 58 | pub struct Subcategory { 59 | pub value: u8, 60 | pub name: String, 61 | } 62 | 63 | /// Load UUID data from the appearance folder 64 | /// This has a different structure than the UUIDs folder 65 | pub fn load_appearance_data(path: &PathBuf) -> Result, Box> { 66 | if path.file_name() != Some("appearance_values.yaml".as_ref()) { 67 | return Err("Invalid file name, must be appearance_values.yaml".into()); 68 | } 69 | let data = std::fs::read_to_string(path)?; 70 | let parsed_data: AppearanceValues = serde_yaml::from_str(&data)?; 71 | Ok(parsed_data.appearance_values) 72 | } 73 | 74 | fn get_file_name(path: &Path) -> Option { 75 | path.file_name()?.to_str().map(|s| s.to_string()) 76 | } 77 | 78 | /// Load the Gatt Specification Supplement information to combine with characteristic data. 79 | pub fn load_gss(path: &PathBuf) -> Result, Box> { 80 | let mut map = HashMap::new(); 81 | if !path.is_dir() { 82 | return Err("Path must be a directory to load gss files".into()); 83 | } 84 | for file in WalkDir::new(path) { 85 | let file = file?; 86 | let path = file.path(); 87 | if path.extension().is_some_and(|ext| ext == "yaml") { 88 | let file_name = get_file_name(path).expect("Filename should exist"); 89 | if file_name.starts_with("org.bluetooth.characteristic.") && file_name.ends_with(".yaml") { 90 | let data = std::fs::read_to_string(path)?; 91 | match serde_yaml::from_str(&data) { 92 | Ok(GssCharacteristic { 93 | characteristic: gss_data, 94 | }) => map.insert(gss_data.identifier.clone(), gss_data), 95 | Err(e) => panic!("error: {e} parsing file: {path:?} \n{data}"), 96 | }; 97 | } 98 | } 99 | } 100 | Ok(map) 101 | } 102 | --------------------------------------------------------------------------------