├── .devcontainer └── devcontainer.json ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benches └── bench │ ├── data.rs │ ├── data_1048576.bin │ ├── data_1048576.hex │ ├── data_131072.bin │ ├── data_131072.hex │ ├── data_16384.bin │ ├── data_16384.hex │ ├── data_2048.bin │ ├── data_2048.hex │ ├── data_256.bin │ ├── data_256.hex │ ├── data_32.bin │ ├── data_32.hex │ ├── gen_data.py │ └── main.rs ├── clippy.toml ├── fuzz ├── .gitignore ├── Cargo.toml └── fuzz_targets │ └── fuzz_const_hex.rs ├── src ├── arch │ ├── aarch64.rs │ ├── generic.rs │ ├── mod.rs │ ├── portable_simd.rs │ ├── wasm32.rs │ └── x86.rs ├── buffer.rs ├── error.rs ├── impl_core.rs ├── lib.rs ├── serde.rs └── traits.rs └── tests └── test.rs /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/rust 3 | { 4 | "name": "Rust", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | "image": "mcr.microsoft.com/devcontainers/rust:1-1-bookworm" 7 | 8 | // Use 'mounts' to make the cargo cache persistent in a Docker Volume. 9 | // "mounts": [ 10 | // { 11 | // "source": "devcontainer-cargo-cache-${devcontainerId}", 12 | // "target": "/usr/local/cargo", 13 | // "type": "volume" 14 | // } 15 | // ] 16 | 17 | // Features to add to the dev container. More info: https://containers.dev/features. 18 | // "features": {}, 19 | 20 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 21 | // "forwardPorts": [], 22 | 23 | // Use 'postCreateCommand' to run commands after the container is created. 24 | // "postCreateCommand": "rustc --version", 25 | 26 | // Configure tool-specific properties. 27 | // "customizations": {}, 28 | 29 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 30 | // "remoteUser": "root" 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | workflow_dispatch: 8 | 9 | permissions: 10 | contents: read 11 | 12 | env: 13 | RUSTFLAGS: -Dwarnings 14 | 15 | jobs: 16 | test: 17 | name: Test +${{ matrix.rust }} ${{ matrix.target.triple }} 18 | runs-on: ubuntu-latest 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | target: 23 | - triple: aarch64-unknown-linux-gnu 24 | feature: neon 25 | - triple: i686-unknown-linux-gnu 26 | feature: avx2 27 | - triple: x86_64-unknown-linux-gnu 28 | feature: avx2 29 | - triple: wasm32-wasip1 30 | feature: simd128 31 | rust: [nightly, stable, 1.64] 32 | exclude: 33 | # https://github.com/rust-lang/rust/issues/131031 34 | - target: 35 | triple: wasm32-wasip1 36 | feature: simd128 37 | rust: nightly 38 | # In 1.64 it's still called wasm32-wasi 39 | - target: 40 | triple: wasm32-wasip1 41 | feature: simd128 42 | rust: 1.64 43 | timeout-minutes: 30 44 | steps: 45 | - uses: actions/checkout@v4 46 | - uses: dtolnay/rust-toolchain@master 47 | with: 48 | toolchain: ${{ matrix.rust }} 49 | target: ${{ matrix.target.triple }} 50 | - uses: taiki-e/setup-cross-toolchain-action@v1 51 | with: 52 | target: ${{ matrix.target.triple }} 53 | 54 | - name: Enable type layout randomization 55 | run: echo RUSTFLAGS=${RUSTFLAGS}\ -Zrandomize-layout >> $GITHUB_ENV 56 | if: matrix.rust == 'nightly' 57 | 58 | - name: Enable target feature 59 | run: | 60 | echo RUSTFLAGS_BAK=$RUSTFLAGS >> $GITHUB_ENV 61 | echo RUSTFLAGS=${RUSTFLAGS}\ -Ctarget-feature=+${{ matrix.target.feature }} >> $GITHUB_ENV 62 | 63 | - uses: Swatinem/rust-cache@v2 64 | 65 | - run: cargo build 66 | - run: cargo build --no-default-features 67 | - run: cargo test 68 | - run: cargo test --no-default-features 69 | - run: cargo test --no-default-features --features force-generic 70 | - run: cargo test --no-default-features --features nightly,portable-simd 71 | if: matrix.rust == 'nightly' 72 | - run: cargo bench --no-run 73 | if: matrix.rust == 'nightly' 74 | 75 | - name: Disable target feature 76 | run: echo RUSTFLAGS=${RUSTFLAGS_BAK}\ -Ctarget-feature=-${{ matrix.target.feature }} >> $GITHUB_ENV 77 | 78 | - run: cargo build 79 | - run: cargo build --no-default-features 80 | - run: cargo test 81 | - run: cargo test --no-default-features 82 | - run: cargo test --no-default-features --features force-generic 83 | - run: cargo test --no-default-features --features nightly,portable-simd 84 | if: matrix.rust == 'nightly' 85 | - run: cargo bench --no-run 86 | if: matrix.rust == 'nightly' 87 | 88 | miri: 89 | name: Miri 90 | runs-on: ubuntu-latest 91 | timeout-minutes: 30 92 | strategy: 93 | fail-fast: false 94 | matrix: 95 | target: [x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu] 96 | flags: ["", -Fforce-generic] 97 | env: 98 | MIRIFLAGS: -Zmiri-strict-provenance 99 | steps: 100 | - uses: actions/checkout@v4 101 | - uses: dtolnay/rust-toolchain@miri 102 | with: 103 | target: ${{ matrix.target }} 104 | - run: cargo miri test --target ${{ matrix.target }} ${{ matrix.flags }} 105 | 106 | fuzz: 107 | name: Fuzz 108 | runs-on: ubuntu-latest 109 | timeout-minutes: 30 110 | steps: 111 | - uses: actions/checkout@v4 112 | - uses: dtolnay/rust-toolchain@nightly 113 | - uses: dtolnay/install@cargo-fuzz 114 | - uses: Swatinem/rust-cache@v2 115 | - run: cargo fuzz check 116 | 117 | feature-checks: 118 | name: feature checks 119 | runs-on: ubuntu-latest 120 | timeout-minutes: 30 121 | steps: 122 | - uses: actions/checkout@v4 123 | - uses: dtolnay/rust-toolchain@nightly 124 | - uses: taiki-e/install-action@cargo-hack 125 | - uses: Swatinem/rust-cache@v2 126 | - name: cargo hack 127 | run: cargo hack check --feature-powerset --depth 2 --all-targets 128 | 129 | clippy: 130 | name: Clippy 131 | runs-on: ubuntu-latest 132 | timeout-minutes: 30 133 | steps: 134 | - uses: actions/checkout@v4 135 | - uses: dtolnay/rust-toolchain@clippy 136 | - uses: Swatinem/rust-cache@v2 137 | - run: cargo clippy --all-targets 138 | 139 | docs: 140 | name: docs 141 | runs-on: ubuntu-latest 142 | timeout-minutes: 30 143 | steps: 144 | - uses: actions/checkout@v4 145 | - uses: dtolnay/rust-toolchain@nightly 146 | with: 147 | components: rust-docs 148 | - uses: Swatinem/rust-cache@v2 149 | - run: cargo doc --workspace --all-features --no-deps --document-private-items 150 | env: 151 | RUSTDOCFLAGS: "--cfg docsrs -D warnings" 152 | 153 | fmt: 154 | name: fmt 155 | runs-on: ubuntu-latest 156 | timeout-minutes: 30 157 | steps: 158 | - uses: actions/checkout@v4 159 | - uses: dtolnay/rust-toolchain@nightly 160 | with: 161 | components: rustfmt 162 | - run: cargo fmt --all --check 163 | 164 | outdated: 165 | name: Outdated 166 | runs-on: ubuntu-latest 167 | if: github.event_name != 'pull_request' 168 | timeout-minutes: 30 169 | steps: 170 | - uses: actions/checkout@v4 171 | - uses: dtolnay/install@cargo-outdated 172 | - run: cargo outdated --workspace --exit-code 1 173 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | 4 | .vscode 5 | .idea 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "const-hex" 3 | version = "1.14.1" 4 | authors = ["DaniPopes <57450786+DaniPopes@users.noreply.github.com>"] 5 | description = "Fast byte array to hex string conversion" 6 | edition = "2021" 7 | rust-version = "1.64" 8 | license = "MIT OR Apache-2.0" 9 | categories = ["value-formatting", "no-std"] 10 | keywords = ["hex", "bytes", "fmt"] 11 | documentation = "https://docs.rs/const-hex" 12 | homepage = "https://github.com/danipopes/const-hex" 13 | repository = "https://github.com/danipopes/const-hex" 14 | exclude = [".github/", "benches/", "fuzz/", "tests/"] 15 | 16 | [package.metadata.docs.rs] 17 | all-features = true 18 | rustdoc-args = ["--cfg", "docsrs"] 19 | 20 | [dependencies] 21 | cfg-if = "1" 22 | hex = { version = "~0.4.2", optional = true, default-features = false } 23 | serde = { version = "1.0", optional = true, default-features = false } 24 | 25 | proptest = { version = "1.4", optional = true, default-features = false } 26 | 27 | [target.'cfg(any(target_arch = "x86", target_arch = "x86_64"))'.dependencies] 28 | cpufeatures = "0.2" 29 | 30 | [dev-dependencies] 31 | faster-hex = { version = "0.10.0", default-features = false, features = [ 32 | "alloc", 33 | ] } 34 | hex = { version = "~0.4.2", default-features = false } 35 | rustc-hex = "2.1" 36 | serde = { version = "1.0", default-features = false, features = ["derive"] } 37 | serde_json = { version = "1.0", default-features = false, features = ["alloc"] } 38 | 39 | [features] 40 | default = ["std"] 41 | std = ["hex?/std", "serde?/std", "proptest?/std", "alloc"] 42 | alloc = ["hex?/alloc", "serde?/alloc", "proptest?/alloc"] 43 | 44 | # Enables `core::error::Error` implementations always instead of conditionally through `std`. 45 | # Requires Rust 1.81 or newer. 46 | core-error = [] 47 | 48 | # Serde support. Use with `#[serde(with = "const_hex")]`. 49 | serde = ["hex?/serde", "dep:serde"] 50 | 51 | # Use `hex`'s traits instead of our own. 52 | # This should not be needed most of the time. 53 | hex = ["dep:hex"] 54 | 55 | # Forces generic implementation, overriding any specialized implementation (e.g. x86 or aarch64). 56 | force-generic = [] 57 | 58 | # Support for the `portable-simd` nightly feature. 59 | # Note that `-Zbuild-std` may be necessary to unlock better performance than 60 | # the specialized implementations. 61 | portable-simd = [] 62 | 63 | # Enables nightly-only features for better performance. 64 | nightly = [] 65 | 66 | # Internal features. 67 | __fuzzing = ["dep:proptest", "std"] 68 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # const-hex 2 | 3 | [![github](https://img.shields.io/badge/github-danipopes/const--hex-8da0cb?style=for-the-badge&labelColor=555555&logo=github)](https://github.com/danipopes/const-hex) 4 | [![crates.io](https://img.shields.io/crates/v/const-hex.svg?style=for-the-badge&color=fc8d62&logo=rust)](https://crates.io/crates/const-hex) 5 | [![docs.rs](https://img.shields.io/badge/docs.rs-const--hex-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs)](https://docs.rs/const-hex) 6 | [![build status](https://img.shields.io/github/actions/workflow/status/danipopes/const-hex/ci.yml?branch=master&style=for-the-badge)](https://github.com/danipopes/const-hex/actions?query=branch%3Amaster) 7 | 8 | This crate provides a fast conversion of byte arrays to hexadecimal strings, 9 | both at compile time, and at run time. 10 | 11 | It aims to be a drop-in replacement for the [`hex`] crate, as well as extending 12 | the API with [const-eval], a [const-generics formatting buffer][buffer], similar 13 | to [`itoa`]'s, and more. 14 | 15 | _Version requirement: rustc 1.64+_ 16 | 17 | [const-eval]: https://docs.rs/const-hex/latest/const_hex/fn.const_encode.html 18 | [buffer]: https://docs.rs/const-hex/latest/const_hex/struct.Buffer.html 19 | [`itoa`]: https://docs.rs/itoa/latest/itoa/struct.Buffer.html 20 | 21 | ## Performance 22 | 23 | This crate offers performance comparable to [`faster-hex`] on `x86`/`x86-64` 24 | architectures but outperforms it on other platforms, as [`faster-hex`] is 25 | only optimized for `x86`/`x86-64`. 26 | 27 | This crate is 10 to 50 times faster than [`hex`] in encoding and decoding, and 28 | 100+ times faster than `libstd` in formatting. 29 | 30 | The following benchmarks were ran on an AMD Ryzen 9 7950X, compiled with 31 | `1.83.0-nightly (9ff5fc4ff 2024-10-03)` on `x86_64-unknown-linux-gnu`. 32 | 33 | You can run these benchmarks with `cargo bench --features std` on a nightly 34 | compiler. 35 | 36 | ```log 37 | test check::const_hex::bench1_32b ... bench: 3.07 ns/iter (+/- 0.62) 38 | test check::const_hex::bench2_256b ... bench: 15.65 ns/iter (+/- 0.46) 39 | test check::const_hex::bench3_2k ... bench: 120.12 ns/iter (+/- 4.08) 40 | test check::const_hex::bench4_16k ... bench: 935.28 ns/iter (+/- 19.27) 41 | test check::const_hex::bench5_128k ... bench: 7,442.57 ns/iter (+/- 4,921.92) 42 | test check::const_hex::bench6_1m ... bench: 59,889.93 ns/iter (+/- 1,471.89) 43 | test check::faster_hex::bench1_32b ... bench: 2.79 ns/iter (+/- 0.08) 44 | test check::faster_hex::bench2_256b ... bench: 14.97 ns/iter (+/- 0.40) 45 | test check::faster_hex::bench3_2k ... bench: 122.31 ns/iter (+/- 5.01) 46 | test check::faster_hex::bench4_16k ... bench: 984.16 ns/iter (+/- 11.57) 47 | test check::faster_hex::bench5_128k ... bench: 7,855.54 ns/iter (+/- 61.75) 48 | test check::faster_hex::bench6_1m ... bench: 63,171.43 ns/iter (+/- 3,022.35) 49 | test check::naive::bench1_32b ... bench: 17.05 ns/iter (+/- 2.25) 50 | test check::naive::bench2_256b ... bench: 188.65 ns/iter (+/- 6.14) 51 | test check::naive::bench3_2k ... bench: 2,050.15 ns/iter (+/- 313.23) 52 | test check::naive::bench4_16k ... bench: 16,852.37 ns/iter (+/- 983.27) 53 | test check::naive::bench5_128k ... bench: 521,793.20 ns/iter (+/- 18,279.50) 54 | test check::naive::bench6_1m ... bench: 4,007,801.65 ns/iter (+/- 80,974.34) 55 | 56 | test decode::const_hex::bench1_32b ... bench: 17.57 ns/iter (+/- 0.53) 57 | test decode::const_hex::bench2_256b ... bench: 39.20 ns/iter (+/- 3.36) 58 | test decode::const_hex::bench3_2k ... bench: 236.98 ns/iter (+/- 3.22) 59 | test decode::const_hex::bench4_16k ... bench: 1,708.26 ns/iter (+/- 38.29) 60 | test decode::const_hex::bench5_128k ... bench: 13,258.62 ns/iter (+/- 665.24) 61 | test decode::const_hex::bench6_1m ... bench: 108,937.41 ns/iter (+/- 6,453.24) 62 | test decode::faster_hex::bench1_32b ... bench: 17.25 ns/iter (+/- 0.14) 63 | test decode::faster_hex::bench2_256b ... bench: 55.01 ns/iter (+/- 1.33) 64 | test decode::faster_hex::bench3_2k ... bench: 253.37 ns/iter (+/- 6.11) 65 | test decode::faster_hex::bench4_16k ... bench: 1,864.45 ns/iter (+/- 25.81) 66 | test decode::faster_hex::bench5_128k ... bench: 14,664.17 ns/iter (+/- 268.45) 67 | test decode::faster_hex::bench6_1m ... bench: 118,576.16 ns/iter (+/- 2,564.34) 68 | test decode::hex::bench1_32b ... bench: 107.13 ns/iter (+/- 7.28) 69 | test decode::hex::bench2_256b ... bench: 666.06 ns/iter (+/- 16.19) 70 | test decode::hex::bench3_2k ... bench: 5,044.12 ns/iter (+/- 147.09) 71 | test decode::hex::bench4_16k ... bench: 40,003.46 ns/iter (+/- 999.15) 72 | test decode::hex::bench5_128k ... bench: 797,007.70 ns/iter (+/- 10,044.26) 73 | test decode::hex::bench6_1m ... bench: 6,409,293.90 ns/iter (+/- 102,747.28) 74 | test decode::rustc_hex::bench1_32b ... bench: 139.10 ns/iter (+/- 5.50) 75 | test decode::rustc_hex::bench2_256b ... bench: 852.05 ns/iter (+/- 24.91) 76 | test decode::rustc_hex::bench3_2k ... bench: 6,086.40 ns/iter (+/- 109.08) 77 | test decode::rustc_hex::bench4_16k ... bench: 48,171.36 ns/iter (+/- 11,681.51) 78 | test decode::rustc_hex::bench5_128k ... bench: 893,339.65 ns/iter (+/- 46,849.14) 79 | test decode::rustc_hex::bench6_1m ... bench: 7,147,395.90 ns/iter (+/- 235,201.49) 80 | 81 | test decode_to_slice::const_hex::bench1_32b ... bench: 5.17 ns/iter (+/- 0.25) 82 | test decode_to_slice::const_hex::bench2_256b ... bench: 27.20 ns/iter (+/- 1.37) 83 | test decode_to_slice::const_hex::bench3_2k ... bench: 213.70 ns/iter (+/- 3.63) 84 | test decode_to_slice::const_hex::bench4_16k ... bench: 1,704.88 ns/iter (+/- 22.26) 85 | test decode_to_slice::const_hex::bench5_128k ... bench: 13,310.03 ns/iter (+/- 78.15) 86 | test decode_to_slice::const_hex::bench6_1m ... bench: 107,783.54 ns/iter (+/- 2,276.99) 87 | test decode_to_slice::faster_hex::bench1_32b ... bench: 6.71 ns/iter (+/- 0.05) 88 | test decode_to_slice::faster_hex::bench2_256b ... bench: 29.46 ns/iter (+/- 0.41) 89 | test decode_to_slice::faster_hex::bench3_2k ... bench: 223.09 ns/iter (+/- 2.95) 90 | test decode_to_slice::faster_hex::bench4_16k ... bench: 1,758.51 ns/iter (+/- 14.19) 91 | test decode_to_slice::faster_hex::bench5_128k ... bench: 13,838.49 ns/iter (+/- 252.65) 92 | test decode_to_slice::faster_hex::bench6_1m ... bench: 114,228.23 ns/iter (+/- 2,169.09) 93 | test decode_to_slice::hex::bench1_32b ... bench: 38.06 ns/iter (+/- 2.13) 94 | test decode_to_slice::hex::bench2_256b ... bench: 311.96 ns/iter (+/- 34.52) 95 | test decode_to_slice::hex::bench3_2k ... bench: 2,659.48 ns/iter (+/- 470.05) 96 | test decode_to_slice::hex::bench4_16k ... bench: 22,164.21 ns/iter (+/- 5,764.35) 97 | test decode_to_slice::hex::bench5_128k ... bench: 628,509.50 ns/iter (+/- 14,196.11) 98 | test decode_to_slice::hex::bench6_1m ... bench: 5,191,809.60 ns/iter (+/- 160,102.40) 99 | 100 | test encode::const_hex::bench1_32b ... bench: 7.06 ns/iter (+/- 0.15) 101 | test encode::const_hex::bench2_256b ... bench: 12.38 ns/iter (+/- 0.68) 102 | test encode::const_hex::bench3_2k ... bench: 74.18 ns/iter (+/- 1.46) 103 | test encode::const_hex::bench4_16k ... bench: 471.42 ns/iter (+/- 12.26) 104 | test encode::const_hex::bench5_128k ... bench: 3,756.98 ns/iter (+/- 76.00) 105 | test encode::const_hex::bench6_1m ... bench: 30,716.17 ns/iter (+/- 795.98) 106 | test encode::faster_hex::bench1_32b ... bench: 17.42 ns/iter (+/- 0.28) 107 | test encode::faster_hex::bench2_256b ... bench: 39.66 ns/iter (+/- 3.48) 108 | test encode::faster_hex::bench3_2k ... bench: 98.34 ns/iter (+/- 2.60) 109 | test encode::faster_hex::bench4_16k ... bench: 618.01 ns/iter (+/- 9.90) 110 | test encode::faster_hex::bench5_128k ... bench: 4,874.14 ns/iter (+/- 44.36) 111 | test encode::faster_hex::bench6_1m ... bench: 42,883.20 ns/iter (+/- 1,099.36) 112 | test encode::hex::bench1_32b ... bench: 102.11 ns/iter (+/- 1.75) 113 | test encode::hex::bench2_256b ... bench: 726.20 ns/iter (+/- 11.22) 114 | test encode::hex::bench3_2k ... bench: 5,707.51 ns/iter (+/- 49.69) 115 | test encode::hex::bench4_16k ... bench: 45,401.65 ns/iter (+/- 838.45) 116 | test encode::hex::bench5_128k ... bench: 363,538.00 ns/iter (+/- 40,336.74) 117 | test encode::hex::bench6_1m ... bench: 3,048,496.30 ns/iter (+/- 223,992.59) 118 | test encode::rustc_hex::bench1_32b ... bench: 54.28 ns/iter (+/- 2.53) 119 | test encode::rustc_hex::bench2_256b ... bench: 321.72 ns/iter (+/- 22.16) 120 | test encode::rustc_hex::bench3_2k ... bench: 2,474.80 ns/iter (+/- 204.16) 121 | test encode::rustc_hex::bench4_16k ... bench: 19,710.76 ns/iter (+/- 647.22) 122 | test encode::rustc_hex::bench5_128k ... bench: 158,282.15 ns/iter (+/- 2,594.36) 123 | test encode::rustc_hex::bench6_1m ... bench: 1,267,268.20 ns/iter (+/- 18,166.59) 124 | 125 | test encode_to_slice::const_hex::bench1_32b ... bench: 1.57 ns/iter (+/- 0.01) 126 | test encode_to_slice::const_hex::bench2_256b ... bench: 6.81 ns/iter (+/- 1.47) 127 | test encode_to_slice::const_hex::bench3_2k ... bench: 58.47 ns/iter (+/- 5.82) 128 | test encode_to_slice::const_hex::bench4_16k ... bench: 503.93 ns/iter (+/- 5.83) 129 | test encode_to_slice::const_hex::bench5_128k ... bench: 3,959.53 ns/iter (+/- 85.04) 130 | test encode_to_slice::const_hex::bench6_1m ... bench: 32,631.90 ns/iter (+/- 1,135.39) 131 | test encode_to_slice::faster_hex::bench1_32b ... bench: 4.37 ns/iter (+/- 0.13) 132 | test encode_to_slice::faster_hex::bench2_256b ... bench: 8.13 ns/iter (+/- 0.14) 133 | test encode_to_slice::faster_hex::bench3_2k ... bench: 52.45 ns/iter (+/- 1.09) 134 | test encode_to_slice::faster_hex::bench4_16k ... bench: 474.66 ns/iter (+/- 8.71) 135 | test encode_to_slice::faster_hex::bench5_128k ... bench: 3,545.60 ns/iter (+/- 75.68) 136 | test encode_to_slice::faster_hex::bench6_1m ... bench: 29,818.05 ns/iter (+/- 1,475.47) 137 | test encode_to_slice::hex::bench1_32b ... bench: 12.11 ns/iter (+/- 0.31) 138 | test encode_to_slice::hex::bench2_256b ... bench: 120.39 ns/iter (+/- 1.18) 139 | test encode_to_slice::hex::bench3_2k ... bench: 996.18 ns/iter (+/- 10.03) 140 | test encode_to_slice::hex::bench4_16k ... bench: 8,130.43 ns/iter (+/- 137.96) 141 | test encode_to_slice::hex::bench5_128k ... bench: 65,671.00 ns/iter (+/- 612.36) 142 | test encode_to_slice::hex::bench6_1m ... bench: 518,929.65 ns/iter (+/- 2,202.04) 143 | 144 | test format::const_hex::bench1_32b ... bench: 10.01 ns/iter (+/- 0.09) 145 | test format::const_hex::bench2_256b ... bench: 26.68 ns/iter (+/- 0.49) 146 | test format::const_hex::bench3_2k ... bench: 121.17 ns/iter (+/- 3.15) 147 | test format::const_hex::bench4_16k ... bench: 1,125.29 ns/iter (+/- 9.99) 148 | test format::const_hex::bench5_128k ... bench: 8,998.22 ns/iter (+/- 162.09) 149 | test format::const_hex::bench6_1m ... bench: 78,056.40 ns/iter (+/- 1,584.60) 150 | test format::std::bench1_32b ... bench: 373.63 ns/iter (+/- 3.41) 151 | test format::std::bench2_256b ... bench: 2,885.37 ns/iter (+/- 43.81) 152 | test format::std::bench3_2k ... bench: 23,561.13 ns/iter (+/- 2,439.06) 153 | test format::std::bench4_16k ... bench: 192,680.53 ns/iter (+/- 20,613.31) 154 | test format::std::bench5_128k ... bench: 1,552,147.50 ns/iter (+/- 32,175.10) 155 | test format::std::bench6_1m ... bench: 12,348,138.40 ns/iter (+/- 291,827.38) 156 | ``` 157 | 158 | ## Acknowledgements 159 | 160 | - [`hex`] for the initial encoding/decoding implementations 161 | - [`faster-hex`] for the `x86`/`x86-64` check and decode implementations 162 | - [dtolnay]/[itoa] for the initial crate/library API layout 163 | 164 | [`hex`]: https://crates.io/crates/hex 165 | [`faster-hex`]: https://crates.io/crates/faster-hex 166 | [dtolnay]: https://github.com/dtolnay 167 | [itoa]: https://github.com/dtolnay/itoa 168 | 169 | #### License 170 | 171 | 172 | Licensed under either of Apache License, Version 173 | 2.0 or MIT license at your option. 174 | 175 | 176 |
177 | 178 | 179 | Unless you explicitly state otherwise, any contribution intentionally submitted 180 | for inclusion in these crates by you, as defined in the Apache-2.0 license, 181 | shall be dual licensed as above, without any additional terms or conditions. 182 | 183 | -------------------------------------------------------------------------------- /benches/bench/data.rs: -------------------------------------------------------------------------------- 1 | // @generated by gen_data.py, do not modify manually 2 | 3 | pub const ENC_32: &[u8; 32] = include_bytes!("./data_32.bin"); 4 | pub const ENC_256: &[u8; 256] = include_bytes!("./data_256.bin"); 5 | pub const ENC_2048: &[u8; 2048] = include_bytes!("./data_2048.bin"); 6 | pub const ENC_16384: &[u8; 16384] = include_bytes!("./data_16384.bin"); 7 | pub const ENC_131072: &[u8; 131072] = include_bytes!("./data_131072.bin"); 8 | pub const ENC_1048576: &[u8; 1048576] = include_bytes!("./data_1048576.bin"); 9 | 10 | pub const DEC_32: &str = include_str!("./data_32.hex"); 11 | pub const DEC_256: &str = include_str!("./data_256.hex"); 12 | pub const DEC_2048: &str = include_str!("./data_2048.hex"); 13 | pub const DEC_16384: &str = include_str!("./data_16384.hex"); 14 | pub const DEC_131072: &str = include_str!("./data_131072.hex"); 15 | pub const DEC_1048576: &str = include_str!("./data_1048576.hex"); 16 | -------------------------------------------------------------------------------- /benches/bench/data_1048576.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaniPopes/const-hex/3c08e80a20e72cf6133c59a2007224d5f22d9b3e/benches/bench/data_1048576.bin -------------------------------------------------------------------------------- /benches/bench/data_131072.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaniPopes/const-hex/3c08e80a20e72cf6133c59a2007224d5f22d9b3e/benches/bench/data_131072.bin -------------------------------------------------------------------------------- /benches/bench/data_16384.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaniPopes/const-hex/3c08e80a20e72cf6133c59a2007224d5f22d9b3e/benches/bench/data_16384.bin -------------------------------------------------------------------------------- /benches/bench/data_16384.hex: -------------------------------------------------------------------------------- 1 | 0951b2954ade33a6bbe2bf1f3021ff0b6c86a8e97b65ed70bee6738a8a7cc2615ad06a586689dcc52d6a7645aeffa91730fc5be91bed290cc76714245623b9f63cc16a894f3b88f885018785a5935f11436bf94957d0996cb39e51911331f5104567d2e66c587b7de44bd4df80b644bbda3093efb3b33ad518196d91c8cdade21737d6740dfe15472a282e2ea60c44f92ce442dd0ce78841eab44c1da834e4a4a2cd387be2714ae54e4f88bfeaa5cbf00217b196137f70956b80b0538b698b413cae6a4da6aff403e3c394c163fe39c85bba4f193baabc603fe1d6c4bb7e5de47c8df6da33c8857b39f03f067e4c7e7be533551cd715b3cbe15b6c3fc0a5f87f5b2e31a2b8446fe74305ff4928ba2c03b68a46831abb8de40c1394b8c9b27e5a153c7e451190991e1528b99c3285af099e0383c60158c1669a4aa4dd3d6f6a54bd7370a6a28e47b00a2679d4865329995435a094003415c76819bdacc0071d46c78f86063f24563b588324fcfcec0e06649f47fe58c5a2cabe864488454db09115de8169b0b9a4e9221f60eb77a204afb84553f278b95cdb76f872919f0b629bf86b10ed781b66fb9f49b0f59801939229c0351dccb39c9a07f91bcfc69b367f42b3f484227259df3924dd71413081e106fdcafa52aa05dfa2d9840f5aa8977a6c8bad55b943e44f04ef313a8d5b10b461b17a6ed80403366b508b6901fd3582f760f1b659c2ca0f4dbd18dc2deda932566b1721111700e93fb1c04e8dfa2291d9a517edfcad0886124aa913c370b2e1a19a13c18de015636ba4fda13e4b97015771618e8c2b7fb2a9533ddb6e6d1eb2f9ccf452c333db67b272f6726f976db121a8fa93f5568cd044c6dc9024087eabcdd271a2b33731eabc3813bed13ec0934fae2c75ca7b0d7dd0313be236a9649b84b8ff1b5bd753d35cc222342c959075c77e6a6b1e107540a88ffba897c4c133f1027bb1921c775583a6083a89b6642a8ebbf2b474a2b5acfb9e381477fcd019ea901f8bb5554652530d7eb99ea4c3e91fd7639e9547946c7996757082159b455313613422e4b53316728ac3cd2c40eebdfd151bf2010b5b9f257a7500e9850fdb675104f981832df269e273eed453f2141ebf4749a9c6c2864f04fd12c99269645bb7b2357b1519dd5a2e0aa2147c3e63a3a4643aa68c13022d008728fa8547281046d6c2fcc76bf4a6591eb10c63cd1af2edcaf1e60be42f895c92a1f426381adbee2cab33a1f195857bf1665a8216a37c5eba94e366a5b13a44a44b3e1cb1c1c1fa65b4e1a37683e648de729ca26e65c1bce082ef761ba3ef3b5b927ef5861904593cc9718ca6a1d3a1c880dd461391c3f8fa0434fb63657fd44535d68ead31ebd29d69d48e798aa598391405520cc36608d8a8b66ed5a1af7f7c6682c8fcd9bc7220c499d912a3a20838fbea78807fbd59c1bdb9cb80b7bb247b9769b8838e7d9915fc0bff53c11bbb139848d0cca24266614a616c663b746114890493aa0b39fa6dbf41f7a83a7c5ea0aaeeae42b742e4eadb83fdcbcaaa0c3075ed6f28d9a8c337fa79dee5f46e3b7493f96561199a6b3c737c0e56bd9b9e9bfd979e5ad68e0a83c163933baa3d91d15e98286983a5f04a1d576f670ad5322495259c8ad9ac1df4a409118d555a3f50578e9137c9792d18e0a7b3c6d478065cae9bfdc97db01819fffb937f573183711361237e41a61d608248680e8e2fadd10ee57190225ee6437fd8dce624d74ab709f4a49460670283f7273ae057776b55aec70e0324c2f384fafe6dfcb5766bc687f6038568515dc33e91c0162a8b645e8ebac450442a57b4b196e40c504212613f199048627609d093f3e0035d75d1c87d124629803ee26801d8ca24b54c45583d78d8e9661a4cb3aab10d4ab9956cb46d66b2781a33a371fed8b31bc84cda4b486bd7441bd33f947fd9fb1659cfc7c3815a4c6e94f4f732520b381c9b5ab0a86edc16a22a329ec3f3b782913978b660a23be56227e708f3435d0571594290efce274489cc78128027605f608483bbb66fb7458e19a1f5bbf82fac4efdbc3c3162abc7ff8f25e820c96541d5d07e37d90a7dd19b021cb4d31231692735306e8faf59b6451d6a5b453a9ba720716a689158f5dfb862e6e23db0b7c11990a0e63d25f7c7d26a16709dc70d26d85b2a4aefe03487237250f2ddef80bde3ace47b4c383e40e97e54bf707176cb4d33857f3a1ad49d1db4fe3ce4b1871458f3ec552e1cb2683b542c8d68905e55d636216126f0d88f9e6d741d3e673c90d7c3997e4a35501ca2ecb3ba80a4d026881bb38d99b12221e8be0e0a9635be76fc2bc3850fa4d6f4cd11d888683689ab59f1cd0dc62525cf0fc017ebacbb763eb1f96104f554deddd9babdd31d772347fc113cbe7b35e52623e42c3e5e0a73fb469554c2bd2eb98ed182784b547046d1357eb141f3b72bfcabb024cce8c612e59fe458350450dd84a8391a0a3980dd02b10723bd9a70280e4b63e7775dabf0a218cca85aee14ddc9377b973a05dc36356a328c958cde488286587da27939539d22a21570fbade9c1be32ee68d594cde6b66c51ef3d67cb8665f38bc4a17feddc867dbc672a33665597c0fd139aae301de896365f84163da2d858e397d8887defb53317be0cfbfa66da61480314c400aeda77e15c04048579fee6713b61ed9c2d3c9154e90af6222d5b3787237b25276b9dacdacecf85df540a8d06d557358c9d2d7c825d940d01af8198717eb8bde802b5e29aafc7ee66cfc190320ea55f48b4cda868d96cc18182ab0528872f70ccdc1df2285ff74657c0ccb0b2f77977137be31aaf654c5dea26dc41077e4901b4882c4e218b9715b032e30bb8a3c9ee43fed05bf87929680984865b9c7d0e845a03b74441e3e5a65bf37009c7405307cc898b956f5845e0d8a3229377c8c09202f88665688dc957dda97e4d948ab3d6bf22a2070039afb1433ee53ffe3eaa8f182561387eec9efc01beec67de8832bcee716bb2cc07271aad0014a3ff09a1d82b539f2551766c4d13f5f442ef6aeb7bf8ab4ba99d9194bb00e42de2c32f4a99759142b834844981bc14332bed9b3b2777548b566603835b6aa55869f136f26bedeb591d4a360e3b160dda7a33c732dc3db427c8ab75d0ed4efd31bb3270bdd76e1bea29c04bd788895e7a152f05e714f1c9e2b9038a67ebb9486b07a0f4a4a436c6d963f8dcb13a017c4bac4662c6dc672befd6957491ed64fc8537b62866736c087b1ffec94a7a70aa99ae2d6caf328ffd98938d33ce7b7577c4a0b052634d503125a3d6e465124789c32bdf7a1c7674c107d9e7eaa48e2a592d576c14634bce01997366d59422cfad38f1ccd515a59e967efa3c07c8d16acbdff23b195da49d01285c2f5c688f36bb942b0ef9928c401fb791342d6ee6b9e4009bb894e06a88de5a69033b6e7deec05f4a40a9d2bf865943a5cedc4b22552728d7b198cad89ea5d62d50975777cbfa75f3624fbdeae1034429161a418713f5b1b01887d6a32ce26578bb4cad5d038302216a7781a9af06c60e81d04134b37b457be05be9b4fb673f9f314eddb8570d615b2f213ca46300542712eaaa2dae633cc172e10a340a1ba26a24db0fecf868e0ef790609515b26f366b4499eedc89da9e4d1f19125aae7914aa3bd44ea342e734e629a3ea9819ea649f09e4d7cceaf5b8851e3fd898500ef06413cccac85c2fdf129c8d6e21ee5574fa1a11753cf1260bfed6bf52966ed05233b6176359657585907b5fc3dad01a6081bcb71aa7b2e9a30980630d9d10a89b0644556f5bfe2c1f692780429fd72fd5cce6b6f04034138545fdf51532f931dfb723921e1a8b761029343f744fd13c273c68636678c9f3b100cc0945d1c0b0f497b4517c7b86f896f95baac0bad0898dba5294ded50079f2717b3868940c13bc0c4c695e9cbb6095b96ef42c701b5312ce733ca816e65d4fd6de104f72c219f24734999a31cfd9837b88ce5f9c432afcb664f82a605155d685bd91b8ad0bcb91fd4ef04a5995878ac944787a69c5062f441a4dbf16f18f4e335841dc05e03d30b0918eac57203f543dcb370977b0a08378d7540053e8d98dcae9cedc5759e7701cff9e2e3227bbdc676d0d0e244b04286b58f34679d9626a2f5654f1ef41b6741a0ca6a040a35806bb2d8fc3ddb6b97d8da5f17f3ccb4936f490d074efc6cafa933778dc6af0e5d2500e2164c70aa8a2fbc1afdd879e75ba770fd8701eae6bca6b33ba7bb89865dcae2221edafec6896f9f499b8b9b0d0957f287244f9485480dfc9356a6f4f4042821d8007c0c7a5ee979850c723a660fc40a7a5135441b06267516e7276f91a6e3be0e8839153489672ad62bd1135ccf45ec4e594d65123d62f62f6fc415c0ed8ad134dd94b7f420e9cfc2e354dd275c12c111c178b6729544c5c1f0e11c2642e4b6268abaff25cd24c3ce7c8e01919c2878faa7f593737bb4ce3370b75e5cebdec8f99c2e71ad42e2010dd1909602c5babda248b8fd9abbc75a71c3486786e0a6e14548e74d2afdf18829037a7cd299d43f2a03602d909b1c4ba43f522a6480cabbf75c059dc9ad495818d9e69f9e7da24ef7a1cbce67ac7e91417e487491118d5da97673dc76579c519caf765248700a5aeb41b6d2742f9f85a63cbe810cfef8f48b288100d710d88eb4c3307339526956abe5387debb453cba5804c866e4950972a7572e909f7478fb9907ae15268516cf59f550e60197f1a3a9fb19af3e928018f67089dd47e79b1d1225c9ba7f0ecae17537bc66f09a5443ed2479ab5fb6ece7325cd9334c5390cc8b4edcc812843ab2e925cf06346ce575a7b6dd079c114340cb475794e2fef989d3d74c8123d154956101145f37482612b28ce206ab356d21f98aabc30165e0efa4d3e8c6edc1fc69e70d67bf0b388485544cc0d8f1d214735b7cde33959394515a20f431efeab7ea7467b913617b526b64b837789e695f635da2eabe4702e99605e9e0b24b7b35348a000c6e43e08ae205e4a7d4eb91438658aee5b5c3ab942fd35b3ed34f4910638ccf935788e7647de474393d67ed0e76a0ddd507cf3fab4c127ab024f432ccbd84d5162874a7da1e8527ecc0ecd99288a8cec6578ac7f66dddcb62c59381d82ff728a37f0a398f3800c134c8cff63ac23ca43b79007e526cbf88cf51d7a0372a9f23b575764074c14f12c638076ec4d1bb747126daf5221a1e61f814fbd44e590b32588beb901a38666d7ccaadb1d712a40005a88dba4e6980fb65166fc80e251b53649ef0b967af387a320c0c2961892fe33455edd75744daf60e817d30274a46313c68218ba599b5acc3d19b80fbd18d7b6aa487a19525701c4c8a36ba4f0724be9ce47513dd5a0c27c7c519d18b0d43bad81c01c480f545cb0995c55e97a23d54ed854ba0ca178a77e243de9f414919c99ccd624317b20f1b826583ba321e852751485eba8e88be63a669adb53ead85ecb1a6fa11f58b4ff374bfdec79ce7f9266d9d3b49b33de60acf1655e8df232145cec90b2ea55460a0cb3b03d3f67b3cee4364e9bb195922ea1374ea80a920a5f7fa83f2507a540457d6f0d8dd2c0e0c1fd2e13c6830bbc9713750a6f3d3014fbe9c6ee975aa4dc37c8d0a1e41bfb19c5f3afc34d75b06f86976425e19d443c8c315e72ac5a6e7aa8edad8d794953c0c3f03aaa65eaa7e1ffa363e5c43018b7f8f0741ba95f61d901314f7f75cf3177355befcb482deadbcfe0491a5426d4cfbf06f28e019c358fb7e979391ef4d7b6bb8cb2c44e8217a1306e4cb6d9c25d97e5a25b19266d6702c2ec970657aade681902da85b33803647152584bc84b61ee4d1dec8dba056a33b3874c9d0deec719e9bb582b94a8ceef2eafbca4482e5c02e092f2ab32f2b2d2c86b714406169f5d8cbe2f2fbdb5a74e132773c1ac12430f4ae8290e6333a91f159e262581109f7bf02d97e79027f116848dcc1059463c29e6bd03f32441618bc1b65e53bdf077eac839bf0c128dc4e0b73ca924f50e3dd422e00bf7626446fcb5cf08a55b271ec932c99af2bfaf0fbaa231eec98c790841b997f9a3207614be7817315c5e4eb356df6e17ef0aabc8186b3e9846c320c7890aa2cc462239bd34466d7acad7935516550ef79af55db66a9c2adcb173c030ffb0fdeff98d8ae3617c7176d6baf336d03fb013a4ed24d10a6536f537f9c5084fca3b7ec5eba65f0ed0d48ce5efb94b91952103d68006a1d6b6b2b5e03b351fa08c7924fa1a8773ed2e4ca929c13823258c6244bef05f4b540288921f01dc64d8b4d66c7ba7784be85f92c3784ba395436b080536aa0de9b94090d1f752d4aef365e91a30217effb13554c9e1c82aaa1dfa9f8ac4cbdaeb3489bc3aeac621aa5a2d7f2338f436f47040e4ff3f8c7b0aacf39b1c150fb68d8b56878f1b73ae8aa7ffe2cb79a877021d4d68070cbdf4b15dd0c561b014a14206a6f2c080656d84c45b83fcf141befff47be648c77ee4f2f787cfffd46f658dae64b96b8c8c19ad6a09be7f98f2fb6ae3668efedc802d4a7f1797bcdb2ec19b3528257a0e057ce543ecb8b62a482383d685970208c14d51698fcf68e3677f7186caa0f2fea72d2055cf5fee4cf1d2c8bb91ff39f05ed413724b100ceee0af95413c6660a5296c7e491ef88c7bb5ee32510e01f060a1d150aa7646f664dcdb9a607ddd9d53b43c69ba9e95d435d3985f10a28253386bcfdb9ecb0415c658200da1de546ef840fa93a7c195a70c1f28a8019e8b9ad4ac10cb7354635ba007149ea96f7f0f9ee8351c9c17915ba74f0f85e1c99b0a65fda244e0d9b7054f4216aada6bfcdf833349d8b76abce75f2015e071778155d8a9f985691470bf3120b5d83b8ba5fd8943c15bf5ccf93373ab1a48fb9815381baf01bc9e21c0a38c772e12f752915ebd3877a6093a53b813b4469da09300e5f946c290802c4f4381ff8b9174ebd72c17db3a774dc1fad4349f9cc61bf2d0a258eaba168adace04ad19a8befec004ad00e6297b2b681f074917c7691508af2bb782ba7c9cc70ef62abad0f3ef95bcd2678fbde15292d42082e7dc2c4d10ee6a556fa4542c45a72f3ff2544840e9e266954186cfd0aedfd75b07bf485dae27e182c5ada4caa0231bb8b63f04ace0509c56fc09a8e21242cd3e1cf655959574fff45a60987ed9da2c24d4c827dbad1357aebdf58f48f71063f4c943ade2485ac55ea84adc498fe734e85bcfab4a32dff85620fcd317200a27ff8bf24f6720d16bf14000a672e86abeb1dc5c2c5178012da8fab7ad6d5ecd949257a20a5ef5864030bf95aa3ef7e2dd47850fd1ca81dabad9d50f74fc623d70d33b27177a16f0d3b931913884d7f78f233c34b6ccc773473d73715a27648d296bbea39b5de866688be7f6702150a80800e05aad53eb90800a3965bf75cd57396e96f967c267e62941cfed194b739b0af1ef1b63c7f7e9ec84b4c1d4938f3d94c0d50cee5ec3f44d3a532f311465b7beb4a3acd9c5debf26034b4240c16a35716abd4e34c7a3e42b5bc43bef89c193dde5bb14147a45e2c3d5ca8e8f63920b03bbcd43782c9f305d2c24f6a9b970ea969709cba3215314b0863768cc8431e60751dc9238f065666e1d50fb985eca8145e0e5130bc126de261cb7d88a700479f359f30b49d2be43b2f4ae2b3c13caffa4a89fa3cdd24a15288f14aeb70a6fcfedeb9bb00ddefc3029c71fbcf501b037aaf292dc670a3a8679c465fb33f77cba90335341b4e29a5f87600e96a579863619ffc6d14cd52a752310374b0f3903469467de1eb272d7a5b67946e43b4424fbb8a0f2055ed8dcc0ffd18ee26d2b80e2821eea1da11fe385a3bd54bd925ba7cacec0f11193c4542644db1a3dc154126a3fab61685cf65c9916bcc02ef02aa4fc3049ae62e4a6d398d9ebc7513f933fa68b6d07faec1320d579c9e571dae359a7014aeb99711b69279a0514added1859ccb11366d6eb29464354651e681d976d00ba1ea710c1bb52fee6ba23dae6f1bef11f976aa89749ddda5a6bd7199785331616bccee745b96ea102935883e95ed765d5815f190499ee2b892b07dfea7130c66f4e0709f86fd954787c8c367fe76d50d9ee7b22dccb58ce7002118205468bbcc67fe5fcd48508fa5d515ab7ab850f32e41650d0c6f9732b6f5e8522e82f841685c0e7b663dfb4a15673b3dfddf817e6c69cdb28897c21324dd63c8df2520c4ac1981ecd3f241fef77d4cf7815957c5e9924a65cce9bdac3b905fd596b125020a60415f0f41f8109a3d5c4ed7c6dbaa942037642748d915488145f9f1dcef4fc6acc14675d7653193aeb0c00036cd3b95c62e7d2be8f1158aec05256a768283c396ce8f4dc961305f23fbf80d988522645f1bbaded4a1f434837db7cfc73af61ae71fcaecd7d1362998c6ec5d0837e6a8edfa9506d9e72a4a5c220fc99a4105da0c384be4b789f720735bd33ccd2433909fcf88a56c0cd4e8a4cb6366730dbc60499ca8e3cd2f9fdf77fe3b06863ceeb755c935a74eca8e9e4f59858bed29cca9c67b4d31f2bf3c19b2fb1d82019bdf80c40ab7b2ca52a9fcdd7e5b3fd5793cb0624eb8b89178a1f38b346ce0f95e49bf4d0ee951841979daa7b61b8ad983a00be4addcd8a96e51b159209228201483d58a4f5a7fade861f32157ce64a92000250c880fefee8db71e3dfe7c7392a7ed0f90c96f367d4074c240fa7663ae482dd348512e3ea707357e88fcc8636d3e41ae66514dc4b0c01c19886bc75e25b4fdb3954791b091ee3a6f653b6448f29ddcdf7c977594b1128be375fdb40ef363c56704356bfa6614f361ff6271044339067559c9f39f42a489eeeb94990d4fdf1752dd7adee89a346ce067b39ec1c325b0f06ff37a1a71646f9a1893a5063abf0bf33f1e9a589146f0ba557b317a675535c931e70f12601e281d1ebec83ba808662785753b78d3911801abf83e6e4259d144ae2f6e2abf88855f6f69cdc18a1b1652b7e88561ef91eb38890d3bad34285f7cb9a5aa383489a6bed3dc395bad7cecfc8eb4bb468edced632e013891d77ea52c312fadf212b7a6b7dea79788d849de7f4e3143e4ff5a02ab94c1bca0283e8f58bc800f12498e6017f06bf8ff525526d9ff5cd9d20768c5c2d564c3433566efd22f32b7cbd3247b7e5828413964e72a6fd74efcf2905ecbcfc2e92224ee02bd697c7369f9b6223a4d6720ff6205f17c87c6d98f3a3d21950d4aba94e7aafa251781512670dc89a33b5583238f9d4b35f72b4be6261bf47aeaa928373c23fc88dee0e2fff4cdfc48dac7d6eab54cbb4efdfb15567bdfb2e4b83b38c4cd59a06a3bab19bf5051204d513c620899d81266a1d57333002a90d331693c0368740ef0f506a5fe9d46648fe5afe8328deeca2b26663a3a6c0cf1e9a00cd62936c206db27036c7eb524f9896cd7b5ae5ef9b14838979983302820ab1ff807c8539639035445d665ff959429cecb128b2a93ce0dca91c9bf5f3dd767f78e6c2e70e66a89d62b543ef960ccf53886e3936c11fa3a816963223f56e7ee1a39af22c4bfc1f09a829410e578de4928bf7ae793ace619c4a1ac92537096e9529a4ceb35476468389bad407d6d9acf9aa3bb9e5c220650b14703b4ae7c12b5af418a7875d18a9bf135410596281c950561e9b86450a3c0568be4adb908434dbdc8471cb0e375332265f1196141b1b1a86f33c8a4af4c4aa862b8c0b425fb53af2f3e9c8ca656dcafdbbc904f0f31880ebd40009df7c4cecc6db6d099725f41641efc58e3965006b5a8e60196e7cd800feee82dff54814ee1d47a11ca60b7fac72dde48de5d4ba25eb7c915cc225b6c381fe43390d53a3dfb0c73a1b893861ad081b94866c794698a2631b47984e20e647c4f37a472a3a4cac66d7e97dd75cdc7a34b98e814ee84b58176d1e986e650c5e6a2c9cfcb40a195dda0858259ee251da3f6ef52acab6b7cdd4d30dfd0e9a7cc33164dcc342cc48b11bcb44d324aa020f1379995d418a264c385102ea4a5057a95a6b54bebea69377e62833313e01525360ee350eed80292a990e9990fbde706264a452f91464772e355d91ddbe80ce065e41f99f782662cd1a0416387d637a5f065bda5d4933e9f44c55cb07f530c913c6f9199d8c42d6d5edaf718639895fba5be0e8d1f355f9ceb233f6f9d448a2947befa9aaba6d58660137189e8f76daeb577337c38a5029841c487cee66c2ec3657cafd83dee9ead33e4e1fcbd790de643287d9bcdaf8bba391f1c610b9938dfec7ced83d5a8af90f9f7c8f1ad4aa8acbfd905452c402434c06c2afdef5339699c35fe1ab1377549cc79a1cb8046d13f3d28883418fd87c164dde0e5e70fdcefb8d3632afff5c98fee3ca7449c556aa1f931563462c7a5ca0d7eaae6ad5a8493c64779d08e4d04c928d857219271c7f0cde153c145096769e3a245c278e0525de7f6bb7307499529cf6e406afaa06128e1a4289c01a465cff7a85c74c3e59a5b5c53ef25f2a1cd8896b89cb7fad47933df2020ad7d376d6651c09061ff699e354a2c24f8bdc88efdbdf7fef7ddb569d43022ef8adcdaadf79743950b94cdff72887cd53b8077c864a188f2e6a32079ff2e7f141f811afe3828d09b0ddcb8080e7881ecd43d204a9e748b06cf1771bdfcbb9df46469236c4d1ab9e6694496f9de9b6c09c3f6876b79b8237a6c57b8995df970a931c05a34ac649696eb3dd5b34b438954c81bdce9d7bdffdb318a1ae20e8864e5d418ef10dabdee12b5207a3d9cccd973c0569d87b786d2ae1fcfc277a03e28372076c9ad9c23cceae9c4e1ddcb6e884da5ee673c136f02d142de58580d8c1d6ebb4b0a85fec1c3497d984f3ac7e149c500bf6436f90d4d116f4dfda0567cb220297512af03a89d25acef0037998d9e37bbe6b902d098820a271e5c2c5cd197c1b9fb04e649bce8e9db568cda2591874a83139c23976f9c6ca8315e5d202fe0ccee73f568894d1202188eca44d48be640ff6ff1ba4bd736711436c6132560dbd235b4c1ff0099387b42399cdc45ae0c1074fd26609146fd0cec396369599719fd3f341bf316f6c4b0f0818f9f81d2bcff3bbaa5cb2e7e0e9ff6c11637a6fe71f34868720d8b02dda260247ba525406b446760c571eaea4bb1676f4a078b23bbc0f2330b9ec284b4ec63c25c9d718dca5cf5cb8b4b42447569f098faec9dfba3f89fc039d9d7cb0ee130d28c7440373c80c92d76e50a8dfd9512e98db96306eec06f4f04dbe8b01d78ffbaf37ff8ca751de4320d022f044e539a6ae03cebcba339847064f9d3d63586d438b17940b40206e5da18b3fcfdd02cfa0816172efd972de55e2cc18c0e1a2fd4c0b60eb83e4e08b8b624ed8dc2da8c2bc8b452ba1d01478a9ef4197aad09a56554694bfb719c8c5025e309cbfe3a137fa46ee1dc7b4755976eda4cc65ec9e6bb2cf4bea99d706ef543b9d704bed15cd360d1daa5abc564ecb608c5df2cc737b90a9c264f69dd5363027eb66b5635aee9447c8b9647163f72f135749bd960ac83018d6e7948066c6f29a50d5d5460eb46b53949e520bd9e7f8d24439167936dd77f53d468030f2eaf9c7804972e055b51df9fd3f0375e27f1722cb909ecbeca676843d0b63d52ab594243dc75d9ffe431ff70467b11314b099c5cae4c4690cae2863e8db6630201273929cafc63e25be29cf34463415bdcf6c9ecad5e436a65d6ac5b7e4662baa8ab3de49acc11cb46e5baf3bd0472444a6444de6c3dd9aa46b8b525ea06762db3fb8423bbc98a3aa8d4ccc1f4b2fb192a8748213cbac763200dcee70903a1dedeffb68f63c6a8acaceb42557f220d39ad8f61cba9407c2eec4b303bd73d9a9cff9ee4e93a1056b9c8c72d47ea272b9dfd7fbd894285a0f5863825ad1b21ac100380aeefc1175b113513c35998297b373c23c4cae5609822e472c6d9f90e583c77352541f4ba4a23cb4cbfe5b6e8080fc5192561d3d10fb8eb97e828a7a03edd3689020c525d8f225603ed145c5b17308ba7c25cdd58c2159cea5812bf2dcd6b87764594f8e5f13475ea8650374eef8a6cdcd8dd26c1551c550104e0c47278d19f848c1249bd15ac8498bf82392b90bcb3e2b3fe2ebee4def21b0c4363d9c1eb2ca6e14346c6618720579c4c24dca1c0990986845680351759338287c404c704536167b95575102c57c122aad419ff0a812897cb5105b4bc5d3aa0e22497f822567bb3039bf84c9c2d4f5f1d52a9a611409a5947a3c3341570fd8d1d7c5fd00da16886153755308226d7398d47a75b69ac278688b3e61226dcfc1d0a79b0156dba06c7533675fd6c84207cc66a9a9dc791ea765907c51d7118f3f6151492c300ffe679cf9842dcfb9143ebdcc39e7aa6cc28f3c330ab351a85892a1b64c2436ff5ab6e7b3376575f8912d100afa5418f40c881fe70b814a12774cfe79d8371640a7b3ecb87b19e3109ede447d81aca11ed30b51194f9c202857c2ff45b17463e3267e96025c8add2a7e4d54f4f49558084e296badc612a74a7ddf0b8f7e70d763a720ec6bfb012205867daa8ff2f9a7f074811673fe43588494e4922defddc72a5a306cf02d61d1d39296c6ba4a8bb430e79d3d2e683f95c63786cc4194d25f8817181cd541ed4fba819cc82f900434cd58e8d990e4f6679ed65f4c38c48eb078499850d8962704c03307d4f32127c59c90567c1162ee29b211429429dcac3b9afb64a0081011692587b37c52e8cbca0a982dff0ed77fe57f3086015111aa89ed7234e31b691a85c6f5963002266df1441f6162362d5417cbab31be55386ad2ac4a52a9720826be6a85b609e86d002741a79837909dc2a26f26a95fb446e1514dafa74a6ab112c8f21fd9a8a4e1a3db7c7a9930881569878654068137145b41f51129fe75ea463b30eb8061bc961fe736db152212f8a95179986ebbf1f1bf3a1bcc5d695b2504bb67d390ffc54d0fe97f5cd8aae67033ab8674d8cf96abfe5ccee445cd23aeb2890afb6ef5597ac7f78c869e8ebaadf1894fd1f3f3d760f5e1eef0e17f18dce0953f5878c14ed540d61da1d14a70e463c7905764175203312dcfdfb63f3dac7b4a506e2ef57f31a7c58d4dc0efcd2e74915865da0a8b4acf41171535b61cd268b45cf444462b44ca343dd9fba02d2e6d4df24be12cc50fa076afd01032c0e778c86bf458177312837d8c54c74bcd29d63d2b0fcb7d07875ebeacba3bd185ceba9c445e40446852e085cfb69e749706a0a9b8981e9198fb6737c2a84b7ddb96271ae3811b5382834ead9ce2e6687e5f53143c30660977f0fdd43f145282f03c0ff6b31afbc0af6ade94bdaafcfcb70f03d18589c2e6a1a6d23d8c717d0342edf987f727744ec665d85ab07d1c04e7f4b9a497375ce7799c941ec82487950868044a0351a893f08aa77cb71a945dfd77835544a6a1d345b41bb4426e2ae423fe427bc52437deef2bace2d0767ac7dcdfe003e30a3928e110da62ac68913b3ef53948bbade75a554b85fdeaf9ceb841b7b27df56a50bcf4e2550b8625d67ca10528ece441431b13990f66ffa90a82a782bdb40ed7bc1273cdbe7abae4a707b1fc31a00b1ba51f41699324991ee8aabcabb7be144f47db88f4e7b51542154f4067e85ac14bac46bf51721be956828dca16c886af4a66e51b7a263dfdc35637163c7c49a7cb6679fa629a318c46d9824e653cd8c5c9f3de5b92a39365a5d418e3bf9bef2028624b828e585a0f1ca81a542a2d00b2f5c55b6699a115d6f149be47277a9757ab384eb5087b313a6394cf071d73565c1a576fc5233759aeb5e42614310024f21f39fb13325623ee87bfff946cac60df10ce044b93a34da4b071424eb437049b5488e8d0118f7bad8ea6373084e963893fe5f2bb6bd7dc776ac468a59361e653d40a9811bf4f262718e58b523240255ae3bd8e678dcb88024c89467d48a22728ab00b982e3f1cad0d853f6ca21586a352d942e6c7ee445c77202f514289e43dc9a3adafdb34513a8cd0f3019b249718bb11df9556abc6a8296d76c18822ab415bd2fa37fe4ce00ba98ede70ed44c687ab2418341301edc0d57af4635b6eb86bb91b12913ca75da7b7372e21c15f8dcb0c1b6ac067af27ac06d3097ebd3e75343a04db6ea042433608051a7f8dc0d7825789e53950e26bb95f2d7fffb85c8d7e615c17095ae6928ad239585a06d6b7b1e5f2f91713313db9999fdc50494d2677384c307a2fcf8fac174cc4d8c1b461b842258264864fe72630445648578fa2c8be7263814312e76a63422d77fe92618f7c0263d7556a93f09b24df3152455b3a7eef55ad620c1003e6f6394efb6ee2ebbc2e3a4ae567000cf6d6a67be6c9cc0384a9b19dcec88a9043ecd2f9626f5bd2a0b6d595f6e6fe79da30571a617f360d03e361232872d9189c44af9c06e6a1d1267b1be0fc047906a38195f81fe5c333e5a2ee492fb4a1feafbd67b62e78cef537a8b0a27ad3a9d769f4bfc48390f067f6e4271a7d82a11b63fdd90eb17dc2987f9c768218c5409f59b8bef7c8630de1e97a4a4eb530df032fae6db211a6727d3148d0d2c10325d68eafa64204395350a05426975154527117c6dcd62bc3fc9251ddab4c9535fecb76d35be2c49b571dd46bcd895190efce0e01cd0ede1824b1775e685ff33f2d032d72d729e0193fd9f87067fd158e19d2cc4fbf5dfe29cb14dd1ead7dd97432bf9477376da03edae3bfbc6f16a2d273094e414e185ea77573e50229c09632817d7b645c6e6b6199b268540fd004d79402fabdddc4e2d18b6792955c713f28747bf7acffdbd639fda440486d2e515303ae2214cf9cfcbc2b0189a0e73ba4e20a8fdb17a5313b34b58e8c69dae1e66d5b13b9db529742ce9873b9d9ca4893d5636e156fd0a80b18182a7201efbb83ce80dc2e2ccf848d18ed62a5707de353c4d23e7be24bbf7bf8758e864b9fcbcd9699d7814b301d9a6f3652890fe746eb5989256c993c395e884607028aa6644005180b7cdc71a2486cb539cbe874472e763be185a0534463fc3841853b871cc9e69659c3c93d2054f293a81696f44a49481310f613ed8412894d894f856811372c59eccdbac0f32dece6dc803b20e29675a7dea888989c2f44b651207918f0ea33e9c68af516b74f8465f8c3cc0abaa4b03c35eabdbdbf5d73a5e16e9f35f97a3ef7d78b782f1f6028f28ff0d15898694b6770e8d5bfc20de5e0f1adda16d209df39543b3bae8541b6ff12d1ebf54422665a34dab6a0d5dc209fbcee96255efc7c7be5ed441cfdaba92bbc3e0ced528627d14e0450b4b70830d472c67eda1963d77709a14dc4d127e703f55be2da977e711238d477abb1fea87de629c1ce49a1ce3819376b5abe35092f9e41dcab6caf19340d49a6ca24435dc0925e2ccb3c83f40e91925582bbcdfb6f869fda3fddc1f6471d14ca150cd2b0018f89cfa13961f361daa8f29623c58f2acf65e1b74c94ac18837d4f7d499ca9c4c284bc6bfdd5764b10fc007fda9afd3457dff2c4bfb5125d9f8433ede5926541850d6b3f247dfd3cb0a99eb3b963871ae18b8f1b6d31fcc8634686b857261ef159048aa14ade691c9b93d5d58ad76cbfac2da17a6e7b539cf952e0c37cdee5abf938ce6b59069f19f2433564112c97a8818b450832f2f382fc8e38229e2b6921c713db23147360ce3b24cf5e1392dac1f009b1abd9810a8ae25252f2484b97c6464927594b3c4e3a026d0d01640b1b34828b3ef16b5647f4a2a009d9d891369c621a592867be5acdfca6fd90c8c85837c1e6c414b5c842124605802cf4b76ff21e1a2fa392c28db0d642f87714588bc3d2fd9e63d7c8c746acafa9d767fc66d513407ca3719d7d59079cd29ddf9ede6d4b021ff3a7f9bbab7be1488445de2aa7c43ebc6bde1c8ccd8249eb623815ce4874c0c66c123e7d552f15224c9b7d8e990e203f3061198666a77a0d8f8700810768bcbb9a7690f979b7c946561681ffab70d819a6cf918a8fb7c6bb7310418f4a1257d40a4bbcdf6c46826e64c8af1c67cdad143a2019c9a3845ad163a5095f251e7bdf333bc8ee3fcdad8768ef5e9476b7f2a20c5a3edae5e77e7c69cd40c617e19b337c2f0c891b60222a569d9fac2577210477b22b2ae7d6178bc60de6c878e10e3ff4e15e9d559242d9c1fa114fc98683fdf3ecf832ddcebe32f6b28d5682be113a58482fa067506fe3c3448dad169de745b45c0e131aac7891c981803e0b3d3f503a4b0ca1a12c6b1cde7c9b1da45ef34a6b1f70f191b650760336b7e752c3c0f982953fd377223eff1dd88bd33daa673eb1262ab4f850878c57cb0c45d4a71108e5229e9cfc9e216c31b16a92f5f92db7d1537aa29fdf8fec7825e451669a79a2112c68e8cdff2e96d19a42636774f066b86f419ae49156ba640348cabd103426fc1ce9663b97af630616e084590f9e5d8b25f6538fbf3dd69b12ba041c2abc366dbac37cc7b827199ae0db069158b71555e38f925981301d390442e3e2059fb382e63735aacff6a7f85b9d856904a0ece4a41d6e30b59775b77dbd861aa1a94e448de4ebbc942c745c0d8022b3aee482343c017e3941ea8a8ec2c78aea87d4cc1bd79cadcc264a8d302f284e8838a09bd7998cacd50c7a27c3482e006df8d8204d398e3b372aee3ccc7d4a7bb2691c3ad19adcd7fccf02726741cb35cb0be2df3f73e2c7fb331b2508b18593aa2af372a5567bbc4acaf43be49ff1654a9360e1f5c2e50c4b341d9d759ce433022ac17effb52b73bdd613fcab155bcb5555a0b75d07f119356b1c0ad80f373348431e4f402c3b177f75732835a04646d58a36522b8c871d2f4bbdbc32e3557091c63f56dff3d587db666b628782d4a285c94c7ed60a123f9db5ebb0dd0db7c94befe31a700327fa858936cc38fdf341800b19cdf409c7c485f2fe0d9b6aa312794fd37976cb21720f7aab2e6a7612ec8655bd0b363b05e927c7f99840c86a0ff5f23107973b96ef3e6a9c11b8425ef612a8ef8eafaff9a239bc901e065d6ab770bc1638bdd6974c1186ebf5ecf611cd1baad4bb55fcf98c6adfd8b958dfab02de9f785ae276aafd3fddcc3299e069021c9b62605af5e4540e57b1f9a4d927d7749693b996078e6b35fa3e4741f26971922cb9f41580de0c8e170e07562ef0fdbfea3fa53fe4cfdd56887247b34165057fcd148133e8fff10a15180cf8f5593f64878c6ae91679f3f1dcff6664ea729043fd5ecfd5505d50a13586649aa70938c3d75ee652d19285b6a13f733292021591ffc3f45682dcd2ef8043399aa1c4b819c3bf30c97af947c755cf58c55f7891314691f75d6a9cf1188f865158639f5902c2bffbc8b8a28230cb038d40c6dcde6e3f88ea28190d86e762123a5878ba9bcc15fbe4f5d3ddfac5bee131b59034946a1edfc8940e49df10081429c27b07b626b486fee6143ef9a10110c28fa7d7ff9ae82f5c9af6d289daabbfc716f37085005f92938319bed2f3702cd864b2093eb3113ade4ecbfa0f2f6dd3ec29fb8aceb2265f3d622dfda9a6e5d9bfe164b4085cee239cb90c4ce4625e292d7816a3825c80ab482c6c7a59a71e3b1403f81d5384afce2f7e526cd3615888199e4ce7060380004a2e2a03ffc704f532372b6eb7dfb8d6cef162d334f0fc3a6a1c697babf788f1a3bc85d00d5f5b1935da4bdbfc0ccbf15e70bdeefc9f1a0a133338b6f0c931263edb26788653355a6c9c779dc27a3c96c21c38308ab39455f4aacc0b2574fdbcb0854ccce38f4691c62d34794113fd225a940c49038d0e702a938af4a6505caa3e5fc90e037568f835f52b90374bf2c6bec610edfe9847e2f51bd5cf62083efb16ec31fe493f76bf48c6072cce4e28e5632be8b8432542f2cdd4334b9b16645c0d2f69cfd8da9a7cff08d9a71e54f48f20cbe4a94c537c9d62e7e9ed6bf7593fc62b83f1819546a90e1d7d18a96e108170a27b04f7edc88b2c2ad99cbd2099f2c9067280bcbaac051c815f118043e20f0f62af185583e2ec54b8bc64453b694e638606ccc9e625fdc2dd4d64bd174ffbbfc63ae05f29978f02ff678a3a8b10718dcafe2edadae2c22a3d521e7cbc126e15e4a06ed8c362d86454fe0fd7b5c83d5e8d7e3f6db02f75e872f804dea118c8e8b21e6c7b6a45447368718270656556f4036a2f212a3a03593d2907b66bb1f8492c74b252304817e598a08aae31c14a2e78a9a9820af246aa505135dc4f7356c0a0cd3ea5c292a9020714d833574794284f4368b5101d259d2805138256180926d014b599d039d3a0b9d03a9133481377b0b8e6d60ef6560fa17f669b46dada1e32102c82e10edcaa4318f208855cf926b162e7abefeaf2b89a6118aef3119ef0545b8046671e9ce02b930c2a208ade55d3419f3204c3764147270ae4cbed02d2e436757045f94c0a0af32bff4099133c595ea58a19cd16db382864b718de1bd2bc1fb87af1ce01a4203746a70fbd29b8886fce3ebb7cbf0f823131ea7153dd69e0e51cffa9f417a9c7e01c0bd522fc7a47082529f1a0bada108d5764625a98d76abda5fe519f447291330ae4e7e4942b4caed8d7c7f27a58fb70b159540b8f5beb6fb8ffa94e149bc0ca7f8b3d3cef8d555be6432e9f3db2933d50d3cf3a8865d4b71d622dc9a272154af23ad2a473bf349b755392441480d35cf89709d5df4e7d59470d519c4362f89029ee3d341ad23952f38dddbcf1c6c73edc7b42cb89fb55f11f8c5674fb7ee6202941821c8bdf82f11ebc8b51bbc56814f9c1bba235d70fde90d1a4d52770daec7bfb2d509020cffe9fe253d9d312918016864caf73d28cbfea675edeeda57ae12459beacc5f5e92d962e884e5a4e3f228b49bcbccf990d140a5eac58fd21d00af0193ba796b12ffc0d6713b4661d531ae6e605cba7b205fc837cf098545da01cd791057487f5b1a161428ff3d6d804f2927c1555cb1cd85ceda08789f42df5174d6a4f04d2077d0dd0ed6c96c0616188c550578251e8b9e4b7bc034fe1885e416024420d40739f8b648fa857edf43e098d17759dbd1109cdd3ab6b5e7faa27431a009c2fc1d52a83b3937088e6ba616eb98726627d5ce39eed4f0b5ed6bf44f23fd1f1af99241fc712e6d0504135320f0fffb84bd6a04b12d4aa743c9935e8bd55acec445eb1e44ebe1529a8f55d307a5299f8c5faa74a1dd6026bb3c229444849bfbc2f99253abaee7f0c9cf892431763b2b19fa694fd18ec13c6bbab1c05df357a508b4dce4025bbcb2c7fcee9d850acbd08360ca75fb85ede71c53a8dc64fe30a86aab25e1f21db86d0793aadbfb9c55839786ddd1cdc86e009d3dbb03d51247f1ddcaa4ef24b9e4887d9555ec302318bb5da4f872a5de690aa4552bfc61e2d068ce4042df63ad766579c408f70dd6d25c7f5349e94a2011f6f8bb2b87b9a95e75451d2a737d862ab5f0c12558849119b763d433645de41fabf8bb58f256036c12a95f82942505244218d0f9da7c7edb2f20499060e681ecf7f9175365d06ab8e4f4063d0b295e110a35d7cd9019ed506c1b0d3c6f3c20fbdeb17121a557b1710a123d6d1bbde2bbffd517f33d478b4bbd70e763433f7bc25cebb663acdd63b48989475b959cc6c9441e405e24618e622509c57cc3157359534f12890fd4b26405f4c71d587137ff222765dd19e2ac133deff3e66113424dd051a339152fce6b6cc64486ff30507070618938492ef1fdcafb59365b8e6ef6b277414531911875d301dc253f9f270a328847b3276eab90262bd3b99de9a8f5401706252bbc80e37b3781a8315a7fd8d25ee02cdb49842bb2ba886ac5964b2821243dbf744abe05cb8d111d82b23f7d23ba5b2e14693ea0d915717cad8970cecd556e1ab0e288821d6ad8f9f32d148f7eda92e7f30326259d1f5aa5e9247d9792ce3775e42a75f613fb4f86722e6308b1e787fb013a5cdbfe219b2116e56665ffb9596720e9620bb6622ba36014233674b4c0b2355168bbfc0b225742ca39b3f5a7bb11474e9edfe787b4fd6bd1f333d866783c02f77b6d55b416ad9f72ed39591d8ffa22d1ed57e88f47e763b21f85bc7919983164fd8137911dd3c6cc61125d4d7e029c36ebb07c97141f7c576590e35624794a10684155e863ba34e9e3bc1a7de44228f92d030f942873ed889cd798a4f4c387f459eae7fb95584e50fffcbdb23e2201ec934be2dbfb24334e0b29c3713da3127b637b548634cc7774231a93609b80146a0a7efc2e27e302f2f2078c566b315e005a2f58335060ab055ce3e2013f4e0faa84fe25069dd4758125f4afa89ab21f0541efea97a953665dbb3900e72beb831f8a25ab6823bf46b31b57ab47717427c9e99a15168a30f3e4540369b8b66bff7dc5d8e823b8967e1a795c23eabd321c4d2c21c2c9bd031803ad5b6b6d4c1c56b91a4c0e10e93cbe663cbe7b448e2aec87a17e87dc6091af100cc235b3e50a542f66fd4088411f7d554a1b10de5bbe20cb6c1e870edbee80d4e779813dfb6f4cf2c5fdfccbca6faaef064ff846e99fe4283cc73a829334cbb93cf8b1aef458185d6cc78704d0df27e9bb0d96f6c801e130c7b3be0f2c6ac46b696643c8d12bc7791713e9f73ab1851663a7ccde1066f3dad60f22688970af9474b241e40f537f12e995a724fdc2fff5debaa51f0847d6397cfd78282639dd850f2a92db9d7ce2d82c29860a4c57c198ff90c3b0e569c230f250708ef34393ac26a6f9fc3dc8bacec119569fd80512883dd7dbba5dfcb685c2082186d03cd8cda7a4680be8b9ff0316a5e3a4331c5d72f9ce90071ab39ebdb092e673430e9add99722197c8111d6e62193ccb3043ce02279fc630e7b28a49db6dff723c9908a6efe0054ac91ba45e4bc18d17dadc1d59b759381514f0445f8e8e3752ab162001fb6013be1e642ae2282182f46d10e917c04788a4a8daa4de9111918e41f273703981e95841b97f2f54603a2f66951dbd80b10cf5f49efc0a05d9c9d236a8ffd7f0851f0d75c471101f9ef24ff74a7440568208262bba60004f0a5fc485f3346d4e403a311b3d9d8239937cfc4a4c42348082e21596e57f658f488b900d4305072284f4675f13b3322fb0cc3c701f3a0d62e64ab1997b54d31d0b6a70088d6759f81b053586a3872b4e9665639e5591bb83379831d995c17d7f3f69f8b55e877fb0bc62aa44225ef3335683bd2f3760137e613c7fe4c6d2f1f95fa150f39ca6af52017bb73130e701380d02dd862ed8163fa206288198e5a7fdaca7a63c079670b139d9a3647166f34b316efb8bdaddae9386d4e52923da2b3677cb5d0c5d2c3d4b4df04d7f3ada623900ee313a1cf7e6e85a4e442e3e8a82b95c04502280b600c9f4a904ab61c30126e243f26c45b337239e3b74c821223775554fb9fb8b2688443f314ba8f978e52573a6689882bfc5ffa08294821355e58435a7db9f99e0c8727b1c83d8118f0b43f5ad371ce75be68905fcd603aef743b7258add41937595f7b0436ed31d197225c7bf5329848cb3ef592bebee6493e2392a5afc750efb618e89a83c7a4501d8d8eac5079be31a7eb1b7bbba966e4a9bcba349f3852b7a551de8eeadcabb68ec5a03c05cd4d64c7a4e4103e1fd98e40e36669cd86378bdfae011229b364d0e183fe7525664d0c4a9451015b8ca6d7b22fe5c2ff996e948924f0037cddb87fe4448603ad620d1e360aba88efcdefef6c1efb42288c7323fd7e9e677c960b59d82f6347150d2b9d23927dcf99167eb444484fcb975bedd0a7b1796139ac5e8e1fefddbc0d5d8edfe0ce3188546d019a3921d8f039f420c859240bb0e780d177a4ecbaa331512d4b9e337196396739b51f773a71a033f99788d5af0eb196f0fb7b9a57b88b87af26a8d89dd741d9d536b54e4d82b57d34243ff989daa4d2e3aafecdc4882177c49bdac026d41ca53d1117cb74eb769c5b14efa46d7a95eae2a48fcbc26bda8bab3578c5b93a497f05d5df5bcfcce19f6b16db77a7e42eeb48ffce6daeeaaa3a5f0e63d2016b537a4b65d79fc0ecf8a1524511332338c2aaced31e4228f36941cb4d9592fc968236f10f4dca62db83c6cd051ac4ec02a76f8b0544fe5984184d3d49ab639a988c669847a4e1ea391ef0d3b25ccc1ebbbb8960ad1b7ec72aaaffaee8c1ae4adb281ba24b15389c9b8cd6013899b52ce66dca0c51a8117e10930f6902ce21d6732992709e624804ed3fe03cf6627c3639c7eab52f1d02c2fc1dd5a13ce7c4fe272932d487765ddbadedc01a02fda4749b796ac262dc038f1fa25b43aa5371e402daaf5eb08efb19cf7251977acf1cf4b4a86494718ccab0b28461b48da4b62bf046ec71680d1d8ad74b3d40b8baf6ecbf91f8875388fd0a642613d4753555389c3092306d8ea1d31c39c154401e2ee7608b71ea50f8ca7d628211d32abef1f09b94da3ac8c3e5fa42227efa81fc9b561e250e2ffef7ec0b1caad9db541caa60a282f3863971aef57dc18f45cf69066b23fc95199f1d3223eaa800629651502eec0a24b8e84f9ace0160e02dfc91d5a51b105f74528442ba05732c125fd6d910105ab6a32a6d9e3003467ff4208d7c77ec0d01ef1f827f7bb062703f433ef427f8fa7b9a0b32be203ca2b19f3226008e8d8495eee8da7d87eb931490bebeca5aff12a9115cddc51886b3c7c02f3932afdd26c9d8857c7a952e4d781715b70af997a1b45790f5a4aeff706ab03f861936703027f1638a5c3375c3fd6cfb5068b0f5d9d1bab9e9f0db4e766ff94b440e806de9abe0f82a12fe70110157d05323490d60140d43c118baa11518480729b454ecad15f4e415f76388fc44c3f776c370d5a9c912f87c745b71a0659d3891143f3532865085d3e41f527334b6e8f8e0ff49d04af7dac150e4a2ed7b9ec7059e301fac58fcc2c1e6b6ce9eba5c6a3bcf92b2a31eca4ccb6039f7e984acbb8108d700bb42847d146b954002266ca244fd42e0bca1760a8454c83b8dbda7ef424a34ced7e6fa944bb75c6d1d2d0d84252a010a5d74f7504e367bf62cf18eaf86354731d61f76dc15927a7b65d3c632c51155fc0772fd922852f9408fc36e36d2b3a048d9bf0cc76f6d6a5a9c1758ab98690ddfaa7651d9ee709e9159a18ea0d917d40bf918452bf34cfe8ee29775fecea76c3b04d9 -------------------------------------------------------------------------------- /benches/bench/data_2048.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaniPopes/const-hex/3c08e80a20e72cf6133c59a2007224d5f22d9b3e/benches/bench/data_2048.bin -------------------------------------------------------------------------------- /benches/bench/data_2048.hex: -------------------------------------------------------------------------------- 1 | 0fc02ff689e43901c6162c78702aedf232722be2d259276af9183503ed62c621b10a1a321cfd13fc4a5b881503e6192a133694425718897bed94fa3cd0105e5d38160ee5441f4db64039a55421e80e713112efab891ae0fd00b941257b47a77031a9abcc559e26858e2b624c195b14becf4f134c8907a37bd39a3e4d297a6e4e7537fb4492a71655b752dbb836422507fde5be62baf4816305c3902c8033034de83776a82dc13dc90638eb107a619b57deea36410a12704f9731d9a2abbc11303b487fda756456e884a0bf56409b86006f3c8be024816fcd27d08a685719bffe4fcc14043e075f031382b79e1128a813f6dcf98d8f1ebbb89ba69e0d31cd37f19b26f5cadeb13391f4197c303ef09f15c38ca87b058d6f3ab8efb075a443ae4922c6284ca88c77f84dfe1cdf6b05e5efcb3c1d4d0b9b3f405af9a9671314aef427848e20239f1fa38f7d35dce64bcea6fd3136cb24347be98678b979645d1285737a4eae249529e3018365cfaf3200156edd297acd6e564b957c0ff003950e81f1239124b834d2dae747ff107874145534e22d45520d44092c24088c8feb6a694010d5d2ffdd86a5bc0bfb45d752c52e37929320d76cfc7d7a2133e18ab475c1fabcc4c083ff7da54c61851c5829ca56df8a6d12a4fc9a49339efd1743af55977e5b9e5e15c2bdd338a1972ca8bd73de075d52654c41b4fb55468085bba2ddc7e83d78fa8ebaab4cdd3f310c3b3f902ece2ed77bb87b7d97819bc22aec8b44cbe81971f3ded380a519adbc9cbccfddea17499b54fa9049c14515b5c03e8a326a9d07453a01e4219e1811f18401a6e4761f4033758caf7b2e891fe033e835b26c61918a911cd545f78a7cac2fe132a3babb9779934def20227bb9c26e4dc2f389f8a26cb3782900a048928e173beee3886475954be07f0ad106356f13db48f6a665ea7c8abe5a7082d3abf76eea91334d64abc3d5c0b22a0807baca6b3c7540edd008c0165d7a30e891959b4c621ed41e539a584a84ff5cd3c63d0cdeb470a6320a6b22bf608b9a6f8304bfcb645664d6204a5cd18893e2cba4caf3757f77d672886f86e47523357c7bd9de5e67e3fdf9b7c7856e81eaa3bea395a1ffd8df07d568ef3c12781a5cf2ae46f9ad0289e2275f2d5fd906f1f206fac7110fe67c171a924cdd161865487baa0c513f1c96fa0e6d8c4220831089ee8c5da9bdb0aa477d2cddedaaeb9b37b52e9be39ee69ca6d9f8b7313d744d103e982356257e23ced55f146c70740140eae3edaf588435b10ae495b73000ce7d7197de13758d8e9efc0a9feb3f3070ce4a41c70f47e5a6932c5ab70452cb530803ad347bb441c0bab7b2094080410bf1afb6ff5f7a6a26a4801e191558eef4d6ef98c5e5c37de7a60a9054aee0330d2bbe207f30d65afb639cd45ca8334175a3f9c1dd550456c3ada66552f6dd1dba3cd7743f575a263314dc673b6cf27446e31cc9c0ecd2e7f57600d4461342619e551152b9265bc44617d0cc2427fb6fb1648cda2abce60e44e622e361507247eded0c8efae796100f57754c06043942bc630520c2ff68ac01078536ccb48e55c914c7ff814f2942b5b446caaf0a2f6ddd9ed49671b54e6a8716d6f2ad2c22929510a5bb2a93a12fcf090b135efc64e55a85dee138c6e6d5b709894c5ea124e3ff8c1c52caa65505ba322e581852a61a7136315e110fdeca5bc321d38f1a29b5a095fef5bc398907440c0cbe38077af82ab72bdf11288ef32a3da15a5b712bf9543ad4635f04d9c92c2e99b337d0228690b6f60fb3b92466df3c63f7cbb5fe13e71f0f3ff9cd4c3529f53fce8243ea411830cfd5c8cc3ee2c97d48fa1c2594a2e092c42b5d5f629f41a73a264c0b3b242fdab69c2c41e3872c897d85bb015e6ade219214a1f1ac045b57dfa954a72fc3e2f911f0f769f7ba6fce908e3ecc0b03308e8818ae1974ef1f073fe458defc4e705578d2dfc0ff12446fcf396a41c1c5f7ef4b8911e3d932c3b15ef74ea09302fd3619717203cb6bed50c3ef389d73fef89ce8e984e4fe57cc5f7bca4f00c7af4d30d83937d400a05e3735d768773d421dd1bfb04565ef9e68d380829d3b3e6e9f040ed2bfff381598d585f6db3f31f919d78b2facd11e3407f5c80b343640fdcc87cf277441d91e6b896cead899caf1dfda70981e89115951cec887c9bca857316748bf86361552f8e5402e43aa44a3cc1f6e0411e2bf111b3c10856f35e45cdd3822eec1dbef05b34a2a8727ee8c1e89be071e77f7b3efce7a72ebe349a28771c0926dcdb89cba3653bbaaec17d3804539bc09450198bafd486b494f906944ecb4f5b70e5f3fb4229b46958b0b9e7f797f85cff04211326f7595e0da859901c744cef262034449f108d994801d5f6d9edff32314230de2f626c7e20a787f02a00b0349e9cfc9752d2f0fbe2620a86cc8c4535ef86409b531a2318f4b4a4df089d8e2d5a9ebadc2983d2138e2a3865fb444bc521dc8fcb7055f921b575dd4c65b647306f2ad440a484f8c1ed890f68805587c28c5e402972ed85fe2fde17b0f282b14beb78982c8083606943adc7a28cf3125cca0c9c8bfc3a6f3d2bcdbe075ccd5041dace360e69275f6837d9323094966cd8106ab1f312fe88a31bc40b4f0a1a6d4276245a2f01818906189dc14be489d3c7f344d18305290a4ceca0e8bc904abe9537c6c77d7973636bc05c0fbc3971c3fc6506ca708dac253b29217b5cff623c127b6221cebab26d6e6a1927851a8ec4902a603b8dee1dd203e7523573c498f40816fe0d5e32b80ea4a9cdc9a420c21f65079a44e938d798efdb593aff824dfa97488513763674837a97d2b16ce7d98d4345692aecddcbdd3915ad237213e2b7c513ffef4e7211634 -------------------------------------------------------------------------------- /benches/bench/data_256.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaniPopes/const-hex/3c08e80a20e72cf6133c59a2007224d5f22d9b3e/benches/bench/data_256.bin -------------------------------------------------------------------------------- /benches/bench/data_256.hex: -------------------------------------------------------------------------------- 1 | ac7dc7a70eeb77979246e0d3beb1fb887c2bc61ce2f6ebba47c265a574e9d115ba2c36708286497acfa7c95f9da2557cbb8fd99946f83657dcbe4f6e4ed5941cd404aada86e71468cbc662a504da21e677d713d4b7860cf0810d58544269c96ca65edf15edbd4535854c9b7a01a68bab90f7d8aa37feaa020997b605159f10f6b07275ec936d465c151142cd02c9d5a4eeb4baa07a98d4af3f4680d23a47729288263df1a63c9d51fb827fee4bd19cf612f749b501f97205744da64a085a284428f07ea82b19c7589527b618ac632cd254342f2bc7868c0af51cf634538b78496ccd9a8c248bf05ebe69f5ecc0ba67830cb29b06626cb2a1f048f7954f6cba90 -------------------------------------------------------------------------------- /benches/bench/data_32.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaniPopes/const-hex/3c08e80a20e72cf6133c59a2007224d5f22d9b3e/benches/bench/data_32.bin -------------------------------------------------------------------------------- /benches/bench/data_32.hex: -------------------------------------------------------------------------------- 1 | 17a5569d2dbd36013a5de79007700f2265e7191301eccc6ce3125a6d11d70006 -------------------------------------------------------------------------------- /benches/bench/gen_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import random 4 | 5 | ROOT = "benches/bench" 6 | 7 | enc_rs = [] 8 | dec_rs = [] 9 | for i in [32, 256, 2048, 16384, 131072, 1048576]: 10 | data = bytearray([random.randint(0, 255) for _ in range(i)]) 11 | data_bin_fname = f"data_{i}.bin" 12 | open(f"{ROOT}/{data_bin_fname}", "wb").write(data) 13 | data_hex_fname = f"data_{i}.hex" 14 | open(f"{ROOT}/{data_hex_fname}", "wt").write(data.hex()) 15 | 16 | enc_rs.append( 17 | f'pub const ENC_{i}: &[u8; {i}] = include_bytes!("./{data_bin_fname}");' 18 | ) 19 | dec_rs.append(f'pub const DEC_{i}: &str = include_str!("./{data_hex_fname}");') 20 | 21 | enc_rs = "\n".join(enc_rs) 22 | dec_rs = "\n".join(dec_rs) 23 | data_rs = f"""\ 24 | // @generated by gen_data.py, do not modify manually 25 | 26 | {enc_rs} 27 | 28 | {dec_rs} 29 | """ 30 | open(f"{ROOT}/data.rs", "w").write(data_rs) 31 | -------------------------------------------------------------------------------- /benches/bench/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate test; 4 | 5 | #[rustfmt::skip] 6 | mod data; 7 | 8 | use std::fmt; 9 | use std::io::Write; 10 | use test::{black_box, Bencher}; 11 | 12 | struct HexBufferFormat(&'static [u8; N]); 13 | impl fmt::Display for HexBufferFormat { 14 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 15 | let mut buffer = const_hex::Buffer::::new(); 16 | f.write_str(buffer.format(self.0)) 17 | } 18 | } 19 | 20 | struct StdFormat(&'static [u8; N]); 21 | impl fmt::Display for StdFormat { 22 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 23 | for &byte in self.0 { 24 | write!(f, "{byte:02x}")?; 25 | } 26 | Ok(()) 27 | } 28 | } 29 | 30 | macro_rules! benches { 31 | ($($name:ident($enc:expr, $dec:expr))*) => { 32 | mod check { 33 | use super::*; 34 | 35 | mod const_hex { 36 | use super::*; 37 | 38 | $( 39 | #[bench] 40 | fn $name(b: &mut Bencher) { 41 | b.iter(|| { 42 | ::const_hex::check_raw(black_box($dec)) 43 | }); 44 | } 45 | )* 46 | } 47 | 48 | mod faster_hex { 49 | use super::*; 50 | 51 | $( 52 | #[bench] 53 | fn $name(b: &mut Bencher) { 54 | b.iter(|| { 55 | ::faster_hex::hex_check(black_box($dec.as_bytes())) 56 | }); 57 | } 58 | )* 59 | } 60 | 61 | mod naive { 62 | use super::*; 63 | 64 | $( 65 | #[bench] 66 | fn $name(b: &mut Bencher) { 67 | b.iter(|| { 68 | let dec = black_box($dec.as_bytes()); 69 | dec.iter().all(u8::is_ascii_hexdigit) 70 | }); 71 | } 72 | )* 73 | } 74 | } 75 | 76 | #[cfg(feature = "alloc")] 77 | mod decode { 78 | use super::*; 79 | 80 | mod const_hex { 81 | use super::*; 82 | 83 | $( 84 | #[bench] 85 | fn $name(b: &mut Bencher) { 86 | b.iter(|| { 87 | ::const_hex::decode(black_box($dec)) 88 | }); 89 | } 90 | )* 91 | } 92 | 93 | mod faster_hex { 94 | use super::*; 95 | 96 | $( 97 | #[bench] 98 | fn $name(b: &mut Bencher) { 99 | b.iter(|| { 100 | const L: usize = $dec.len() / 2; 101 | let mut buf = vec![0; L]; 102 | ::faster_hex::hex_decode(black_box($dec.as_bytes()), black_box(&mut buf)).unwrap(); 103 | unsafe { String::from_utf8_unchecked(buf) } 104 | }); 105 | } 106 | )* 107 | } 108 | 109 | mod hex { 110 | use super::*; 111 | 112 | $( 113 | #[bench] 114 | fn $name(b: &mut Bencher) { 115 | b.iter(|| { 116 | ::hex::decode(black_box($dec)) 117 | }); 118 | } 119 | )* 120 | } 121 | 122 | mod rustc_hex { 123 | use super::*; 124 | 125 | $( 126 | #[bench] 127 | fn $name(b: &mut Bencher) { 128 | b.iter(|| { 129 | ::rustc_hex::FromHex::from_hex::>(black_box($dec)) 130 | }); 131 | } 132 | )* 133 | } 134 | } 135 | 136 | mod decode_to_slice { 137 | use super::*; 138 | 139 | mod const_hex { 140 | use super::*; 141 | 142 | $( 143 | #[bench] 144 | fn $name(b: &mut Bencher) { 145 | let buf = &mut [0; $dec.len() / 2]; 146 | b.iter(|| { 147 | let res = ::const_hex::decode_to_slice(black_box($dec), black_box(buf)); 148 | black_box(res.unwrap()); 149 | }); 150 | } 151 | )* 152 | } 153 | 154 | mod faster_hex { 155 | use super::*; 156 | 157 | $( 158 | #[bench] 159 | fn $name(b: &mut Bencher) { 160 | let buf = &mut [0; $dec.len() / 2]; 161 | b.iter(|| { 162 | ::faster_hex::hex_decode(black_box($dec.as_bytes()), black_box(buf)) 163 | }) 164 | } 165 | )* 166 | } 167 | 168 | mod hex { 169 | use super::*; 170 | 171 | $( 172 | #[bench] 173 | fn $name(b: &mut Bencher) { 174 | let buf = &mut [0; $dec.len() / 2]; 175 | b.iter(|| { 176 | ::hex::decode_to_slice(black_box($dec), black_box(buf)) 177 | }); 178 | } 179 | )* 180 | } 181 | } 182 | 183 | #[cfg(feature = "alloc")] 184 | mod encode { 185 | use super::*; 186 | 187 | mod const_hex { 188 | use super::*; 189 | 190 | $( 191 | #[bench] 192 | fn $name(b: &mut Bencher) { 193 | b.iter(|| { 194 | ::const_hex::encode(black_box($enc)) 195 | }); 196 | } 197 | )* 198 | } 199 | 200 | mod faster_hex { 201 | use super::*; 202 | 203 | $( 204 | #[bench] 205 | fn $name(b: &mut Bencher) { 206 | b.iter(|| { 207 | ::faster_hex::hex_string(black_box($enc)) 208 | }); 209 | } 210 | )* 211 | } 212 | 213 | mod hex { 214 | use super::*; 215 | 216 | $( 217 | #[bench] 218 | fn $name(b: &mut Bencher) { 219 | b.iter(|| { 220 | ::hex::encode(black_box($enc)) 221 | }); 222 | } 223 | )* 224 | } 225 | 226 | mod rustc_hex { 227 | use super::*; 228 | 229 | $( 230 | #[bench] 231 | fn $name(b: &mut Bencher) { 232 | b.iter(|| { 233 | ::rustc_hex::ToHex::to_hex::(&black_box($enc)[..]) 234 | }); 235 | } 236 | )* 237 | } 238 | } 239 | 240 | mod encode_to_slice { 241 | use super::*; 242 | 243 | mod const_hex { 244 | use super::*; 245 | 246 | $( 247 | #[bench] 248 | fn $name(b: &mut Bencher) { 249 | let buf = &mut [0; $enc.len() * 2]; 250 | b.iter(|| { 251 | ::const_hex::encode_to_slice(black_box($enc), black_box(buf)) 252 | }); 253 | } 254 | )* 255 | } 256 | 257 | mod faster_hex { 258 | use super::*; 259 | 260 | $( 261 | #[bench] 262 | fn $name(b: &mut Bencher) { 263 | let buf = &mut [0; $enc.len() * 2]; 264 | b.iter(|| { 265 | ::faster_hex::hex_encode(black_box($enc), black_box(buf)).map(drop) 266 | }); 267 | } 268 | )* 269 | } 270 | 271 | mod hex { 272 | use super::*; 273 | 274 | $( 275 | #[bench] 276 | fn $name(b: &mut Bencher) { 277 | let buf = &mut [0; $enc.len() * 2]; 278 | b.iter(|| { 279 | ::hex::encode_to_slice(black_box($enc), black_box(buf)) 280 | }); 281 | } 282 | )* 283 | } 284 | } 285 | 286 | mod format { 287 | use super::*; 288 | 289 | mod const_hex { 290 | use super::*; 291 | 292 | $( 293 | #[bench] 294 | fn $name(b: &mut Bencher) { 295 | let mut buf = Vec::with_capacity($enc.len() * 2); 296 | b.iter(|| { 297 | buf.clear(); 298 | write!(&mut buf, "{}", HexBufferFormat(black_box($enc))) 299 | }); 300 | } 301 | )* 302 | } 303 | 304 | mod std { 305 | use super::*; 306 | 307 | $( 308 | #[bench] 309 | fn $name(b: &mut Bencher) { 310 | let mut buf = Vec::with_capacity($enc.len() * 2); 311 | b.iter(|| { 312 | buf.clear(); 313 | write!(&mut buf, "{}", StdFormat(black_box($enc))) 314 | }); 315 | } 316 | )* 317 | } 318 | } 319 | } 320 | } 321 | 322 | benches! { 323 | bench1_32b(data::ENC_32, data::DEC_32) 324 | bench2_256b(data::ENC_256, data::DEC_256) 325 | bench3_2k(data::ENC_2048, data::DEC_2048) 326 | bench4_16k(data::ENC_16384, data::DEC_16384) 327 | bench5_128k(data::ENC_131072, data::DEC_131072) 328 | bench6_1m(data::ENC_1048576, data::DEC_1048576) 329 | } 330 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | msrv = "1.64.0" 2 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | artifacts/ 2 | corpus/ 3 | coverage/ 4 | target/ 5 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "const-hex-fuzz" 3 | version = "0.0.0" 4 | authors = ["DaniPopes <57450786+DaniPopes@users.noreply.github.com>"] 5 | edition = "2021" 6 | publish = false 7 | 8 | [package.metadata] 9 | cargo-fuzz = true 10 | 11 | [dependencies] 12 | const-hex = { path = "..", features = ["__fuzzing"] } 13 | libfuzzer-sys = "0.4" 14 | 15 | [[bin]] 16 | name = "fuzz_const_hex" 17 | path = "fuzz_targets/fuzz_const_hex.rs" 18 | test = false 19 | doc = false 20 | 21 | [features] 22 | portable-simd = ["const-hex/portable-simd"] 23 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/fuzz_const_hex.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use libfuzzer_sys::fuzz_target; 4 | 5 | fuzz_target!(|data: &[u8]| { 6 | const_hex::fuzzing::fuzz(data).unwrap(); 7 | }); 8 | -------------------------------------------------------------------------------- /src/arch/aarch64.rs: -------------------------------------------------------------------------------- 1 | #![allow(unsafe_op_in_unsafe_fn)] 2 | 3 | use super::generic; 4 | use crate::get_chars_table; 5 | use core::arch::aarch64::*; 6 | 7 | pub(crate) const USE_CHECK_FN: bool = true; 8 | 9 | cfg_if::cfg_if! { 10 | if #[cfg(feature = "std")] { 11 | #[inline(always)] 12 | fn has_neon() -> bool { 13 | std::arch::is_aarch64_feature_detected!("neon") 14 | } 15 | } else { 16 | #[inline(always)] 17 | fn has_neon() -> bool { 18 | cfg!(target_feature = "neon") 19 | } 20 | } 21 | } 22 | 23 | #[inline] 24 | pub(crate) unsafe fn encode(input: &[u8], output: *mut u8) { 25 | if cfg!(miri) || !has_neon() { 26 | return generic::encode::(input, output); 27 | } 28 | encode_neon::(input, output); 29 | } 30 | 31 | #[target_feature(enable = "neon")] 32 | pub(crate) unsafe fn encode_neon(input: &[u8], output: *mut u8) { 33 | // Load table. 34 | let hex_table = vld1q_u8(get_chars_table::().as_ptr()); 35 | 36 | generic::encode_unaligned_chunks::(input, output, |chunk: uint8x16_t| { 37 | // Load input bytes and mask to nibbles. 38 | let mut lo = vandq_u8(chunk, vdupq_n_u8(0x0F)); 39 | let mut hi = vshrq_n_u8(chunk, 4); 40 | 41 | // Lookup the corresponding ASCII hex digit for each nibble. 42 | lo = vqtbl1q_u8(hex_table, lo); 43 | hi = vqtbl1q_u8(hex_table, hi); 44 | 45 | // Interleave the nibbles ([hi[0], lo[0], hi[1], lo[1], ...]). 46 | let hex_lo = vzip1q_u8(hi, lo); 47 | let hex_hi = vzip2q_u8(hi, lo); 48 | (hex_lo, hex_hi) 49 | }); 50 | } 51 | 52 | #[inline] 53 | pub(crate) fn check(input: &[u8]) -> bool { 54 | if cfg!(miri) || !has_neon() { 55 | return generic::check(input); 56 | } 57 | unsafe { check_neon(input) } 58 | } 59 | 60 | #[target_feature(enable = "neon")] 61 | pub(crate) unsafe fn check_neon(input: &[u8]) -> bool { 62 | generic::check_unaligned_chunks(input, |chunk: uint8x16_t| { 63 | let ge0 = vcgeq_u8(chunk, vdupq_n_u8(b'0')); 64 | let le9 = vcleq_u8(chunk, vdupq_n_u8(b'9')); 65 | let valid_digit = vandq_u8(ge0, le9); 66 | 67 | let geua = vcgeq_u8(chunk, vdupq_n_u8(b'A')); 68 | let leuf = vcleq_u8(chunk, vdupq_n_u8(b'F')); 69 | let valid_upper = vandq_u8(geua, leuf); 70 | 71 | let gela = vcgeq_u8(chunk, vdupq_n_u8(b'a')); 72 | let lelf = vcleq_u8(chunk, vdupq_n_u8(b'f')); 73 | let valid_lower = vandq_u8(gela, lelf); 74 | 75 | let valid_letter = vorrq_u8(valid_lower, valid_upper); 76 | let valid_mask = vorrq_u8(valid_digit, valid_letter); 77 | vminvq_u8(valid_mask) == 0xFF 78 | }) 79 | } 80 | 81 | pub(crate) use generic::decode_checked; 82 | pub(crate) use generic::decode_unchecked; 83 | -------------------------------------------------------------------------------- /src/arch/generic.rs: -------------------------------------------------------------------------------- 1 | use crate::{byte2hex, HEX_DECODE_LUT, NIL}; 2 | 3 | /// Set to `true` to use `check` + `decode_unchecked` for decoding. Otherwise uses `decode_checked`. 4 | /// 5 | /// This should be set to `false` if `check` is not specialized. 6 | #[allow(dead_code)] 7 | pub(crate) const USE_CHECK_FN: bool = false; 8 | 9 | /// Default encoding function. 10 | /// 11 | /// # Safety 12 | /// 13 | /// `output` must be a valid pointer to at least `2 * input.len()` bytes. 14 | pub(crate) unsafe fn encode(input: &[u8], output: *mut u8) { 15 | for (i, byte) in input.iter().enumerate() { 16 | let (high, low) = byte2hex::(*byte); 17 | unsafe { 18 | output.add(i * 2).write(high); 19 | output.add(i * 2 + 1).write(low); 20 | } 21 | } 22 | } 23 | 24 | /// Encodes unaligned chunks of `T` in `input` to `output` using `encode_chunk`. 25 | /// 26 | /// The remainder is encoded using the generic [`encode`]. 27 | #[inline] 28 | #[allow(dead_code)] 29 | pub(crate) unsafe fn encode_unaligned_chunks( 30 | input: &[u8], 31 | output: *mut u8, 32 | mut encode_chunk: impl FnMut(T) -> (T, T), 33 | ) { 34 | let (chunks, remainder) = chunks_unaligned::(input); 35 | let n_in_chunks = chunks.len(); 36 | let chunk_output = output.cast::(); 37 | for (i, chunk) in chunks.enumerate() { 38 | let (lo, hi) = encode_chunk(chunk); 39 | unsafe { 40 | chunk_output.add(i * 2).write_unaligned(lo); 41 | chunk_output.add(i * 2 + 1).write_unaligned(hi); 42 | } 43 | } 44 | let n_out_chunks = n_in_chunks * 2; 45 | unsafe { encode::(remainder, unsafe { chunk_output.add(n_out_chunks).cast() }) }; 46 | } 47 | 48 | /// Default check function. 49 | #[inline] 50 | pub(crate) const fn check(mut input: &[u8]) -> bool { 51 | while let &[byte, ref rest @ ..] = input { 52 | if HEX_DECODE_LUT[byte as usize] == NIL { 53 | return false; 54 | } 55 | input = rest; 56 | } 57 | true 58 | } 59 | 60 | /// Runs the given check function on unaligned chunks of `T` in `input`, with the remainder passed 61 | /// to the generic [`check`]. 62 | #[inline] 63 | #[allow(dead_code)] 64 | pub(crate) fn check_unaligned_chunks( 65 | input: &[u8], 66 | check_chunk: impl FnMut(T) -> bool, 67 | ) -> bool { 68 | let (mut chunks, remainder) = chunks_unaligned(input); 69 | chunks.all(check_chunk) && check(remainder) 70 | } 71 | 72 | /// Default checked decoding function. 73 | /// 74 | /// # Safety 75 | /// 76 | /// Assumes `output.len() == input.len() / 2`. 77 | pub(crate) unsafe fn decode_checked(input: &[u8], output: &mut [u8]) -> bool { 78 | unsafe { decode_maybe_check::(input, output) } 79 | } 80 | 81 | /// Default unchecked decoding function. 82 | /// 83 | /// # Safety 84 | /// 85 | /// Assumes `output.len() == input.len() / 2` and that the input is valid hex. 86 | pub(crate) unsafe fn decode_unchecked(input: &[u8], output: &mut [u8]) { 87 | #[allow(unused_braces)] // False positive on older rust versions. 88 | let success = unsafe { decode_maybe_check::<{ cfg!(debug_assertions) }>(input, output) }; 89 | debug_assert!(success); 90 | } 91 | 92 | /// Default decoding function. Checks input validity if `CHECK` is `true`, otherwise assumes it. 93 | /// 94 | /// # Safety 95 | /// 96 | /// Assumes `output.len() == input.len() / 2` and that the input is valid hex if `CHECK` is `true`. 97 | #[inline(always)] 98 | unsafe fn decode_maybe_check(input: &[u8], output: &mut [u8]) -> bool { 99 | macro_rules! next { 100 | ($var:ident, $i:expr) => { 101 | let hex = unsafe { *input.get_unchecked($i) }; 102 | let $var = HEX_DECODE_LUT[hex as usize]; 103 | if CHECK { 104 | if $var == NIL { 105 | return false; 106 | } 107 | } 108 | }; 109 | } 110 | 111 | debug_assert_eq!(output.len(), input.len() / 2); 112 | let mut i = 0; 113 | while i < output.len() { 114 | next!(high, i * 2); 115 | next!(low, i * 2 + 1); 116 | output[i] = high << 4 | low; 117 | i += 1; 118 | } 119 | true 120 | } 121 | 122 | #[inline] 123 | fn chunks_unaligned(input: &[u8]) -> (impl ExactSizeIterator + '_, &[u8]) { 124 | let chunks = input.chunks_exact(core::mem::size_of::()); 125 | let remainder = chunks.remainder(); 126 | ( 127 | chunks.map(|chunk| unsafe { chunk.as_ptr().cast::().read_unaligned() }), 128 | remainder, 129 | ) 130 | } 131 | -------------------------------------------------------------------------------- /src/arch/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod generic; 2 | 3 | // The main implementation functions. 4 | cfg_if::cfg_if! { 5 | if #[cfg(feature = "force-generic")] { 6 | pub(crate) use generic as imp; 7 | } else if #[cfg(feature = "portable-simd")] { 8 | pub(crate) mod portable_simd; 9 | pub(crate) use portable_simd as imp; 10 | } else if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] { 11 | pub(crate) mod x86; 12 | pub(crate) use x86 as imp; 13 | } else if #[cfg(target_arch = "aarch64")] { 14 | pub(crate) mod aarch64; 15 | pub(crate) use aarch64 as imp; 16 | // See https://github.com/DaniPopes/const-hex/issues/17 17 | } else if #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))] { 18 | pub(crate) mod wasm32; 19 | pub(crate) use wasm32 as imp; 20 | } else { 21 | pub(crate) use generic as imp; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/arch/portable_simd.rs: -------------------------------------------------------------------------------- 1 | #![allow(unsafe_op_in_unsafe_fn)] 2 | 3 | use super::generic; 4 | use crate::get_chars_table; 5 | use core::simd::prelude::*; 6 | 7 | type Simd = u8x16; 8 | 9 | pub(crate) const USE_CHECK_FN: bool = true; 10 | 11 | pub(crate) unsafe fn encode(input: &[u8], output: *mut u8) { 12 | // Load table. 13 | let hex_table = Simd::from_array(*get_chars_table::()); 14 | 15 | generic::encode_unaligned_chunks::(input, output, |chunk: Simd| { 16 | // Load input bytes and mask to nibbles. 17 | let mut lo = chunk & Simd::splat(15); 18 | let mut hi = chunk >> Simd::splat(4); 19 | 20 | // Lookup the corresponding ASCII hex digit for each nibble. 21 | lo = hex_table.swizzle_dyn(lo); 22 | hi = hex_table.swizzle_dyn(hi); 23 | 24 | // Interleave the nibbles ([hi[0], lo[0], hi[1], lo[1], ...]). 25 | Simd::interleave(hi, lo) 26 | }); 27 | } 28 | 29 | pub(crate) fn check(input: &[u8]) -> bool { 30 | generic::check_unaligned_chunks(input, |chunk: Simd| { 31 | let valid_digit = chunk.simd_ge(Simd::splat(b'0')) & chunk.simd_le(Simd::splat(b'9')); 32 | let valid_upper = chunk.simd_ge(Simd::splat(b'A')) & chunk.simd_le(Simd::splat(b'F')); 33 | let valid_lower = chunk.simd_ge(Simd::splat(b'a')) & chunk.simd_le(Simd::splat(b'f')); 34 | let valid = valid_digit | valid_upper | valid_lower; 35 | valid.all() 36 | }) 37 | } 38 | 39 | pub(crate) use generic::decode_checked; 40 | pub(crate) use generic::decode_unchecked; 41 | -------------------------------------------------------------------------------- /src/arch/wasm32.rs: -------------------------------------------------------------------------------- 1 | #![allow(unsafe_op_in_unsafe_fn)] 2 | 3 | use super::generic; 4 | use crate::get_chars_table; 5 | use core::arch::wasm32::*; 6 | 7 | pub(crate) const USE_CHECK_FN: bool = true; 8 | 9 | #[inline] 10 | #[target_feature(enable = "simd128")] 11 | pub(crate) unsafe fn encode(input: &[u8], output: *mut u8) { 12 | // Load table. 13 | let hex_table = v128_load(get_chars_table::().as_ptr().cast()); 14 | 15 | generic::encode_unaligned_chunks::(input, output, |chunk: v128| { 16 | // Load input bytes and mask to nibbles. 17 | let mut lo = v128_and(chunk, u8x16_splat(0x0F)); 18 | let mut hi = u8x16_shr(chunk, 4); 19 | 20 | // Lookup the corresponding ASCII hex digit for each nibble. 21 | lo = u8x16_swizzle(hex_table, lo); 22 | hi = u8x16_swizzle(hex_table, hi); 23 | 24 | // Interleave the nibbles ([hi[0], lo[0], hi[1], lo[1], ...]). 25 | #[rustfmt::skip] 26 | let hex_lo = u8x16_shuffle::< 27 | 0, 16, 28 | 1, 17, 29 | 2, 18, 30 | 3, 19, 31 | 4, 20, 32 | 5, 21, 33 | 6, 22, 34 | 7, 23, 35 | >(hi, lo); 36 | #[rustfmt::skip] 37 | let hex_hi = u8x16_shuffle::< 38 | 8, 24, 39 | 9, 25, 40 | 10, 26, 41 | 11, 27, 42 | 12, 28, 43 | 13, 29, 44 | 14, 30, 45 | 15, 31, 46 | >(hi, lo); 47 | (hex_lo, hex_hi) 48 | }); 49 | } 50 | 51 | #[inline] 52 | #[target_feature(enable = "simd128")] 53 | pub(crate) fn check(input: &[u8]) -> bool { 54 | generic::check_unaligned_chunks(input, |chunk: v128| { 55 | let ge0 = u8x16_ge(chunk, u8x16_splat(b'0')); 56 | let le9 = u8x16_le(chunk, u8x16_splat(b'9')); 57 | let valid_digit = v128_and(ge0, le9); 58 | 59 | let geua = u8x16_ge(chunk, u8x16_splat(b'A')); 60 | let leuf = u8x16_le(chunk, u8x16_splat(b'F')); 61 | let valid_upper = v128_and(geua, leuf); 62 | 63 | let gela = u8x16_ge(chunk, u8x16_splat(b'a')); 64 | let lelf = u8x16_le(chunk, u8x16_splat(b'f')); 65 | let valid_lower = v128_and(gela, lelf); 66 | 67 | let valid_letter = v128_or(valid_lower, valid_upper); 68 | let valid = v128_or(valid_digit, valid_letter); 69 | u8x16_all_true(valid) 70 | }) 71 | } 72 | 73 | pub(crate) use generic::decode_checked; 74 | pub(crate) use generic::decode_unchecked; 75 | -------------------------------------------------------------------------------- /src/arch/x86.rs: -------------------------------------------------------------------------------- 1 | #![allow(unsafe_op_in_unsafe_fn)] 2 | #![allow(unexpected_cfgs)] 3 | 4 | use super::generic; 5 | use crate::get_chars_table; 6 | 7 | #[cfg(target_arch = "x86")] 8 | use core::arch::x86::*; 9 | #[cfg(target_arch = "x86_64")] 10 | use core::arch::x86_64::*; 11 | 12 | pub(crate) const USE_CHECK_FN: bool = true; 13 | const CHUNK_SIZE_AVX: usize = core::mem::size_of::<__m256i>(); 14 | 15 | cfg_if::cfg_if! { 16 | if #[cfg(feature = "std")] { 17 | #[inline(always)] 18 | fn has_sse2() -> bool { 19 | std::arch::is_x86_feature_detected!("sse2") 20 | } 21 | #[inline(always)] 22 | fn has_ssse3() -> bool { 23 | std::arch::is_x86_feature_detected!("ssse3") 24 | } 25 | #[inline(always)] 26 | fn has_avx2() -> bool { 27 | std::arch::is_x86_feature_detected!("avx2") 28 | } 29 | } else { 30 | cpufeatures::new!(cpuid_sse2, "sse2"); 31 | use cpuid_sse2::get as has_sse2; 32 | cpufeatures::new!(cpuid_ssse3, "ssse3"); 33 | use cpuid_ssse3::get as has_ssse3; 34 | cpufeatures::new!(cpuid_avx2, "avx2"); 35 | use cpuid_avx2::get as has_avx2; 36 | } 37 | } 38 | 39 | #[inline] 40 | pub(crate) unsafe fn encode(input: &[u8], output: *mut u8) { 41 | if !has_ssse3() { 42 | return generic::encode::(input, output); 43 | } 44 | encode_ssse3::(input, output); 45 | } 46 | 47 | #[target_feature(enable = "ssse3")] 48 | unsafe fn encode_ssse3(input: &[u8], output: *mut u8) { 49 | // Load table. 50 | let hex_table = _mm_loadu_si128(get_chars_table::().as_ptr().cast()); 51 | 52 | generic::encode_unaligned_chunks::(input, output, |chunk: __m128i| { 53 | // Load input bytes and mask to nibbles. 54 | let mut lo = _mm_and_si128(chunk, _mm_set1_epi8(0x0F)); 55 | #[allow(clippy::cast_possible_wrap)] 56 | let mut hi = _mm_srli_epi32::<4>(_mm_and_si128(chunk, _mm_set1_epi8(0xF0u8 as i8))); 57 | 58 | // Lookup the corresponding ASCII hex digit for each nibble. 59 | lo = _mm_shuffle_epi8(hex_table, lo); 60 | hi = _mm_shuffle_epi8(hex_table, hi); 61 | 62 | // Interleave the nibbles ([hi[0], lo[0], hi[1], lo[1], ...]). 63 | let hex_lo = _mm_unpacklo_epi8(hi, lo); 64 | let hex_hi = _mm_unpackhi_epi8(hi, lo); 65 | (hex_lo, hex_hi) 66 | }); 67 | } 68 | 69 | #[inline] 70 | pub(crate) fn check(input: &[u8]) -> bool { 71 | if !has_sse2() { 72 | return generic::check(input); 73 | } 74 | unsafe { check_sse2(input) } 75 | } 76 | 77 | /// Modified from [`faster-hex`](https://github.com/nervosnetwork/faster-hex/blob/856aba7b141a5fe16113fae110d535065882f25a/src/decode.rs). 78 | #[target_feature(enable = "sse2")] 79 | unsafe fn check_sse2(input: &[u8]) -> bool { 80 | let ascii_zero = _mm_set1_epi8((b'0' - 1) as i8); 81 | let ascii_nine = _mm_set1_epi8((b'9' + 1) as i8); 82 | let ascii_ua = _mm_set1_epi8((b'A' - 1) as i8); 83 | let ascii_uf = _mm_set1_epi8((b'F' + 1) as i8); 84 | let ascii_la = _mm_set1_epi8((b'a' - 1) as i8); 85 | let ascii_lf = _mm_set1_epi8((b'f' + 1) as i8); 86 | 87 | generic::check_unaligned_chunks(input, |chunk: __m128i| { 88 | let ge0 = _mm_cmpgt_epi8(chunk, ascii_zero); 89 | let le9 = _mm_cmplt_epi8(chunk, ascii_nine); 90 | let valid_digit = _mm_and_si128(ge0, le9); 91 | 92 | let geua = _mm_cmpgt_epi8(chunk, ascii_ua); 93 | let leuf = _mm_cmplt_epi8(chunk, ascii_uf); 94 | let valid_upper = _mm_and_si128(geua, leuf); 95 | 96 | let gela = _mm_cmpgt_epi8(chunk, ascii_la); 97 | let lelf = _mm_cmplt_epi8(chunk, ascii_lf); 98 | let valid_lower = _mm_and_si128(gela, lelf); 99 | 100 | let valid_letter = _mm_or_si128(valid_lower, valid_upper); 101 | let valid_mask = _mm_movemask_epi8(_mm_or_si128(valid_digit, valid_letter)); 102 | valid_mask == 0xffff 103 | }) 104 | } 105 | 106 | #[inline] 107 | pub(crate) unsafe fn decode_unchecked(input: &[u8], output: &mut [u8]) { 108 | if !has_avx2() { 109 | return generic::decode_unchecked(input, output); 110 | } 111 | decode_avx2(input, output); 112 | } 113 | 114 | /// Modified from [`faster-hex`](https://github.com/nervosnetwork/faster-hex/blob/856aba7b141a5fe16113fae110d535065882f25a/src/decode.rs). 115 | #[target_feature(enable = "avx2")] 116 | unsafe fn decode_avx2(mut input: &[u8], mut output: &mut [u8]) { 117 | #[rustfmt::skip] 118 | let mask_a = _mm256_setr_epi8( 119 | 0, -1, 2, -1, 4, -1, 6, -1, 8, -1, 10, -1, 12, -1, 14, -1, 120 | 0, -1, 2, -1, 4, -1, 6, -1, 8, -1, 10, -1, 12, -1, 14, -1, 121 | ); 122 | 123 | #[rustfmt::skip] 124 | let mask_b = _mm256_setr_epi8( 125 | 1, -1, 3, -1, 5, -1, 7, -1, 9, -1, 11, -1, 13, -1, 15, -1, 126 | 1, -1, 3, -1, 5, -1, 7, -1, 9, -1, 11, -1, 13, -1, 15, -1 127 | ); 128 | 129 | while output.len() >= CHUNK_SIZE_AVX { 130 | let av1 = _mm256_loadu_si256(input.as_ptr().cast()); 131 | let av2 = _mm256_loadu_si256(input.as_ptr().add(CHUNK_SIZE_AVX).cast()); 132 | 133 | let mut a1 = _mm256_shuffle_epi8(av1, mask_a); 134 | let mut b1 = _mm256_shuffle_epi8(av1, mask_b); 135 | let mut a2 = _mm256_shuffle_epi8(av2, mask_a); 136 | let mut b2 = _mm256_shuffle_epi8(av2, mask_b); 137 | 138 | a1 = unhex_avx2(a1); 139 | a2 = unhex_avx2(a2); 140 | b1 = unhex_avx2(b1); 141 | b2 = unhex_avx2(b2); 142 | 143 | let bytes = nib2byte_avx2(a1, b1, a2, b2); 144 | 145 | // dst does not need to be aligned on any particular boundary 146 | _mm256_storeu_si256(output.as_mut_ptr() as *mut _, bytes); 147 | output = output.get_unchecked_mut(32..); 148 | input = input.get_unchecked(64..); 149 | } 150 | 151 | generic::decode_unchecked(input, output); 152 | } 153 | 154 | #[inline] 155 | #[target_feature(enable = "avx2")] 156 | unsafe fn unhex_avx2(value: __m256i) -> __m256i { 157 | let sr6 = _mm256_srai_epi16(value, 6); 158 | let and15 = _mm256_and_si256(value, _mm256_set1_epi16(0xf)); 159 | let mul = _mm256_maddubs_epi16(sr6, _mm256_set1_epi16(9)); 160 | _mm256_add_epi16(mul, and15) 161 | } 162 | 163 | // (a << 4) | b; 164 | #[inline] 165 | #[target_feature(enable = "avx2")] 166 | unsafe fn nib2byte_avx2(a1: __m256i, b1: __m256i, a2: __m256i, b2: __m256i) -> __m256i { 167 | let a4_1 = _mm256_slli_epi16(a1, 4); 168 | let a4_2 = _mm256_slli_epi16(a2, 4); 169 | let a4orb_1 = _mm256_or_si256(a4_1, b1); 170 | let a4orb_2 = _mm256_or_si256(a4_2, b2); 171 | let pck1 = _mm256_packus_epi16(a4orb_1, a4orb_2); 172 | _mm256_permute4x64_epi64(pck1, 0b11011000) 173 | } 174 | 175 | // Not used. 176 | pub(crate) use generic::decode_checked; 177 | -------------------------------------------------------------------------------- /src/buffer.rs: -------------------------------------------------------------------------------- 1 | use crate::{byte2hex, imp}; 2 | use core::fmt; 3 | use core::slice; 4 | use core::str; 5 | 6 | #[cfg(feature = "alloc")] 7 | #[allow(unused_imports)] 8 | use alloc::{string::String, vec::Vec}; 9 | 10 | /// A correctly sized stack allocation for the formatted bytes to be written 11 | /// into. 12 | /// 13 | /// `N` is the amount of bytes of the input, while `PREFIX` specifies whether 14 | /// the "0x" prefix is prepended to the output. 15 | /// 16 | /// Note that this buffer will contain only the prefix, if specified, and null 17 | /// ('\0') bytes before any formatting is done. 18 | /// 19 | /// # Examples 20 | /// 21 | /// ``` 22 | /// let mut buffer = const_hex::Buffer::<4>::new(); 23 | /// let printed = buffer.format(b"1234"); 24 | /// assert_eq!(printed, "31323334"); 25 | /// ``` 26 | #[must_use] 27 | #[repr(C)] 28 | #[derive(Clone)] 29 | pub struct Buffer { 30 | // Workaround for Rust issue #76560: 31 | // https://github.com/rust-lang/rust/issues/76560 32 | // This would ideally be `[u8; (N + PREFIX as usize) * 2]` 33 | prefix: [u8; 2], 34 | bytes: [[u8; 2]; N], 35 | } 36 | 37 | impl Default for Buffer { 38 | #[inline] 39 | fn default() -> Self { 40 | Self::new() 41 | } 42 | } 43 | 44 | impl fmt::Debug for Buffer { 45 | #[inline] 46 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 47 | f.debug_tuple("Buffer").field(&self.as_str()).finish() 48 | } 49 | } 50 | 51 | impl Buffer { 52 | /// The length of the buffer in bytes. 53 | pub const LEN: usize = (N + PREFIX as usize) * 2; 54 | 55 | const ASSERT_SIZE: () = assert!(core::mem::size_of::() == 2 + N * 2, "invalid size"); 56 | const ASSERT_ALIGNMENT: () = assert!(core::mem::align_of::() == 1, "invalid alignment"); 57 | 58 | /// This is a cheap operation; you don't need to worry about reusing buffers 59 | /// for efficiency. 60 | #[inline] 61 | pub const fn new() -> Self { 62 | let () = Self::ASSERT_SIZE; 63 | let () = Self::ASSERT_ALIGNMENT; 64 | Self { 65 | prefix: if PREFIX { [b'0', b'x'] } else { [0, 0] }, 66 | bytes: [[0; 2]; N], 67 | } 68 | } 69 | 70 | /// Print an array of bytes into this buffer. 71 | #[inline] 72 | pub const fn const_format(self, array: &[u8; N]) -> Self { 73 | self.const_format_inner::(array) 74 | } 75 | 76 | /// Print an array of bytes into this buffer. 77 | #[inline] 78 | pub const fn const_format_upper(self, array: &[u8; N]) -> Self { 79 | self.const_format_inner::(array) 80 | } 81 | 82 | /// Same as `encode_to_slice_inner`, but const-stable. 83 | const fn const_format_inner(mut self, array: &[u8; N]) -> Self { 84 | let mut i = 0; 85 | while i < N { 86 | let (high, low) = byte2hex::(array[i]); 87 | self.bytes[i][0] = high; 88 | self.bytes[i][1] = low; 89 | i += 1; 90 | } 91 | self 92 | } 93 | 94 | /// Print an array of bytes into this buffer and return a reference to its 95 | /// *lower* hex string representation within the buffer. 96 | #[inline] 97 | pub fn format(&mut self, array: &[u8; N]) -> &mut str { 98 | // length of array is guaranteed to be N. 99 | self.format_inner::(array) 100 | } 101 | 102 | /// Print an array of bytes into this buffer and return a reference to its 103 | /// *upper* hex string representation within the buffer. 104 | #[inline] 105 | pub fn format_upper(&mut self, array: &[u8; N]) -> &mut str { 106 | // length of array is guaranteed to be N. 107 | self.format_inner::(array) 108 | } 109 | 110 | /// Print a slice of bytes into this buffer and return a reference to its 111 | /// *lower* hex string representation within the buffer. 112 | /// 113 | /// # Panics 114 | /// 115 | /// If the slice is not exactly `N` bytes long. 116 | #[track_caller] 117 | #[inline] 118 | pub fn format_slice>(&mut self, slice: T) -> &mut str { 119 | self.format_slice_inner::(slice.as_ref()) 120 | } 121 | 122 | /// Print a slice of bytes into this buffer and return a reference to its 123 | /// *upper* hex string representation within the buffer. 124 | /// 125 | /// # Panics 126 | /// 127 | /// If the slice is not exactly `N` bytes long. 128 | #[track_caller] 129 | #[inline] 130 | pub fn format_slice_upper>(&mut self, slice: T) -> &mut str { 131 | self.format_slice_inner::(slice.as_ref()) 132 | } 133 | 134 | // Checks length 135 | #[track_caller] 136 | fn format_slice_inner(&mut self, slice: &[u8]) -> &mut str { 137 | assert_eq!(slice.len(), N, "length mismatch"); 138 | self.format_inner::(slice) 139 | } 140 | 141 | // Doesn't check length 142 | #[inline] 143 | fn format_inner(&mut self, input: &[u8]) -> &mut str { 144 | // SAFETY: Length was checked previously; 145 | // we only write only ASCII bytes. 146 | unsafe { 147 | let buf = self.as_mut_bytes(); 148 | let output = buf.as_mut_ptr().add(PREFIX as usize * 2); 149 | imp::encode::(input, output); 150 | str::from_utf8_unchecked_mut(buf) 151 | } 152 | } 153 | 154 | /// Copies `self` into a new owned `String`. 155 | #[cfg(feature = "alloc")] 156 | #[inline] 157 | #[allow(clippy::inherent_to_string)] // this is intentional 158 | pub fn to_string(&self) -> String { 159 | // SAFETY: The buffer always contains valid UTF-8. 160 | unsafe { String::from_utf8_unchecked(self.as_bytes().to_vec()) } 161 | } 162 | 163 | /// Returns a reference to the underlying bytes casted to a string slice. 164 | #[inline] 165 | pub const fn as_str(&self) -> &str { 166 | // SAFETY: The buffer always contains valid UTF-8. 167 | unsafe { str::from_utf8_unchecked(self.as_bytes()) } 168 | } 169 | 170 | /// Returns a mutable reference to the underlying bytes casted to a string 171 | /// slice. 172 | #[inline] 173 | pub fn as_mut_str(&mut self) -> &mut str { 174 | // SAFETY: The buffer always contains valid UTF-8. 175 | unsafe { str::from_utf8_unchecked_mut(self.as_mut_bytes()) } 176 | } 177 | 178 | /// Copies `self` into a new `Vec`. 179 | #[cfg(feature = "alloc")] 180 | #[inline] 181 | pub fn to_vec(&self) -> Vec { 182 | self.as_bytes().to_vec() 183 | } 184 | 185 | /// Returns a reference the underlying stack-allocated byte array. 186 | /// 187 | /// # Panics 188 | /// 189 | /// If `LEN` does not equal `Self::LEN`. 190 | /// 191 | /// This is panic is evaluated at compile-time if the `nightly` feature 192 | /// is enabled, as inline `const` blocks are currently unstable. 193 | /// 194 | /// See Rust tracking issue [#76001](https://github.com/rust-lang/rust/issues/76001). 195 | #[inline] 196 | pub const fn as_byte_array(&self) -> &[u8; LEN] { 197 | maybe_const_assert!(LEN == Self::LEN, "`LEN` must be equal to `Self::LEN`"); 198 | // SAFETY: [u16; N] is layout-compatible with [u8; N * 2]. 199 | unsafe { &*self.as_ptr().cast::<[u8; LEN]>() } 200 | } 201 | 202 | /// Returns a mutable reference the underlying stack-allocated byte array. 203 | /// 204 | /// # Panics 205 | /// 206 | /// If `LEN` does not equal `Self::LEN`. 207 | /// 208 | /// See [`as_byte_array`](Buffer::as_byte_array) for more information. 209 | #[inline] 210 | pub fn as_mut_byte_array(&mut self) -> &mut [u8; LEN] { 211 | maybe_const_assert!(LEN == Self::LEN, "`LEN` must be equal to `Self::LEN`"); 212 | // SAFETY: [u16; N] is layout-compatible with [u8; N * 2]. 213 | unsafe { &mut *self.as_mut_ptr().cast::<[u8; LEN]>() } 214 | } 215 | 216 | /// Returns a reference to the underlying bytes. 217 | #[inline] 218 | pub const fn as_bytes(&self) -> &[u8] { 219 | // SAFETY: [u16; N] is layout-compatible with [u8; N * 2]. 220 | unsafe { slice::from_raw_parts(self.as_ptr(), Self::LEN) } 221 | } 222 | 223 | /// Returns a mutable reference to the underlying bytes. 224 | /// 225 | /// # Safety 226 | /// 227 | /// The caller must ensure that the content of the slice is valid UTF-8 228 | /// before the borrow ends and the underlying `str` is used. 229 | /// 230 | /// Use of a `str` whose contents are not valid UTF-8 is undefined behavior. 231 | #[inline] 232 | pub unsafe fn as_mut_bytes(&mut self) -> &mut [u8] { 233 | // SAFETY: [u16; N] is layout-compatible with [u8; N * 2]. 234 | unsafe { slice::from_raw_parts_mut(self.as_mut_ptr(), Self::LEN) } 235 | } 236 | 237 | /// Returns a mutable reference to the underlying buffer, excluding the prefix. 238 | /// 239 | /// # Safety 240 | /// 241 | /// See [`as_mut_bytes`](Buffer::as_mut_bytes). 242 | #[inline] 243 | pub unsafe fn buffer(&mut self) -> &mut [u8] { 244 | unsafe { slice::from_raw_parts_mut(self.bytes.as_mut_ptr().cast(), N * 2) } 245 | } 246 | 247 | /// Returns a raw pointer to the buffer. 248 | /// 249 | /// The caller must ensure that the buffer outlives the pointer this 250 | /// function returns, or else it will end up pointing to garbage. 251 | #[inline] 252 | pub const fn as_ptr(&self) -> *const u8 { 253 | unsafe { (self as *const Self).cast::().add(!PREFIX as usize * 2) } 254 | } 255 | 256 | /// Returns an unsafe mutable pointer to the slice's buffer. 257 | /// 258 | /// The caller must ensure that the slice outlives the pointer this 259 | /// function returns, or else it will end up pointing to garbage. 260 | #[inline] 261 | pub fn as_mut_ptr(&mut self) -> *mut u8 { 262 | unsafe { (self as *mut Self).cast::().add(!PREFIX as usize * 2) } 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | //! Modified from `hex::error`. 2 | 3 | use core::fmt; 4 | 5 | /// The error type for decoding a hex string into `Vec` or `[u8; N]`. 6 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 7 | #[allow(clippy::module_name_repetitions)] 8 | pub enum FromHexError { 9 | /// An invalid character was found. Valid ones are: `0...9`, `a...f` 10 | /// or `A...F`. 11 | #[allow(missing_docs)] 12 | InvalidHexCharacter { c: char, index: usize }, 13 | 14 | /// A hex string's length needs to be even, as two digits correspond to 15 | /// one byte. 16 | OddLength, 17 | 18 | /// If the hex string is decoded into a fixed sized container, such as an 19 | /// array, the hex string's length * 2 has to match the container's 20 | /// length. 21 | InvalidStringLength, 22 | } 23 | 24 | #[cfg(feature = "core-error")] 25 | impl core::error::Error for FromHexError {} 26 | #[cfg(all(feature = "std", not(feature = "core-error")))] 27 | impl std::error::Error for FromHexError {} 28 | 29 | impl fmt::Display for FromHexError { 30 | #[inline] 31 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 32 | match *self { 33 | Self::InvalidHexCharacter { c, index } => { 34 | write!(f, "invalid character {c:?} at position {index}") 35 | } 36 | Self::OddLength => f.write_str("odd number of digits"), 37 | Self::InvalidStringLength => f.write_str("invalid string length"), 38 | } 39 | } 40 | } 41 | 42 | #[cfg(all(test, feature = "alloc"))] 43 | mod tests { 44 | use super::*; 45 | use alloc::string::ToString; 46 | 47 | #[test] 48 | fn test_display() { 49 | assert_eq!( 50 | FromHexError::InvalidHexCharacter { c: '\n', index: 5 }.to_string(), 51 | "invalid character '\\n' at position 5" 52 | ); 53 | 54 | assert_eq!(FromHexError::OddLength.to_string(), "odd number of digits"); 55 | assert_eq!( 56 | FromHexError::InvalidStringLength.to_string(), 57 | "invalid string length" 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/impl_core.rs: -------------------------------------------------------------------------------- 1 | //! Modified implementations of unstable libcore functions. 2 | 3 | #![allow(dead_code)] 4 | 5 | use core::mem::{self, MaybeUninit}; 6 | 7 | /// `MaybeUninit::slice_assume_init_mut` 8 | #[inline(always)] 9 | pub(crate) unsafe fn slice_assume_init_mut(slice: &mut [MaybeUninit]) -> &mut [T] { 10 | // SAFETY: similar to safety notes for `slice_get_ref`, but we have a 11 | // mutable reference which is also guaranteed to be valid for writes. 12 | unsafe { &mut *(slice as *mut [MaybeUninit] as *mut [T]) } 13 | } 14 | 15 | /// `MaybeUninit::uninit_array` 16 | #[inline] 17 | pub(crate) const fn uninit_array() -> [MaybeUninit; N] { 18 | // SAFETY: An uninitialized `[MaybeUninit<_>; N]` is valid. 19 | unsafe { MaybeUninit::<[MaybeUninit; N]>::uninit().assume_init() } 20 | } 21 | 22 | /// `MaybeUninit::array_assume_init` 23 | #[inline] 24 | pub(crate) unsafe fn array_assume_init(array: [MaybeUninit; N]) -> [T; N] { 25 | // SAFETY: 26 | // * The caller guarantees that all elements of the array are initialized 27 | // * `MaybeUninit` and T are guaranteed to have the same layout 28 | // * `MaybeUninit` does not drop, so there are no double-frees 29 | // And thus the conversion is safe 30 | unsafe { transpose(array).assume_init() } 31 | } 32 | 33 | /// `MaybeUninit::transpose` 34 | #[inline(always)] 35 | unsafe fn transpose(array: [MaybeUninit; N]) -> MaybeUninit<[T; N]> { 36 | unsafe { 37 | mem::transmute_copy::<[MaybeUninit; N], MaybeUninit<[T; N]>>(&mem::ManuallyDrop::new( 38 | &array, 39 | )) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [![github]](https://github.com/danipopes/const-hex) [![crates-io]](https://crates.io/crates/const-hex) [![docs-rs]](https://docs.rs/const-hex) 2 | //! 3 | //! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github 4 | //! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust 5 | //! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs 6 | //! 7 | //! This crate provides a fast conversion of byte arrays to hexadecimal strings, 8 | //! both at compile time, and at run time. 9 | //! 10 | //! It aims to be a drop-in replacement for the [`hex`] crate, as well as 11 | //! extending the API with [const-eval](const_encode), a 12 | //! [const-generics formatting buffer](Buffer), similar to [`itoa`]'s, and more. 13 | //! 14 | //! _Version requirement: rustc 1.64+_ 15 | //! 16 | //! [`itoa`]: https://docs.rs/itoa/latest/itoa/struct.Buffer.html 17 | //! [`hex`]: https://docs.rs/hex 18 | 19 | #![cfg_attr(not(feature = "std"), no_std)] 20 | #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] 21 | #![cfg_attr( 22 | feature = "nightly", 23 | feature(core_intrinsics, inline_const), 24 | allow(internal_features, stable_features) 25 | )] 26 | #![cfg_attr(feature = "portable-simd", feature(portable_simd))] 27 | #![warn( 28 | missing_copy_implementations, 29 | missing_debug_implementations, 30 | missing_docs, 31 | unreachable_pub, 32 | unsafe_op_in_unsafe_fn, 33 | clippy::missing_const_for_fn, 34 | clippy::missing_inline_in_public_items, 35 | clippy::all, 36 | rustdoc::all 37 | )] 38 | #![cfg_attr(not(any(test, feature = "__fuzzing")), warn(unused_crate_dependencies))] 39 | #![deny(unused_must_use, rust_2018_idioms)] 40 | #![allow( 41 | clippy::cast_lossless, 42 | clippy::inline_always, 43 | clippy::let_unit_value, 44 | clippy::must_use_candidate, 45 | clippy::wildcard_imports, 46 | unsafe_op_in_unsafe_fn, 47 | unused_unsafe 48 | )] 49 | 50 | #[cfg(feature = "alloc")] 51 | #[allow(unused_imports)] 52 | #[macro_use] 53 | extern crate alloc; 54 | 55 | use cfg_if::cfg_if; 56 | 57 | #[cfg(feature = "alloc")] 58 | #[allow(unused_imports)] 59 | use alloc::{string::String, vec::Vec}; 60 | 61 | // `cpufeatures` may be unused when `force-generic` is enabled. 62 | #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 63 | use cpufeatures as _; 64 | 65 | mod arch; 66 | use arch::{generic, imp}; 67 | 68 | mod impl_core; 69 | 70 | pub mod traits; 71 | #[cfg(feature = "alloc")] 72 | pub use traits::ToHexExt; 73 | 74 | // If the `hex` feature is enabled, re-export the `hex` crate's traits. 75 | // Otherwise, use our own with the more optimized implementation. 76 | cfg_if! { 77 | if #[cfg(feature = "hex")] { 78 | pub use hex; 79 | #[doc(inline)] 80 | pub use hex::{FromHex, FromHexError, ToHex}; 81 | } else { 82 | mod error; 83 | pub use error::FromHexError; 84 | 85 | #[allow(deprecated)] 86 | pub use traits::{FromHex, ToHex}; 87 | } 88 | } 89 | 90 | // Support for nightly features. 91 | cfg_if! { 92 | if #[cfg(feature = "nightly")] { 93 | // Branch prediction hints. 94 | #[allow(unused_imports)] 95 | use core::intrinsics::{likely, unlikely}; 96 | 97 | // `inline_const`: [#76001](https://github.com/rust-lang/rust/issues/76001) 98 | macro_rules! maybe_const_assert { 99 | ($($tt:tt)*) => { 100 | const { assert!($($tt)*) } 101 | }; 102 | } 103 | } else { 104 | #[allow(unused_imports)] 105 | use core::convert::{identity as likely, identity as unlikely}; 106 | 107 | macro_rules! maybe_const_assert { 108 | ($($tt:tt)*) => { 109 | assert!($($tt)*) 110 | }; 111 | } 112 | } 113 | } 114 | 115 | // Serde support. 116 | cfg_if! { 117 | if #[cfg(feature = "serde")] { 118 | pub mod serde; 119 | 120 | #[doc(no_inline)] 121 | pub use self::serde::deserialize; 122 | #[cfg(feature = "alloc")] 123 | #[doc(no_inline)] 124 | pub use self::serde::{serialize, serialize_upper}; 125 | } 126 | } 127 | 128 | mod buffer; 129 | pub use buffer::Buffer; 130 | 131 | /// The table of lowercase characters used for hex encoding. 132 | pub const HEX_CHARS_LOWER: &[u8; 16] = b"0123456789abcdef"; 133 | 134 | /// The table of uppercase characters used for hex encoding. 135 | pub const HEX_CHARS_UPPER: &[u8; 16] = b"0123456789ABCDEF"; 136 | 137 | /// The lookup table of hex byte to value, used for hex decoding. 138 | /// 139 | /// [`NIL`] is used for invalid values. 140 | pub const HEX_DECODE_LUT: &[u8; 256] = &make_decode_lut(); 141 | 142 | /// Represents an invalid value in the [`HEX_DECODE_LUT`] table. 143 | pub const NIL: u8 = u8::MAX; 144 | 145 | /// Encodes `input` as a hex string into a [`Buffer`]. 146 | /// 147 | /// # Examples 148 | /// 149 | /// ``` 150 | /// const BUFFER: const_hex::Buffer<4> = const_hex::const_encode(b"kiwi"); 151 | /// assert_eq!(BUFFER.as_str(), "6b697769"); 152 | /// ``` 153 | #[inline] 154 | pub const fn const_encode( 155 | input: &[u8; N], 156 | ) -> Buffer { 157 | Buffer::new().const_format(input) 158 | } 159 | 160 | /// Encodes `input` as a hex string using lowercase characters into a mutable 161 | /// slice of bytes `output`. 162 | /// 163 | /// # Errors 164 | /// 165 | /// If the output buffer is not exactly `input.len() * 2` bytes long. 166 | /// 167 | /// # Examples 168 | /// 169 | /// ``` 170 | /// let mut bytes = [0u8; 4 * 2]; 171 | /// const_hex::encode_to_slice(b"kiwi", &mut bytes)?; 172 | /// assert_eq!(&bytes, b"6b697769"); 173 | /// # Ok::<_, const_hex::FromHexError>(()) 174 | /// ``` 175 | #[inline] 176 | pub fn encode_to_slice>(input: T, output: &mut [u8]) -> Result<(), FromHexError> { 177 | encode_to_slice_inner::(input.as_ref(), output) 178 | } 179 | 180 | /// Encodes `input` as a hex string using uppercase characters into a mutable 181 | /// slice of bytes `output`. 182 | /// 183 | /// # Errors 184 | /// 185 | /// If the output buffer is not exactly `input.len() * 2` bytes long. 186 | /// 187 | /// # Examples 188 | /// 189 | /// ``` 190 | /// let mut bytes = [0u8; 4 * 2]; 191 | /// const_hex::encode_to_slice_upper(b"kiwi", &mut bytes)?; 192 | /// assert_eq!(&bytes, b"6B697769"); 193 | /// # Ok::<_, const_hex::FromHexError>(()) 194 | /// ``` 195 | #[inline] 196 | pub fn encode_to_slice_upper>( 197 | input: T, 198 | output: &mut [u8], 199 | ) -> Result<(), FromHexError> { 200 | encode_to_slice_inner::(input.as_ref(), output) 201 | } 202 | 203 | /// Encodes `data` as a hex string using lowercase characters. 204 | /// 205 | /// Lowercase characters are used (e.g. `f9b4ca`). The resulting string's 206 | /// length is always even, each byte in `data` is always encoded using two hex 207 | /// digits. Thus, the resulting string contains exactly twice as many bytes as 208 | /// the input data. 209 | /// 210 | /// # Examples 211 | /// 212 | /// ``` 213 | /// assert_eq!(const_hex::encode("Hello world!"), "48656c6c6f20776f726c6421"); 214 | /// assert_eq!(const_hex::encode([1, 2, 3, 15, 16]), "0102030f10"); 215 | /// ``` 216 | #[cfg(feature = "alloc")] 217 | #[inline] 218 | pub fn encode>(data: T) -> String { 219 | encode_inner::(data.as_ref()) 220 | } 221 | 222 | /// Encodes `data` as a hex string using uppercase characters. 223 | /// 224 | /// Apart from the characters' casing, this works exactly like `encode()`. 225 | /// 226 | /// # Examples 227 | /// 228 | /// ``` 229 | /// assert_eq!(const_hex::encode_upper("Hello world!"), "48656C6C6F20776F726C6421"); 230 | /// assert_eq!(const_hex::encode_upper([1, 2, 3, 15, 16]), "0102030F10"); 231 | /// ``` 232 | #[cfg(feature = "alloc")] 233 | #[inline] 234 | pub fn encode_upper>(data: T) -> String { 235 | encode_inner::(data.as_ref()) 236 | } 237 | 238 | /// Encodes `data` as a prefixed hex string using lowercase characters. 239 | /// 240 | /// See [`encode()`] for more details. 241 | /// 242 | /// # Examples 243 | /// 244 | /// ``` 245 | /// assert_eq!(const_hex::encode_prefixed("Hello world!"), "0x48656c6c6f20776f726c6421"); 246 | /// assert_eq!(const_hex::encode_prefixed([1, 2, 3, 15, 16]), "0x0102030f10"); 247 | /// ``` 248 | #[cfg(feature = "alloc")] 249 | #[inline] 250 | pub fn encode_prefixed>(data: T) -> String { 251 | encode_inner::(data.as_ref()) 252 | } 253 | 254 | /// Encodes `data` as a prefixed hex string using uppercase characters. 255 | /// 256 | /// See [`encode_upper()`] for more details. 257 | /// 258 | /// # Examples 259 | /// 260 | /// ``` 261 | /// assert_eq!(const_hex::encode_upper_prefixed("Hello world!"), "0x48656C6C6F20776F726C6421"); 262 | /// assert_eq!(const_hex::encode_upper_prefixed([1, 2, 3, 15, 16]), "0x0102030F10"); 263 | /// ``` 264 | #[cfg(feature = "alloc")] 265 | #[inline] 266 | pub fn encode_upper_prefixed>(data: T) -> String { 267 | encode_inner::(data.as_ref()) 268 | } 269 | 270 | /// Returns `true` if the input is a valid hex string and can be decoded successfully. 271 | /// 272 | /// Prefer using [`check`] instead when possible (at runtime), as it is likely to be faster. 273 | /// 274 | /// # Examples 275 | /// 276 | /// ``` 277 | /// const _: () = { 278 | /// assert!(const_hex::const_check(b"48656c6c6f20776f726c6421").is_ok()); 279 | /// assert!(const_hex::const_check(b"0x48656c6c6f20776f726c6421").is_ok()); 280 | /// 281 | /// assert!(const_hex::const_check(b"48656c6c6f20776f726c642").is_err()); 282 | /// assert!(const_hex::const_check(b"Hello world!").is_err()); 283 | /// }; 284 | /// ``` 285 | #[inline] 286 | pub const fn const_check(input: &[u8]) -> Result<(), FromHexError> { 287 | if input.len() % 2 != 0 { 288 | return Err(FromHexError::OddLength); 289 | } 290 | let input = strip_prefix(input); 291 | if const_check_raw(input) { 292 | Ok(()) 293 | } else { 294 | Err(unsafe { invalid_hex_error(input) }) 295 | } 296 | } 297 | 298 | /// Returns `true` if the input is a valid hex string. 299 | /// 300 | /// Note that this does not check prefixes or length, but just the contents of the string. 301 | /// 302 | /// Prefer using [`check_raw`] instead when possible (at runtime), as it is likely to be faster. 303 | /// 304 | /// # Examples 305 | /// 306 | /// ``` 307 | /// const _: () = { 308 | /// assert!(const_hex::const_check_raw(b"48656c6c6f20776f726c6421")); 309 | /// 310 | /// // Odd length, but valid hex 311 | /// assert!(const_hex::const_check_raw(b"48656c6c6f20776f726c642")); 312 | /// 313 | /// // Valid hex string, but the prefix is not valid 314 | /// assert!(!const_hex::const_check_raw(b"0x48656c6c6f20776f726c6421")); 315 | /// 316 | /// assert!(!const_hex::const_check_raw(b"Hello world!")); 317 | /// }; 318 | /// ``` 319 | #[inline] 320 | pub const fn const_check_raw(input: &[u8]) -> bool { 321 | generic::check(input) 322 | } 323 | 324 | /// Returns `true` if the input is a valid hex string and can be decoded successfully. 325 | /// 326 | /// # Examples 327 | /// 328 | /// ``` 329 | /// assert!(const_hex::check("48656c6c6f20776f726c6421").is_ok()); 330 | /// assert!(const_hex::check("0x48656c6c6f20776f726c6421").is_ok()); 331 | /// 332 | /// assert!(const_hex::check("48656c6c6f20776f726c642").is_err()); 333 | /// assert!(const_hex::check("Hello world!").is_err()); 334 | /// ``` 335 | #[inline] 336 | pub fn check>(input: T) -> Result<(), FromHexError> { 337 | #[allow(clippy::missing_const_for_fn)] 338 | fn check_inner(input: &[u8]) -> Result<(), FromHexError> { 339 | if input.len() % 2 != 0 { 340 | return Err(FromHexError::OddLength); 341 | } 342 | let stripped = strip_prefix(input); 343 | if imp::check(stripped) { 344 | Ok(()) 345 | } else { 346 | let mut e = unsafe { invalid_hex_error(stripped) }; 347 | if let FromHexError::InvalidHexCharacter { ref mut index, .. } = e { 348 | *index += input.len() - stripped.len(); 349 | } 350 | Err(e) 351 | } 352 | } 353 | 354 | check_inner(input.as_ref()) 355 | } 356 | 357 | /// Returns `true` if the input is a valid hex string. 358 | /// 359 | /// Note that this does not check prefixes or length, but just the contents of the string. 360 | /// 361 | /// # Examples 362 | /// 363 | /// ``` 364 | /// assert!(const_hex::check_raw("48656c6c6f20776f726c6421")); 365 | /// 366 | /// // Odd length, but valid hex 367 | /// assert!(const_hex::check_raw("48656c6c6f20776f726c642")); 368 | /// 369 | /// // Valid hex string, but the prefix is not valid 370 | /// assert!(!const_hex::check_raw("0x48656c6c6f20776f726c6421")); 371 | /// 372 | /// assert!(!const_hex::check_raw("Hello world!")); 373 | /// ``` 374 | #[inline] 375 | pub fn check_raw>(input: T) -> bool { 376 | imp::check(input.as_ref()) 377 | } 378 | 379 | /// Decode a hex string into a fixed-length byte-array. 380 | /// 381 | /// Both, upper and lower case characters are valid in the input string and can 382 | /// even be mixed (e.g. `f9b4ca`, `F9B4CA` and `f9B4Ca` are all valid strings). 383 | /// 384 | /// Strips the `0x` prefix if present. 385 | /// 386 | /// Prefer using [`decode_to_array`] instead when possible (at runtime), as it is likely to be faster. 387 | /// 388 | /// # Errors 389 | /// 390 | /// This function returns an error if the input is not an even number of 391 | /// characters long or contains invalid hex characters, or if the input is not 392 | /// exactly `N * 2` bytes long. 393 | /// 394 | /// # Example 395 | /// 396 | /// ``` 397 | /// const _: () = { 398 | /// let bytes = const_hex::const_decode_to_array(b"6b697769"); 399 | /// assert!(matches!(bytes.as_ref(), Ok(b"kiwi"))); 400 | /// 401 | /// let bytes = const_hex::const_decode_to_array(b"0x6b697769"); 402 | /// assert!(matches!(bytes.as_ref(), Ok(b"kiwi"))); 403 | /// }; 404 | /// ``` 405 | #[inline] 406 | pub const fn const_decode_to_array(input: &[u8]) -> Result<[u8; N], FromHexError> { 407 | if input.len() % 2 != 0 { 408 | return Err(FromHexError::OddLength); 409 | } 410 | let input = strip_prefix(input); 411 | if input.len() != N * 2 { 412 | return Err(FromHexError::InvalidStringLength); 413 | } 414 | match const_decode_to_array_impl(input) { 415 | Some(output) => Ok(output), 416 | None => Err(unsafe { invalid_hex_error(input) }), 417 | } 418 | } 419 | 420 | const fn const_decode_to_array_impl(input: &[u8]) -> Option<[u8; N]> { 421 | macro_rules! next { 422 | ($var:ident, $i:expr) => { 423 | let hex = unsafe { *input.as_ptr().add($i) }; 424 | let $var = HEX_DECODE_LUT[hex as usize]; 425 | if $var == NIL { 426 | return None; 427 | } 428 | }; 429 | } 430 | 431 | let mut output = [0; N]; 432 | debug_assert!(input.len() == N * 2); 433 | let mut i = 0; 434 | while i < output.len() { 435 | next!(high, i * 2); 436 | next!(low, i * 2 + 1); 437 | output[i] = high << 4 | low; 438 | i += 1; 439 | } 440 | Some(output) 441 | } 442 | 443 | /// Decodes a hex string into raw bytes. 444 | /// 445 | /// Both, upper and lower case characters are valid in the input string and can 446 | /// even be mixed (e.g. `f9b4ca`, `F9B4CA` and `f9B4Ca` are all valid strings). 447 | /// 448 | /// Strips the `0x` prefix if present. 449 | /// 450 | /// # Errors 451 | /// 452 | /// This function returns an error if the input is not an even number of 453 | /// characters long or contains invalid hex characters. 454 | /// 455 | /// # Example 456 | /// 457 | /// ``` 458 | /// assert_eq!( 459 | /// const_hex::decode("48656c6c6f20776f726c6421"), 460 | /// Ok("Hello world!".to_owned().into_bytes()) 461 | /// ); 462 | /// assert_eq!( 463 | /// const_hex::decode("0x48656c6c6f20776f726c6421"), 464 | /// Ok("Hello world!".to_owned().into_bytes()) 465 | /// ); 466 | /// 467 | /// assert_eq!(const_hex::decode("123"), Err(const_hex::FromHexError::OddLength)); 468 | /// assert!(const_hex::decode("foo").is_err()); 469 | /// ``` 470 | #[cfg(feature = "alloc")] 471 | #[inline] 472 | pub fn decode>(input: T) -> Result, FromHexError> { 473 | fn decode_inner(input: &[u8]) -> Result, FromHexError> { 474 | if unlikely(input.len() % 2 != 0) { 475 | return Err(FromHexError::OddLength); 476 | } 477 | let input = strip_prefix(input); 478 | 479 | // Do not initialize memory since it will be entirely overwritten. 480 | let len = input.len() / 2; 481 | let mut output = Vec::with_capacity(len); 482 | // SAFETY: The entire vec is never read from, and gets dropped if decoding fails. 483 | #[allow(clippy::uninit_vec)] 484 | unsafe { 485 | output.set_len(len); 486 | } 487 | 488 | // SAFETY: Lengths are checked above. 489 | unsafe { decode_checked(input, &mut output) }.map(|()| output) 490 | } 491 | 492 | decode_inner(input.as_ref()) 493 | } 494 | 495 | /// Decode a hex string into a mutable bytes slice. 496 | /// 497 | /// Both, upper and lower case characters are valid in the input string and can 498 | /// even be mixed (e.g. `f9b4ca`, `F9B4CA` and `f9B4Ca` are all valid strings). 499 | /// 500 | /// Strips the `0x` prefix if present. 501 | /// 502 | /// # Errors 503 | /// 504 | /// This function returns an error if the input is not an even number of 505 | /// characters long or contains invalid hex characters, or if the output slice 506 | /// is not exactly half the length of the input. 507 | /// 508 | /// # Example 509 | /// 510 | /// ``` 511 | /// let mut bytes = [0u8; 4]; 512 | /// const_hex::decode_to_slice("6b697769", &mut bytes).unwrap(); 513 | /// assert_eq!(&bytes, b"kiwi"); 514 | /// 515 | /// const_hex::decode_to_slice("0x6b697769", &mut bytes).unwrap(); 516 | /// assert_eq!(&bytes, b"kiwi"); 517 | /// ``` 518 | #[inline] 519 | pub fn decode_to_slice>(input: T, output: &mut [u8]) -> Result<(), FromHexError> { 520 | decode_to_slice_inner(input.as_ref(), output) 521 | } 522 | 523 | /// Decode a hex string into a fixed-length byte-array. 524 | /// 525 | /// Both, upper and lower case characters are valid in the input string and can 526 | /// even be mixed (e.g. `f9b4ca`, `F9B4CA` and `f9B4Ca` are all valid strings). 527 | /// 528 | /// Strips the `0x` prefix if present. 529 | /// 530 | /// # Errors 531 | /// 532 | /// This function returns an error if the input is not an even number of 533 | /// characters long or contains invalid hex characters, or if the input is not 534 | /// exactly `N / 2` bytes long. 535 | /// 536 | /// # Example 537 | /// 538 | /// ``` 539 | /// let bytes = const_hex::decode_to_array(b"6b697769").unwrap(); 540 | /// assert_eq!(&bytes, b"kiwi"); 541 | /// 542 | /// let bytes = const_hex::decode_to_array(b"0x6b697769").unwrap(); 543 | /// assert_eq!(&bytes, b"kiwi"); 544 | /// ``` 545 | #[inline] 546 | pub fn decode_to_array, const N: usize>(input: T) -> Result<[u8; N], FromHexError> { 547 | fn decode_to_array_inner(input: &[u8]) -> Result<[u8; N], FromHexError> { 548 | let mut output = impl_core::uninit_array(); 549 | // SAFETY: The entire array is never read from. 550 | let output_slice = unsafe { impl_core::slice_assume_init_mut(&mut output) }; 551 | // SAFETY: All elements are initialized. 552 | decode_to_slice_inner(input, output_slice) 553 | .map(|()| unsafe { impl_core::array_assume_init(output) }) 554 | } 555 | 556 | decode_to_array_inner(input.as_ref()) 557 | } 558 | 559 | #[cfg(feature = "alloc")] 560 | fn encode_inner(data: &[u8]) -> String { 561 | let capacity = PREFIX as usize * 2 + data.len() * 2; 562 | let mut buf = Vec::::with_capacity(capacity); 563 | // SAFETY: The entire vec is never read from, and gets dropped if decoding fails. 564 | #[allow(clippy::uninit_vec)] 565 | unsafe { 566 | buf.set_len(capacity) 567 | }; 568 | let mut output = buf.as_mut_ptr(); 569 | if PREFIX { 570 | // SAFETY: `output` is long enough. 571 | unsafe { 572 | output.add(0).write(b'0'); 573 | output.add(1).write(b'x'); 574 | output = output.add(2); 575 | } 576 | } 577 | // SAFETY: `output` is long enough (input.len() * 2). 578 | unsafe { imp::encode::(data, output) }; 579 | // SAFETY: We only write only ASCII bytes. 580 | unsafe { String::from_utf8_unchecked(buf) } 581 | } 582 | 583 | fn encode_to_slice_inner( 584 | input: &[u8], 585 | output: &mut [u8], 586 | ) -> Result<(), FromHexError> { 587 | if unlikely(output.len() != 2 * input.len()) { 588 | return Err(FromHexError::InvalidStringLength); 589 | } 590 | // SAFETY: Lengths are checked above. 591 | unsafe { imp::encode::(input, output.as_mut_ptr()) }; 592 | Ok(()) 593 | } 594 | 595 | fn decode_to_slice_inner(input: &[u8], output: &mut [u8]) -> Result<(), FromHexError> { 596 | if unlikely(input.len() % 2 != 0) { 597 | return Err(FromHexError::OddLength); 598 | } 599 | let input = strip_prefix(input); 600 | if unlikely(output.len() != input.len() / 2) { 601 | return Err(FromHexError::InvalidStringLength); 602 | } 603 | // SAFETY: Lengths are checked above. 604 | unsafe { decode_checked(input, output) } 605 | } 606 | 607 | /// # Safety 608 | /// 609 | /// Assumes `output.len() == input.len() / 2`. 610 | #[inline] 611 | unsafe fn decode_checked(input: &[u8], output: &mut [u8]) -> Result<(), FromHexError> { 612 | debug_assert_eq!(output.len(), input.len() / 2); 613 | 614 | if imp::USE_CHECK_FN { 615 | // check then decode 616 | if imp::check(input) { 617 | unsafe { imp::decode_unchecked(input, output) }; 618 | return Ok(()); 619 | } 620 | } else { 621 | // check and decode at the same time 622 | if unsafe { imp::decode_checked(input, output) } { 623 | return Ok(()); 624 | } 625 | } 626 | 627 | Err(unsafe { invalid_hex_error(input) }) 628 | } 629 | 630 | #[inline] 631 | const fn byte2hex(byte: u8) -> (u8, u8) { 632 | let table = get_chars_table::(); 633 | let high = table[(byte >> 4) as usize]; 634 | let low = table[(byte & 0x0f) as usize]; 635 | (high, low) 636 | } 637 | 638 | #[inline] 639 | const fn strip_prefix(bytes: &[u8]) -> &[u8] { 640 | match bytes { 641 | [b'0', b'x', rest @ ..] => rest, 642 | _ => bytes, 643 | } 644 | } 645 | 646 | /// Creates an invalid hex error from the input. 647 | /// 648 | /// # Safety 649 | /// 650 | /// Assumes `input` contains at least one invalid character. 651 | #[cold] 652 | #[cfg_attr(debug_assertions, track_caller)] 653 | const unsafe fn invalid_hex_error(input: &[u8]) -> FromHexError { 654 | // Find the first invalid character. 655 | let mut index = None; 656 | let mut iter = input; 657 | while let [byte, rest @ ..] = iter { 658 | if HEX_DECODE_LUT[*byte as usize] == NIL { 659 | index = Some(input.len() - rest.len() - 1); 660 | break; 661 | } 662 | iter = rest; 663 | } 664 | 665 | let index = match index { 666 | Some(index) => index, 667 | None => { 668 | if cfg!(debug_assertions) { 669 | panic!("input was valid but `check` failed") 670 | } else { 671 | unsafe { core::hint::unreachable_unchecked() } 672 | } 673 | } 674 | }; 675 | 676 | FromHexError::InvalidHexCharacter { 677 | c: input[index] as char, 678 | index, 679 | } 680 | } 681 | 682 | #[inline(always)] 683 | const fn get_chars_table() -> &'static [u8; 16] { 684 | if UPPER { 685 | HEX_CHARS_UPPER 686 | } else { 687 | HEX_CHARS_LOWER 688 | } 689 | } 690 | 691 | const fn make_decode_lut() -> [u8; 256] { 692 | let mut lut = [0; 256]; 693 | let mut i = 0u8; 694 | loop { 695 | lut[i as usize] = match i { 696 | b'0'..=b'9' => i - b'0', 697 | b'A'..=b'F' => i - b'A' + 10, 698 | b'a'..=b'f' => i - b'a' + 10, 699 | // use max value for invalid characters 700 | _ => NIL, 701 | }; 702 | if i == NIL { 703 | break; 704 | } 705 | i += 1; 706 | } 707 | lut 708 | } 709 | 710 | #[allow( 711 | missing_docs, 712 | unused, 713 | clippy::all, 714 | clippy::missing_inline_in_public_items 715 | )] 716 | #[cfg(all(feature = "__fuzzing", not(miri)))] 717 | #[doc(hidden)] 718 | pub mod fuzzing { 719 | use super::*; 720 | use proptest::test_runner::TestCaseResult; 721 | use proptest::{prop_assert, prop_assert_eq}; 722 | use std::fmt::Write; 723 | 724 | pub fn fuzz(data: &[u8]) -> TestCaseResult { 725 | self::encode(&data)?; 726 | self::decode(&data)?; 727 | Ok(()) 728 | } 729 | 730 | pub fn encode(input: &[u8]) -> TestCaseResult { 731 | test_buffer::<8, 16>(input)?; 732 | test_buffer::<20, 40>(input)?; 733 | test_buffer::<32, 64>(input)?; 734 | test_buffer::<64, 128>(input)?; 735 | test_buffer::<128, 256>(input)?; 736 | 737 | let encoded = crate::encode(input); 738 | let expected = mk_expected(input); 739 | prop_assert_eq!(&encoded, &expected); 740 | 741 | let decoded = crate::decode(&encoded).unwrap(); 742 | prop_assert_eq!(decoded, input); 743 | 744 | Ok(()) 745 | } 746 | 747 | pub fn decode(input: &[u8]) -> TestCaseResult { 748 | if let Ok(decoded) = crate::decode(input) { 749 | let input_len = strip_prefix(input).len() / 2; 750 | prop_assert_eq!(decoded.len(), input_len); 751 | } 752 | 753 | Ok(()) 754 | } 755 | 756 | fn mk_expected(bytes: &[u8]) -> String { 757 | let mut s = String::with_capacity(bytes.len() * 2); 758 | for i in bytes { 759 | write!(s, "{i:02x}").unwrap(); 760 | } 761 | s 762 | } 763 | 764 | fn test_buffer(bytes: &[u8]) -> TestCaseResult { 765 | if let Ok(bytes) = <&[u8; N]>::try_from(bytes) { 766 | let mut buffer = Buffer::::new(); 767 | let string = buffer.format(bytes).to_string(); 768 | prop_assert_eq!(string.len(), bytes.len() * 2); 769 | prop_assert_eq!(string.as_bytes(), buffer.as_byte_array::()); 770 | prop_assert_eq!(string.as_str(), buffer.as_str()); 771 | prop_assert_eq!(string.as_str(), mk_expected(bytes)); 772 | 773 | let mut buffer = Buffer::::new(); 774 | let prefixed = buffer.format(bytes).to_string(); 775 | prop_assert_eq!(prefixed.len(), 2 + bytes.len() * 2); 776 | prop_assert_eq!(prefixed.as_str(), buffer.as_str()); 777 | prop_assert_eq!(prefixed.as_str(), format!("0x{string}")); 778 | 779 | prop_assert_eq!(decode_to_array(&string), Ok(*bytes)); 780 | prop_assert_eq!(decode_to_array(&prefixed), Ok(*bytes)); 781 | prop_assert_eq!(const_decode_to_array(string.as_bytes()), Ok(*bytes)); 782 | prop_assert_eq!(const_decode_to_array(prefixed.as_bytes()), Ok(*bytes)); 783 | } 784 | 785 | Ok(()) 786 | } 787 | 788 | proptest::proptest! { 789 | #![proptest_config(proptest::prelude::ProptestConfig { 790 | cases: 1024, 791 | ..Default::default() 792 | })] 793 | 794 | #[test] 795 | fn fuzz_encode(s in ".+") { 796 | encode(s.as_bytes())?; 797 | } 798 | 799 | #[test] 800 | fn fuzz_check_true(s in "[0-9a-fA-F]+") { 801 | let s = s.as_bytes(); 802 | prop_assert!(crate::check_raw(s)); 803 | prop_assert!(crate::const_check_raw(s)); 804 | if s.len() % 2 == 0 { 805 | prop_assert!(crate::check(s).is_ok()); 806 | prop_assert!(crate::const_check(s).is_ok()); 807 | } 808 | } 809 | 810 | #[test] 811 | fn fuzz_check_false(s in ".{16}[0-9a-fA-F]+") { 812 | let s = s.as_bytes(); 813 | prop_assert!(crate::check(s).is_err()); 814 | prop_assert!(crate::const_check(s).is_err()); 815 | prop_assert!(!crate::check_raw(s)); 816 | prop_assert!(!crate::const_check_raw(s)); 817 | } 818 | } 819 | } 820 | -------------------------------------------------------------------------------- /src/serde.rs: -------------------------------------------------------------------------------- 1 | //! Hex encoding with [`serde`]. 2 | //! 3 | //! # Examples 4 | //! 5 | //! ``` 6 | //! # #[cfg(feature = "alloc")] { 7 | //! use serde::{Serialize, Deserialize}; 8 | //! 9 | //! #[derive(Serialize, Deserialize)] 10 | //! struct Foo { 11 | //! #[serde(with = "const_hex")] 12 | //! bar: Vec, 13 | //! } 14 | //! # } 15 | //! ``` 16 | 17 | use crate::FromHex; 18 | use core::fmt; 19 | use core::marker::PhantomData; 20 | use serde::de::{Error, Visitor}; 21 | use serde::Deserializer; 22 | 23 | #[cfg(feature = "alloc")] 24 | mod serialize { 25 | use serde::Serializer; 26 | 27 | /// Serializes `data` as hex string using lowercase characters. 28 | /// 29 | /// Lowercase characters are used (e.g. `f9b4ca`). The resulting string's length 30 | /// is always even, each byte in data is always encoded using two hex digits. 31 | /// Thus, the resulting string contains exactly twice as many bytes as the input 32 | /// data. 33 | #[inline] 34 | pub fn serialize(data: T, serializer: S) -> Result 35 | where 36 | S: Serializer, 37 | T: AsRef<[u8]>, 38 | { 39 | serializer.serialize_str(&crate::encode_prefixed(data.as_ref())) 40 | } 41 | 42 | /// Serializes `data` as hex string using uppercase characters. 43 | /// 44 | /// Apart from the characters' casing, this works exactly like [`serialize`]. 45 | #[inline] 46 | pub fn serialize_upper(data: T, serializer: S) -> Result 47 | where 48 | S: Serializer, 49 | T: AsRef<[u8]>, 50 | { 51 | serializer.serialize_str(&crate::encode_upper_prefixed(data.as_ref())) 52 | } 53 | } 54 | 55 | #[cfg(feature = "alloc")] 56 | pub use serialize::{serialize, serialize_upper}; 57 | 58 | /// Deserializes a hex string into raw bytes. 59 | /// 60 | /// Both, upper and lower case characters are valid in the input string and can 61 | /// even be mixed (e.g. `f9b4ca`, `F9B4CA` and `f9B4Ca` are all valid strings). 62 | #[inline] 63 | pub fn deserialize<'de, D, T>(deserializer: D) -> Result 64 | where 65 | D: Deserializer<'de>, 66 | T: FromHex, 67 | ::Error: fmt::Display, 68 | { 69 | struct HexStrVisitor(PhantomData); 70 | 71 | impl Visitor<'_> for HexStrVisitor 72 | where 73 | T: FromHex, 74 | ::Error: fmt::Display, 75 | { 76 | type Value = T; 77 | 78 | fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 79 | f.write_str("a hex encoded string") 80 | } 81 | 82 | fn visit_bytes(self, data: &[u8]) -> Result { 83 | FromHex::from_hex(data).map_err(Error::custom) 84 | } 85 | 86 | fn visit_str(self, data: &str) -> Result { 87 | FromHex::from_hex(data.as_bytes()).map_err(Error::custom) 88 | } 89 | } 90 | 91 | deserializer.deserialize_str(HexStrVisitor(PhantomData)) 92 | } 93 | -------------------------------------------------------------------------------- /src/traits.rs: -------------------------------------------------------------------------------- 1 | //! Modified from `hex`. 2 | 3 | #![allow(clippy::ptr_as_ptr, clippy::borrow_as_ptr, clippy::missing_errors_doc)] 4 | 5 | use core::iter; 6 | 7 | #[cfg(feature = "alloc")] 8 | #[allow(unused_imports)] 9 | use alloc::{ 10 | borrow::{Cow, ToOwned}, 11 | boxed::Box, 12 | rc::Rc, 13 | string::String, 14 | vec::Vec, 15 | }; 16 | 17 | #[cfg(all(feature = "alloc", target_has_atomic = "ptr"))] 18 | #[allow(unused_imports)] 19 | use alloc::sync::Arc; 20 | 21 | /// Encoding values as hex string. 22 | /// 23 | /// This trait is implemented for all `T` which implement `AsRef<[u8]>`. This 24 | /// includes `String`, `str`, `Vec` and `[u8]`. 25 | /// 26 | /// # Examples 27 | /// 28 | /// ``` 29 | /// #![allow(deprecated)] 30 | /// use const_hex::ToHex; 31 | /// 32 | /// assert_eq!("Hello world!".encode_hex::(), "48656c6c6f20776f726c6421"); 33 | /// assert_eq!("Hello world!".encode_hex_upper::(), "48656C6C6F20776F726C6421"); 34 | /// ``` 35 | #[cfg_attr(feature = "alloc", doc = "\n[`encode`]: crate::encode")] 36 | #[cfg_attr(not(feature = "alloc"), doc = "\n[`encode`]: crate::encode_to_slice")] 37 | #[deprecated(note = "use `ToHexExt` instead")] 38 | pub trait ToHex { 39 | /// Encode the hex strict representing `self` into the result. 40 | /// Lower case letters are used (e.g. `f9b4ca`). 41 | fn encode_hex>(&self) -> T; 42 | 43 | /// Encode the hex strict representing `self` into the result. 44 | /// Upper case letters are used (e.g. `F9B4CA`). 45 | fn encode_hex_upper>(&self) -> T; 46 | } 47 | 48 | /// Encoding values as hex string. 49 | /// 50 | /// This trait is implemented for all `T` which implement `AsRef<[u8]>`. This 51 | /// includes `String`, `str`, `Vec` and `[u8]`. 52 | /// 53 | /// # Examples 54 | /// 55 | /// ``` 56 | /// use const_hex::ToHexExt; 57 | /// 58 | /// assert_eq!("Hello world!".encode_hex(), "48656c6c6f20776f726c6421"); 59 | /// assert_eq!("Hello world!".encode_hex_upper(), "48656C6C6F20776F726C6421"); 60 | /// assert_eq!("Hello world!".encode_hex_with_prefix(), "0x48656c6c6f20776f726c6421"); 61 | /// assert_eq!("Hello world!".encode_hex_upper_with_prefix(), "0x48656C6C6F20776F726C6421"); 62 | /// ``` 63 | #[cfg(feature = "alloc")] 64 | pub trait ToHexExt { 65 | /// Encode the hex strict representing `self` into the result. 66 | /// Lower case letters are used (e.g. `f9b4ca`). 67 | fn encode_hex(&self) -> String; 68 | 69 | /// Encode the hex strict representing `self` into the result. 70 | /// Upper case letters are used (e.g. `F9B4CA`). 71 | fn encode_hex_upper(&self) -> String; 72 | 73 | /// Encode the hex strict representing `self` into the result with prefix `0x`. 74 | /// Lower case letters are used (e.g. `0xf9b4ca`). 75 | fn encode_hex_with_prefix(&self) -> String; 76 | 77 | /// Encode the hex strict representing `self` into the result with prefix `0X`. 78 | /// Upper case letters are used (e.g. `0xF9B4CA`). 79 | fn encode_hex_upper_with_prefix(&self) -> String; 80 | } 81 | 82 | struct BytesToHexChars<'a, const UPPER: bool> { 83 | inner: core::slice::Iter<'a, u8>, 84 | next: Option, 85 | } 86 | 87 | impl<'a, const UPPER: bool> BytesToHexChars<'a, UPPER> { 88 | fn new(inner: &'a [u8]) -> Self { 89 | BytesToHexChars { 90 | inner: inner.iter(), 91 | next: None, 92 | } 93 | } 94 | } 95 | 96 | impl Iterator for BytesToHexChars<'_, UPPER> { 97 | type Item = char; 98 | 99 | fn next(&mut self) -> Option { 100 | match self.next.take() { 101 | Some(current) => Some(current), 102 | None => self.inner.next().map(|byte| { 103 | let (high, low) = crate::byte2hex::(*byte); 104 | self.next = Some(low as char); 105 | high as char 106 | }), 107 | } 108 | } 109 | } 110 | 111 | #[inline] 112 | fn encode_to_iter, const UPPER: bool>(source: &[u8]) -> T { 113 | BytesToHexChars::::new(source).collect() 114 | } 115 | 116 | #[allow(deprecated)] 117 | impl> ToHex for T { 118 | #[inline] 119 | fn encode_hex>(&self) -> U { 120 | encode_to_iter::<_, false>(self.as_ref()) 121 | } 122 | 123 | #[inline] 124 | fn encode_hex_upper>(&self) -> U { 125 | encode_to_iter::<_, true>(self.as_ref()) 126 | } 127 | } 128 | 129 | #[cfg(feature = "alloc")] 130 | impl> ToHexExt for T { 131 | #[inline] 132 | fn encode_hex(&self) -> String { 133 | crate::encode(self) 134 | } 135 | 136 | #[inline] 137 | fn encode_hex_upper(&self) -> String { 138 | crate::encode_upper(self) 139 | } 140 | 141 | #[inline] 142 | fn encode_hex_with_prefix(&self) -> String { 143 | crate::encode_prefixed(self) 144 | } 145 | 146 | #[inline] 147 | fn encode_hex_upper_with_prefix(&self) -> String { 148 | crate::encode_upper_prefixed(self) 149 | } 150 | } 151 | 152 | /// Types that can be decoded from a hex string. 153 | /// 154 | /// This trait is implemented for `Vec` and small `u8`-arrays. 155 | /// 156 | /// # Example 157 | /// 158 | /// ``` 159 | /// use const_hex::FromHex; 160 | /// 161 | /// let buffer = <[u8; 12]>::from_hex("48656c6c6f20776f726c6421")?; 162 | /// assert_eq!(buffer, *b"Hello world!"); 163 | /// # Ok::<(), const_hex::FromHexError>(()) 164 | /// ``` 165 | pub trait FromHex: Sized { 166 | /// The associated error which can be returned from parsing. 167 | type Error; 168 | 169 | /// Creates an instance of type `Self` from the given hex string, or fails 170 | /// with a custom error type. 171 | /// 172 | /// Both, upper and lower case characters are valid and can even be 173 | /// mixed (e.g. `f9b4ca`, `F9B4CA` and `f9B4Ca` are all valid strings). 174 | fn from_hex>(hex: T) -> Result; 175 | } 176 | 177 | #[cfg(feature = "alloc")] 178 | impl FromHex for Box { 179 | type Error = T::Error; 180 | 181 | #[inline] 182 | fn from_hex>(hex: U) -> Result { 183 | FromHex::from_hex(hex.as_ref()).map(Self::new) 184 | } 185 | } 186 | 187 | #[cfg(feature = "alloc")] 188 | impl FromHex for Cow<'_, T> 189 | where 190 | T: ToOwned + ?Sized, 191 | T::Owned: FromHex, 192 | { 193 | type Error = ::Error; 194 | 195 | #[inline] 196 | fn from_hex>(hex: U) -> Result { 197 | FromHex::from_hex(hex.as_ref()).map(Cow::Owned) 198 | } 199 | } 200 | 201 | #[cfg(feature = "alloc")] 202 | impl FromHex for Rc { 203 | type Error = T::Error; 204 | 205 | #[inline] 206 | fn from_hex>(hex: U) -> Result { 207 | FromHex::from_hex(hex.as_ref()).map(Self::new) 208 | } 209 | } 210 | 211 | #[cfg(all(feature = "alloc", target_has_atomic = "ptr"))] 212 | impl FromHex for Arc { 213 | type Error = T::Error; 214 | 215 | #[inline] 216 | fn from_hex>(hex: U) -> Result { 217 | FromHex::from_hex(hex.as_ref()).map(Self::new) 218 | } 219 | } 220 | 221 | #[cfg(feature = "alloc")] 222 | impl FromHex for Vec { 223 | type Error = crate::FromHexError; 224 | 225 | #[inline] 226 | fn from_hex>(hex: T) -> Result { 227 | crate::decode(hex.as_ref()) 228 | } 229 | } 230 | 231 | #[cfg(feature = "alloc")] 232 | impl FromHex for Vec { 233 | type Error = crate::FromHexError; 234 | 235 | #[inline] 236 | fn from_hex>(hex: T) -> Result { 237 | // SAFETY: transmuting `u8` to `i8` is safe. 238 | crate::decode(hex.as_ref()).map(|vec| unsafe { core::mem::transmute::, Self>(vec) }) 239 | } 240 | } 241 | 242 | #[cfg(feature = "alloc")] 243 | impl FromHex for Box<[u8]> { 244 | type Error = crate::FromHexError; 245 | 246 | #[inline] 247 | fn from_hex>(hex: T) -> Result { 248 | >::from_hex(hex).map(Vec::into_boxed_slice) 249 | } 250 | } 251 | 252 | #[cfg(feature = "alloc")] 253 | impl FromHex for Box<[i8]> { 254 | type Error = crate::FromHexError; 255 | 256 | #[inline] 257 | fn from_hex>(hex: T) -> Result { 258 | >::from_hex(hex).map(Vec::into_boxed_slice) 259 | } 260 | } 261 | 262 | impl FromHex for [u8; N] { 263 | type Error = crate::FromHexError; 264 | 265 | #[inline] 266 | fn from_hex>(hex: T) -> Result { 267 | crate::decode_to_array(hex.as_ref()) 268 | } 269 | } 270 | 271 | impl FromHex for [i8; N] { 272 | type Error = crate::FromHexError; 273 | 274 | #[inline] 275 | fn from_hex>(hex: T) -> Result { 276 | // SAFETY: casting `[u8]` to `[i8]` is safe. 277 | crate::decode_to_array(hex.as_ref()) 278 | .map(|buf| unsafe { *(&buf as *const [u8; N] as *const [i8; N]) }) 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /tests/test.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::format_collect)] 2 | 3 | use const_hex::Buffer; 4 | 5 | #[test] 6 | fn buffer_fmt() { 7 | let mut buffer = Buffer::<256, true>::new(); 8 | buffer.format(&ALL); 9 | let s = format!("{buffer:?}"); 10 | let pre = "Buffer(\"0x"; 11 | let post = "\")"; 12 | assert_eq!(&s[..pre.len()], pre); 13 | assert_eq!(&s[s.len() - post.len()..], post); 14 | assert_lower(&s[pre.len()..s.len() - post.len()]); 15 | } 16 | 17 | #[test] 18 | fn buffer_prefix() { 19 | let mut buffer = Buffer::<256, true>::new(); 20 | let s = buffer.format(&ALL); 21 | assert_eq!(&s[..2], "0x"); 22 | assert_lower(&s[2..]); 23 | } 24 | 25 | #[test] 26 | fn buffer_array_lower() { 27 | let mut buffer = Buffer::<256>::new(); 28 | let s = buffer.format(&ALL); 29 | assert_lower(s); 30 | } 31 | 32 | #[test] 33 | fn buffer_array_upper() { 34 | let mut buffer = Buffer::<256>::new(); 35 | let s = buffer.format_upper(&ALL); 36 | assert_upper(s); 37 | } 38 | 39 | #[test] 40 | fn buffer_slice_lower() { 41 | let mut buffer = Buffer::<256>::new(); 42 | let s = buffer.format_slice(ALL); 43 | assert_lower(s); 44 | } 45 | 46 | #[test] 47 | fn buffer_slice_upper() { 48 | let mut buffer = Buffer::<256>::new(); 49 | let s = buffer.format_slice_upper(ALL); 50 | assert_upper(s); 51 | } 52 | 53 | #[test] 54 | fn buffer_const_lower() { 55 | const BUFFER: Buffer<256> = Buffer::new().const_format(&ALL); 56 | assert_lower(BUFFER.as_str()); 57 | } 58 | 59 | #[test] 60 | fn buffer_const_upper() { 61 | const BUFFER: Buffer<256> = Buffer::new().const_format_upper(&ALL); 62 | assert_upper(BUFFER.as_str()); 63 | } 64 | 65 | #[test] 66 | #[cfg(feature = "alloc")] 67 | fn encode_lower() { 68 | let encoded = const_hex::encode(ALL); 69 | assert_lower(&encoded); 70 | } 71 | 72 | #[test] 73 | #[cfg(feature = "alloc")] 74 | fn encode_upper() { 75 | let encoded = const_hex::encode_upper(ALL); 76 | assert_upper(&encoded); 77 | } 78 | 79 | #[test] 80 | #[cfg(feature = "alloc")] 81 | fn encode_lower_prefixed() { 82 | let encoded = const_hex::encode_prefixed(ALL); 83 | assert_eq!(&encoded[0..2], "0x"); 84 | assert_lower(&encoded[2..]); 85 | } 86 | 87 | #[test] 88 | #[cfg(feature = "alloc")] 89 | fn encode_upper_prefixed() { 90 | let encoded = const_hex::encode_upper_prefixed(ALL); 91 | assert_eq!(&encoded[0..2], "0x"); 92 | assert_upper(&encoded[2..]); 93 | } 94 | 95 | #[test] 96 | #[cfg(feature = "alloc")] 97 | fn decode_lower() { 98 | let decoded = const_hex::decode(ALL_LOWER).unwrap(); 99 | assert_eq!(decoded, ALL); 100 | let decoded = const_hex::decode_to_array(ALL_LOWER).unwrap(); 101 | assert_eq!(decoded, ALL); 102 | } 103 | 104 | #[test] 105 | #[cfg(feature = "alloc")] 106 | fn decode_upper() { 107 | let decoded = const_hex::decode(ALL_UPPER).unwrap(); 108 | assert_eq!(decoded, ALL); 109 | let decoded = const_hex::decode_to_array(ALL_UPPER).unwrap(); 110 | assert_eq!(decoded, ALL); 111 | } 112 | 113 | #[test] 114 | #[cfg(feature = "alloc")] 115 | fn roundtrips() { 116 | test_roundtrip("1234"); 117 | test_roundtrip("00000000000011"); 118 | test_roundtrip("0000000000000022"); 119 | test_roundtrip("000000000000000033"); 120 | test_roundtrip("00000000000000003344"); 121 | test_roundtrip("05161049138038061049183398181016"); 122 | } 123 | 124 | #[test] 125 | #[cfg(feature = "alloc")] 126 | fn roundtrip_long() { 127 | test_roundtrip("608060405234801561001057600080fd5b506040516104913803806104918339818101604052602081101561003357600080fd5b810190808051604051939291908464010000000082111561005357600080fd5b90830190602082018581111561006857600080fd5b825164010000000081118282018810171561008257600080fd5b82525081516020918201929091019080838360005b838110156100af578181015183820152602001610097565b50505050905090810190601f1680156100dc5780820380516001836020036101000a031916815260200191505b50604052505081516100f6915060009060208401906100fd565b505061019e565b828054600181600116156101000203166002900490600052602060002090601f0160209004810192826101335760008555610179565b82601f1061014c57805160ff1916838001178555610179565b82800160010185558215610179579182015b8281111561017957825182559160200191906001019061015e565b50610185929150610189565b5090565b5b80821115610185576000815560010161018a565b6102e4806101ad6000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063a41368621461003b578063cfae3217146100e3575b600080fd5b6100e16004803603602081101561005157600080fd5b81019060208101813564010000000081111561006c57600080fd5b82018360208201111561007e57600080fd5b803590602001918460018302840111640100000000831117156100a057600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610160945050505050565b005b6100eb610177565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561012557818101518382015260200161010d565b50505050905090810190601f1680156101525780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b805161017390600090602084019061020d565b5050565b60008054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156102035780601f106101d857610100808354040283529160200191610203565b820191906000526020600020905b8154815290600101906020018083116101e657829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f0160209004810192826102435760008555610289565b82601f1061025c57805160ff1916838001178555610289565b82800160010185558215610289579182015b8281111561028957825182559160200191906001019061026e565b50610295929150610299565b5090565b5b80821115610295576000815560010161029a56fea26469706673582212208b9161dfd195d53618942a72a3b481d61a7b142de919925a0b34f9c986e5707e64736f6c63430007060033"); 128 | } 129 | 130 | #[cfg(feature = "alloc")] 131 | fn test_roundtrip(s: &str) { 132 | const_hex::check(s).expect(s); 133 | let decoded = const_hex::decode(s).expect(s); 134 | assert_eq!(decoded, hex::decode(s).expect(s)); 135 | assert_eq!(const_hex::encode(&decoded), s); 136 | } 137 | 138 | #[test] 139 | fn check() { 140 | assert_eq!(const_hex::check(""), Ok(())); 141 | assert!(const_hex::check_raw("")); 142 | 143 | assert_eq!(const_hex::check(ALL_LOWER), Ok(())); 144 | assert_eq!(const_hex::check(ALL_UPPER), Ok(())); 145 | assert!(const_hex::check_raw(ALL_LOWER)); 146 | assert!(const_hex::check_raw(ALL_UPPER)); 147 | 148 | let error_cases = [ 149 | ("ag", 1, 'g'), 150 | ("0xbz", 3, 'z'), 151 | ("0x12340000000n", 13, 'n'), 152 | ]; 153 | for (s, index, c) in error_cases { 154 | assert_eq!(s[index..].chars().next(), Some(c), "{s:?}"); 155 | assert_eq!( 156 | const_hex::check(s), 157 | Err(const_hex::FromHexError::InvalidHexCharacter { c, index }) 158 | ); 159 | } 160 | } 161 | 162 | #[test] 163 | #[cfg(all(feature = "serde", feature = "alloc", not(feature = "hex")))] 164 | fn serde() { 165 | #[derive(serde::Serialize, serde::Deserialize)] 166 | struct All { 167 | #[serde(with = "const_hex")] 168 | x: Vec, 169 | } 170 | 171 | let all = All { x: ALL.to_vec() }; 172 | let encoded = serde_json::to_string(&all).unwrap(); 173 | assert_eq!(encoded, format!(r#"{{"x":"0x{ALL_LOWER}"}}"#)); 174 | let decoded: All = serde_json::from_str(&encoded).unwrap(); 175 | assert_eq!(decoded.x, ALL); 176 | } 177 | 178 | const ALL: [u8; 256] = [ 179 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 180 | 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 181 | 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 182 | 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 183 | 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 184 | 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 185 | 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 186 | 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 187 | 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 188 | 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 189 | 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 190 | 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 191 | 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 192 | 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 193 | 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 194 | 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 195 | ]; 196 | 197 | const ALL_LOWER: &str = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"; 198 | const ALL_UPPER: &str = "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF"; 199 | 200 | #[track_caller] 201 | fn assert_lower(s: &str) { 202 | let expected = (0..=u8::MAX) 203 | .map(|i| format!("{i:02x}")) 204 | .collect::(); 205 | assert_eq!(ALL_LOWER, expected); 206 | assert_eq!(s, expected); 207 | } 208 | 209 | #[track_caller] 210 | fn assert_upper(s: &str) { 211 | let expected = (0..=u8::MAX) 212 | .map(|i| format!("{i:02X}")) 213 | .collect::(); 214 | assert_eq!(ALL_UPPER, expected); 215 | assert_eq!(s, expected); 216 | } 217 | --------------------------------------------------------------------------------