├── .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 | [](https://github.com/danipopes/const-hex)
4 | [](https://crates.io/crates/const-hex)
5 | [](https://docs.rs/const-hex)
6 | [](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 |
--------------------------------------------------------------------------------