diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e56f1a..54ccf92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.0.0-alpha.0 - 2025-02-24 + +- TODO: Write changelog. + # 0.3.0 - 2024-09-18 - Re-implement `HexWriter` [#113](https://github.com/rust-bitcoin/hex-conservative/pull/113) diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock index a272c4f..29322ff 100644 --- a/Cargo-minimal.lock +++ b/Cargo-minimal.lock @@ -16,59 +16,19 @@ checksum = "5edd69c67b2f8e0911629b7e6b8a34cb3956613cd7c6e6414966dee349c2db4f" [[package]] name = "hex-conservative" -version = "0.3.0" +version = "1.0.0-alpha.0" dependencies = [ "arrayvec", "serde", "serde_json", ] -[[package]] -name = "hex-fuzz" -version = "0.0.1" -dependencies = [ - "hex-conservative", - "honggfuzz", -] - -[[package]] -name = "honggfuzz" -version = "0.5.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c76b6234c13c9ea73946d1379d33186151148e0da231506b964b44f3d023505" -dependencies = [ - "lazy_static", - "memmap2", - "rustc_version", -] - [[package]] name = "itoa" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91fd9dc2c587067de817fec4ad355e3818c3d893a78cab32a0a474c7a15bb8d5" -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.143" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc207893e85c5d6be840e969b496b53d94cec8be2d501b214f50daa97fa8024" - -[[package]] -name = "memmap2" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deaba38d7abf1d4cca21cc89e932e542ba2b9258664d2a9ef0e61512039c9375" -dependencies = [ - "libc", -] - [[package]] name = "num-traits" version = "0.1.32" @@ -93,21 +53,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - -[[package]] -name = "semver" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76b5842e81eb9bbea19276a9dbbda22ac042532f390a67ab08b895617978abf3" - [[package]] name = "serde" version = "1.0.156" diff --git a/Cargo-recent.lock b/Cargo-recent.lock index 08aa332..e907537 100644 --- a/Cargo-recent.lock +++ b/Cargo-recent.lock @@ -10,65 +10,25 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "hex-conservative" -version = "0.3.0" +version = "1.0.0-alpha.0" dependencies = [ "arrayvec", "serde", "serde_json", ] -[[package]] -name = "hex-fuzz" -version = "0.0.1" -dependencies = [ - "hex-conservative", - "honggfuzz", -] - -[[package]] -name = "honggfuzz" -version = "0.5.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c76b6234c13c9ea73946d1379d33186151148e0da231506b964b44f3d023505" -dependencies = [ - "lazy_static", - "memmap2", - "rustc_version", -] - [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.158" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" - [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "memmap2" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" -dependencies = [ - "libc", -] - [[package]] name = "proc-macro2" version = "1.0.86" @@ -87,27 +47,12 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" -[[package]] -name = "semver" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" - [[package]] name = "serde" version = "1.0.210" diff --git a/Cargo.toml b/Cargo.toml index e38b7a7..47c525d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hex-conservative" -version = "0.3.0" +version = "1.0.0-alpha.0" authors = ["Martin Habovštiak ", "Andrew Poelstra "] license = "CC0-1.0" repository = "https://github.com/rust-bitcoin/hex-conservative" @@ -17,9 +17,6 @@ exclude = ["tests", "contrib"] all-features = true rustdoc-args = ["--cfg", "docsrs"] -[workspace] -members = ["fuzz"] - [features] default = ["std"] std = ["alloc"] @@ -33,13 +30,3 @@ serde = { version = "1.0", default-features = false, optional = true } [dev-dependencies] serde = { version = "1.0.156", features = ["derive"] } serde_json = "1.0" - -[[example]] -name = "hexy" - -[[example]] -name = "wrap_array" - -[[example]] -name = "serde" -required-features = ["std", "serde"] diff --git a/examples/hexy.rs b/examples/hexy.rs deleted file mode 100644 index 9064908..0000000 --- a/examples/hexy.rs +++ /dev/null @@ -1,72 +0,0 @@ -//! Demonstrate hexadecimal encoding and decoding for a type with a natural hex representation. -//! -//! For a type where hex is supported but is not the natural representation see `./custom.rs`. -//! To wrap an array see the `./wrap_array_*` examples. - -use std::fmt; -use std::str::FromStr; - -use hex_conservative::{fmt_hex_exact, Case, DisplayHex as _, FromHex as _, HexToArrayError}; - -fn main() { - let s = "deadbeefcafebabedeadbeefcafebabedeadbeefcafebabedeadbeefcafebabe"; - println!("Parse hex from string: {}", s); - - let hexy = s.parse::().expect("the correct number of valid hex digits"); - let display = format!("{}", hexy); - println!("Display Hexy as string: {}", display); - - assert_eq!(display, s); -} - -/// A struct that always uses hex when in string form. -pub struct Hexy { - // Some opaque data. - data: [u8; 32], -} - -impl Hexy { - /// Demonstrates getting internal opaque data as a byte slice. - pub fn as_bytes(&self) -> &[u8] { &self.data } -} - -impl fmt::Debug for Hexy { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - fmt::Formatter::debug_struct(f, "Hexy").field("data", &self.data.as_hex()).finish() - } -} - -// We implement `Display`/`FromStr` using `LowerHex`/`FromHex` respectively, if hex was not the -// natural representation for this type this would not be the case. - -impl fmt::Display for Hexy { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) } -} - -impl FromStr for Hexy { - type Err = HexToArrayError; - - fn from_str(s: &str) -> Result { - // Errors if the input is invalid - let a = <[u8; 32]>::from_hex(s)?; - Ok(Hexy { data: a }) - } -} - -// Implement conversion to hex by first converting our type to a byte slice. - -impl fmt::LowerHex for Hexy { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // This is equivalent to but more performant than: - // fmt::LowerHex::fmt(&self.as_bytes().as_hex(), f) - fmt_hex_exact!(f, 32, self.as_bytes(), Case::Lower) - } -} - -impl fmt::UpperHex for Hexy { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // This is equivalent to but more performant than: - // fmt::UpperHex::fmt(&self.as_bytes().as_hex(), f) - fmt_hex_exact!(f, 32, self.as_bytes(), Case::Upper) - } -} diff --git a/examples/serde.rs b/examples/serde.rs deleted file mode 100644 index ad40974..0000000 --- a/examples/serde.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! Demonstrate how to use the serde module with struct fields. - -#![allow(clippy::disallowed_names)] // Foo is a valid name. - -use hex_conservative as hex; -use serde::{Deserialize, Serialize}; - -/// Abstracts over foo. -#[derive(Debug, Serialize, Deserialize)] -pub struct Foo { - // serialized as a hexadecimal string. - #[serde(with = "hex::serde")] - pub u: Vec, - // serialized as an array of decimal integers. - pub v: Vec, -} - -fn main() { - let v = vec![0xde, 0xad, 0xbe, 0xef]; - - let foo = Foo { u: v.clone(), v }; - let ser = serde_json::to_string(&foo).expect("failed to serialize foo"); - - // Prints: - // - // foo: {"u":"deadbeef","v":[222,173,190,239]} - // - println!("\nfoo: {}", ser); -} diff --git a/examples/wrap_array.rs b/examples/wrap_array.rs deleted file mode 100644 index bb237a4..0000000 --- a/examples/wrap_array.rs +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//! Hex encode/decode a type that wraps an array. -//! -//! Creates a simple array wrapper types using implementations of the standard library `fmt` traits. - -use core::fmt; -use core::str::FromStr; - -use hex_conservative::{DisplayHex as _, FromHex as _, HexToArrayError}; - -fn main() { - let hex = "deadbeefcafebabedeadbeefcafebabedeadbeefcafebabedeadbeefcafebabe"; - println!("\nParse from hex: {}\n", hex); - - let array = <[u8; 32]>::from_hex(hex).expect("failed to parse array"); - let wrap = Wrap::from_str(hex).expect("failed to parse wrapped array from hex string"); - - println!("Print an array using traits from the standard libraries `fmt` module along with the provided implementation of `DisplayHex`:\n"); - println!("LowerHex: {:x}", array.as_hex()); - println!("UpperHex: {:X}", array.as_hex()); - println!("Display: {}", array.as_hex()); - println!("Debug: {:?}", array.as_hex()); - println!("Debug pretty: {:#?}", array.as_hex()); - - println!("\n"); - - println!( - "Print the wrapped array directly using traits from the standard libraries `fmt` module:\n" - ); - println!("LowerHex: {:x}", wrap); - println!("UpperHex: {:X}", wrap); - println!("Display: {}", wrap); - println!("Debug: {:?}", wrap); - println!("Debug pretty: {:#?}", wrap); - - // We cannot call `to_lower_hex_string` on the wrapped type to allocate a string, if you wish to - // use that trait method see `./wrap_array_display_hex_trait.rs`. - let array_hex = array.as_hex().to_string(); - let wrap_hex = wrap.to_string(); - assert_eq!(array_hex, wrap_hex); -} - -pub struct Wrap([u8; 32]); - -impl fmt::Debug for Wrap { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - fmt::Formatter::debug_tuple(f, "Wrap").field(&self.0.as_hex()).finish() - } -} - -impl fmt::Display for Wrap { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - fmt::Display::fmt(&self.0.as_hex(), f) - } -} - -impl fmt::LowerHex for Wrap { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - fmt::LowerHex::fmt(&self.0.as_hex(), f) - } -} - -impl fmt::UpperHex for Wrap { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - fmt::LowerHex::fmt(&self.0.as_hex(), f) - } -} - -impl FromStr for Wrap { - type Err = HexToArrayError; - fn from_str(s: &str) -> Result { Ok(Self(<[u8; 32]>::from_hex(s)?)) } -} diff --git a/fuzz/.gitignore b/fuzz/.gitignore deleted file mode 100644 index 3ebcb10..0000000 --- a/fuzz/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -hfuzz_target -hfuzz_workspace diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml deleted file mode 100644 index 82f48f5..0000000 --- a/fuzz/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "hex-fuzz" -edition = "2021" -rust-version = "1.63.0" -version = "0.0.1" -authors = ["Generated by fuzz/generate-files.sh"] -publish = false - -[package.metadata] -cargo-fuzz = true - -[dependencies] -honggfuzz = { version = "0.5.56", default-features = false } -hex = { path = "..", package = "hex-conservative" } - -[[bin]] -name = "hex" -path = "fuzz_targets/hex.rs" - -[[bin]] -name = "encode" -path = "fuzz_targets/encode.rs" - -[lints.rust] -unexpected_cfgs = { level = "deny", check-cfg = ['cfg(fuzzing)'] } diff --git a/fuzz/README.md b/fuzz/README.md deleted file mode 100644 index 61d52a5..0000000 --- a/fuzz/README.md +++ /dev/null @@ -1,88 +0,0 @@ -# Fuzzing - -`hex-conservative` has fuzzing harnesses setup for use with honggfuzz. - -To run the fuzz-tests as in CI -- briefly fuzzing every target -- simply -run - - ./fuzz.sh - -in this directory. - -To build honggfuzz, you must have libunwind on your system, as well as -libopcodes and libbfd from binutils **2.38** on your system. The most -recently-released binutils 2.39 has changed their API in a breaking way. - -On Nix, you can obtain these libraries by running - - nix-shell -p libopcodes_2_38 -p libunwind - -and then run fuzz.sh as above. - -# Long-term fuzzing - -To see the full list of targets, the most straightforward way is to run - - source ./fuzz-util.sh - listTargetNames - -To run each of them for an hour, run - - ./cycle.sh - -To run a single fuzztest indefinitely, run - - cargo hfuzz run - -This script uses the `chrt` utility to try to reduce the priority of the -jobs. If you would like to run for longer, the most straightforward way -is to edit `cycle.sh` before starting. To run the fuzz-tests in parallel, -you will need to implement a custom harness. - -# Adding fuzz tests - -All fuzz tests can be found in the `fuzz_target/` directory. Adding a new -one is as simple as copying an existing one and editing the `do_test` -function to do what you want. - -If you need to add dependencies, edit the file `generate-files.sh` to add -it to the generated `Cargo.toml`. - -Once you've added a fuzztest, regenerate the `Cargo.toml` and CI job by -running - - ./generate-files.sh - -Then to test your fuzztest, run - - ./fuzz.sh - -If it is working, you will see a rapid stream of data for many seconds -(you can hit Ctrl+C to stop it early). If not, you should quickly see -an error. - -# Reproducing Failures - -If a fuzztest fails, it will exit with a summary which looks something like - -``` -... - fuzzTarget : hfuzz_target/x86_64-unknown-linux-gnu/release/hashes_sha256 -CRASH: -DESCRIPTION: -ORIG_FNAME: 00000000000000000000000000000000.00000000.honggfuzz.cov -FUZZ_FNAME: hfuzz_workspace/hashes_sha256/SIGABRT.PC.7ffff7c8abc7.STACK.18826d9b64.CODE.-6.ADDR.0.INSTR.mov____%eax,%ebp.fuzz -... -===================================================================== -fff400610004 -``` - -The final line is a hex-encoded version of the input that caused the crash. You -can test this directly by editing the `duplicate_crash` test to copy/paste the -hex output into the call to `extend_vec_from_hex`. Then run the test with - - cargo test - -Note that if you set your `RUSTFLAGS` while fuzzing (see above) you must make -sure they are set the same way when running `cargo test`. - diff --git a/fuzz/cycle.sh b/fuzz/cycle.sh deleted file mode 100755 index 0b59827..0000000 --- a/fuzz/cycle.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash - -# Continuosly cycle over fuzz targets running each for 1 hour. -# It uses chrt SCHED_IDLE so that other process takes priority. -# -# For hfuzz options see https://github.com/google/honggfuzz/blob/master/docs/USAGE.md - -set -e -REPO_DIR=$(git rev-parse --show-toplevel) -# shellcheck source=./fuzz-util.sh -source "$REPO_DIR/fuzz/fuzz-util.sh" - -while : -do - for targetFile in $(listTargetFiles); do - targetName=$(targetFileToName "$targetFile") - echo "Fuzzing target $targetName ($targetFile)" - - # fuzz for one hour - HFUZZ_RUN_ARGS='--run_time 3600' chrt -i 0 cargo hfuzz run "$targetName" - # minimize the corpus - HFUZZ_RUN_ARGS="-i hfuzz_workspace/$targetName/input/ -P -M" chrt -i 0 cargo hfuzz run "$targetName" - done -done - diff --git a/fuzz/fuzz-util.sh b/fuzz/fuzz-util.sh deleted file mode 100755 index 804e0da..0000000 --- a/fuzz/fuzz-util.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env bash - -REPO_DIR=$(git rev-parse --show-toplevel) - -listTargetFiles() { - pushd "$REPO_DIR/fuzz" > /dev/null || exit 1 - find fuzz_targets/ -type f -name "*.rs" - popd > /dev/null || exit 1 -} - -targetFileToName() { - echo "$1" \ - | sed 's/^fuzz_targets\///' \ - | sed 's/\.rs$//' \ - | sed 's/\//_/g' -} - -targetFileToHFuzzInputArg() { - baseName=$(basename "$1") - dirName="${baseName%.*}" - if [ -d "hfuzz_input/$dirName" ]; then - echo "HFUZZ_INPUT_ARGS=\"-f hfuzz_input/$FILE/input\"" - fi -} - -listTargetNames() { - for target in $(listTargetFiles); do - targetFileToName "$target" - done -} - -# Utility function to avoid CI failures on Windows -checkWindowsFiles() { - incorrectFilenames=$(find . -type f -name "*,*" -o -name "*:*" -o -name "*<*" -o -name "*>*" -o -name "*|*" -o -name "*\?*" -o -name "*\**" -o -name "*\"*" | wc -l) - if [ "$incorrectFilenames" -gt 0 ]; then - echo "Bailing early because there is a Windows-incompatible filename in the tree." - exit 2 - fi -} - -# Checks whether a fuzz case output some report, and dumps it in hex -checkReport() { - reportFile="hfuzz_workspace/$1/HONGGFUZZ.REPORT.TXT" - if [ -f "$reportFile" ]; then - cat "$reportFile" - for CASE in "hfuzz_workspace/$1/SIG"*; do - xxd -p -c10000 < "$CASE" - done - exit 1 - fi -} diff --git a/fuzz/fuzz.sh b/fuzz/fuzz.sh deleted file mode 100755 index a22e01c..0000000 --- a/fuzz/fuzz.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash -set -ex - -REPO_DIR=$(git rev-parse --show-toplevel) - -# shellcheck source=./fuzz-util.sh -source "$REPO_DIR/fuzz/fuzz-util.sh" - -# Check that input files are correct Windows file names -checkWindowsFiles - -if [ "$1" == "" ]; then - targetFiles="$(listTargetFiles)" -else - targetFiles=fuzz_targets/"$1".rs -fi - -cargo --version -rustc --version - -# Testing -cargo install --force honggfuzz --no-default-features -for targetFile in $targetFiles; do - targetName=$(targetFileToName "$targetFile") - echo "Fuzzing target $targetName ($targetFile)" - if [ -d "hfuzz_input/$targetName" ]; then - HFUZZ_INPUT_ARGS="-f hfuzz_input/$targetName/input\"" - else - HFUZZ_INPUT_ARGS="" - fi - HFUZZ_RUN_ARGS="--run_time 3600 --exit_upon_crash -v $HFUZZ_INPUT_ARGS" cargo hfuzz run "$targetName" - - checkReport "$targetName" -done diff --git a/fuzz/fuzz_targets/encode.rs b/fuzz/fuzz_targets/encode.rs deleted file mode 100644 index 3962449..0000000 --- a/fuzz/fuzz_targets/encode.rs +++ /dev/null @@ -1,79 +0,0 @@ -use std::fmt; - -use hex::DisplayHex; -use honggfuzz::fuzz; - -/// A struct that always uses hex when in string form. -pub struct Hexy<'s> { - // Some opaque data. - data: &'s [u8], -} - -impl Hexy<'_> { - /// Demonstrates getting internal opaque data as a byte slice. - pub fn as_bytes(&self) -> &[u8] { self.data } -} - -impl fmt::LowerHex for Hexy<'_> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::LowerHex::fmt(&self.data.as_hex(), f) - } -} - -impl fmt::UpperHex for Hexy<'_> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::UpperHex::fmt(&self.data.as_hex(), f) - } -} - -fn do_test(data: &[u8]) { - let hexy = Hexy { data }; - - let lower = format!("{:x}", hexy); - assert!(lower.len() % 2 == 0); - println!("lower: {}", lower); - for c in lower.chars() { - assert!(c.is_ascii_lowercase() || c.is_ascii_digit()); - assert!(c.is_ascii_hexdigit()); - } - - let lower = format!("{:X}", hexy); - assert!(lower.len() % 2 == 0); - for c in lower.chars() { - assert!(c.is_ascii_uppercase() || c.is_ascii_digit()); - assert!(c.is_ascii_hexdigit()); - } -} - -fn main() { - loop { - fuzz!(|d| { do_test(d) }); - } -} - -#[cfg(all(test, fuzzing))] -mod tests { - fn extend_vec_from_hex(hex: &str, out: &mut Vec) { - let mut b = 0; - for (idx, c) in hex.as_bytes().iter().enumerate() { - b <<= 4; - match *c { - b'A'..=b'F' => b |= c - b'A' + 10, - b'a'..=b'f' => b |= c - b'a' + 10, - b'0'..=b'9' => b |= c - b'0', - _ => panic!("Bad hex"), - } - if (idx & 1) == 1 { - out.push(b); - b = 0; - } - } - } - - #[test] - fn duplicate_crash() { - let mut a = Vec::new(); - extend_vec_from_hex("e5952d4fff", &mut a); - super::do_test(&a); - } -} diff --git a/fuzz/fuzz_targets/hex.rs b/fuzz/fuzz_targets/hex.rs deleted file mode 100644 index 8d78c58..0000000 --- a/fuzz/fuzz_targets/hex.rs +++ /dev/null @@ -1,46 +0,0 @@ -use hex::{DisplayHex, FromHex}; -use honggfuzz::fuzz; - -const LEN: usize = 32; // Arbitrary amount of data. - -fn do_test(data: &[u8]) { - if let Ok(s) = std::str::from_utf8(data) { - if let Ok(hexy) = <[u8; LEN]>::from_hex(s) { - let got = format!("{:x}", hexy.as_hex()); - assert_eq!(got, s.to_lowercase()); - } - } -} - -fn main() { - loop { - fuzz!(|d| { do_test(d) }); - } -} - -#[cfg(all(test, fuzzing))] -mod tests { - fn extend_vec_from_hex(hex: &str, out: &mut Vec) { - let mut b = 0; - for (idx, c) in hex.as_bytes().iter().enumerate() { - b <<= 4; - match *c { - b'A'..=b'F' => b |= c - b'A' + 10, - b'a'..=b'f' => b |= c - b'a' + 10, - b'0'..=b'9' => b |= c - b'0', - _ => panic!("Bad hex"), - } - if (idx & 1) == 1 { - out.push(b); - b = 0; - } - } - } - - #[test] - fn duplicate_crash() { - let mut a = Vec::new(); - extend_vec_from_hex("41414141414141414141414141414141414141414141414141414141414141414141414241414141414141414141414141414141414141414141414141414141", &mut a); - super::do_test(&a); - } -} diff --git a/fuzz/generate-files.sh b/fuzz/generate-files.sh deleted file mode 100755 index 5ffec5e..0000000 --- a/fuzz/generate-files.sh +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env bash - -set -e - -REPO_DIR=$(git rev-parse --show-toplevel) - -# shellcheck source=./fuzz-util.sh -source "$REPO_DIR/fuzz/fuzz-util.sh" - -# 1. Generate fuzz/Cargo.toml -cat > "$REPO_DIR/fuzz/Cargo.toml" <> "$REPO_DIR/fuzz/Cargo.toml" <> "$REPO_DIR/fuzz/Cargo.toml" < "$REPO_DIR/.github/workflows/cron-daily-fuzz.yml" <executed_\${{ matrix.fuzz_target }} - - uses: actions/upload-artifact@v3 - with: - name: executed_\${{ matrix.fuzz_target }} - path: executed_\${{ matrix.fuzz_target }} - - verify-execution: - if: \${{ !github.event.act }} - needs: fuzz - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 - - name: Display structure of downloaded files - run: ls -R - - run: find executed_* -type f -exec cat {} + | sort > executed - - run: source ./fuzz/fuzz-util.sh && listTargetNames | sort | diff - executed -EOF - diff --git a/src/buf_encoder.rs b/src/buf_encoder.rs deleted file mode 100644 index af63c06..0000000 --- a/src/buf_encoder.rs +++ /dev/null @@ -1,300 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//! Implements a buffered encoder. -//! -//! This is a low-level module, most uses should be satisfied by the `display` module instead. -//! -//! The main type in this module is [`BufEncoder`] which provides buffered hex encoding. -//! `BufEncoder` is faster than the usual `write!(f, "{02x}", b)?` in a for loop because it reduces -//! dynamic dispatch and decreases the number of allocations if a `String` is being created. - -use core::borrow::Borrow; - -use arrayvec::ArrayString; - -use super::{Case, Table}; - -/// Hex-encodes bytes into the provided buffer. -/// -/// This is an important building block for fast hex-encoding. Because string writing tools -/// provided by `core::fmt` involve dynamic dispatch and don't allow reserving capacity in strings -/// buffering the hex and then formatting it is significantly faster. -/// -/// The buffer has a fixed capacity specified when created. The capacity must be an even number since -/// each byte is encoded as two hex characters. -/// -/// # Examples -/// ``` -/// # use hex_conservative::buf_encoder::BufEncoder; -/// # use hex_conservative::Case; -/// let mut encoder = BufEncoder::<4>::new(Case::Lower); -/// encoder.put_byte(0xab); -/// assert_eq!(encoder.as_str(), "ab"); -/// ``` -/// The following code doesn't compile because of odd capacity: -/// ```compile_fail -/// # use hex_conservative::buf_encoder::BufEncoder; -/// # use hex_conservative::Case; -/// let mut encoder = BufEncoder::<3>::new(Case::Lower); -/// # let _ = encoder; -/// ``` -#[derive(Debug)] -pub struct BufEncoder { - buf: ArrayString, - table: &'static Table, -} - -impl BufEncoder { - const _CHECK_EVEN_CAPACITY: () = [(); 1][CAP % 2]; - - /// Creates an empty `BufEncoder` that will encode bytes to hex characters in the given case. - #[inline] - #[allow(clippy::let_unit_value)] // Allow the unit value of the const check - pub fn new(case: Case) -> Self { - let _ = Self::_CHECK_EVEN_CAPACITY; - BufEncoder { buf: ArrayString::new(), table: case.table() } - } - - /// Encodes `byte` as hex and appends it to the buffer. - /// - /// ## Panics - /// - /// The method panics if the buffer is full. - #[inline] - #[track_caller] - pub fn put_byte(&mut self, byte: u8) { - let mut hex_chars = [0u8; 2]; - let hex_str = self.table.byte_to_str(&mut hex_chars, byte); - self.buf.push_str(hex_str); - } - - /// Encodes `bytes` as hex and appends them to the buffer. - /// - /// ## Panics - /// - /// The method panics if the bytes wouldn't fit the buffer. - #[inline] - #[track_caller] - pub fn put_bytes(&mut self, bytes: I) - where - I: IntoIterator, - I::Item: Borrow, - { - self.put_bytes_inner(bytes.into_iter()) - } - - #[inline] - #[track_caller] - fn put_bytes_inner(&mut self, bytes: I) - where - I: Iterator, - I::Item: Borrow, - { - // May give the compiler better optimization opportunity - if let Some(max) = bytes.size_hint().1 { - assert!(max <= self.space_remaining()); - } - for byte in bytes { - self.put_byte(*byte.borrow()); - } - } - - /// Encodes as many `bytes` as fit into the buffer as hex and return the remainder. - /// - /// This method works just like `put_bytes` but instead of panicking it returns the unwritten - /// bytes. The method returns an empty slice if all bytes were written - #[must_use = "this may write only part of the input buffer"] - #[inline] - #[track_caller] - pub fn put_bytes_min<'a>(&mut self, bytes: &'a [u8]) -> &'a [u8] { - let to_write = self.space_remaining().min(bytes.len()); - self.put_bytes(&bytes[..to_write]); - &bytes[to_write..] - } - - /// Returns true if no more bytes can be written into the buffer. - #[inline] - pub fn is_full(&self) -> bool { self.buf.is_full() } - - /// Returns the written bytes as a hex `str`. - #[inline] - pub fn as_str(&self) -> &str { &self.buf } - - /// Resets the buffer to become empty. - #[inline] - pub fn clear(&mut self) { self.buf.clear(); } - - /// How many bytes can be written to this buffer. - /// - /// Note that this returns the number of bytes before encoding, not number of hex digits. - #[inline] - pub fn space_remaining(&self) -> usize { self.buf.remaining_capacity() / 2 } - - pub(crate) fn put_filler(&mut self, filler: char, max_count: usize) -> usize { - let mut buf = [0; 4]; - let filler = filler.encode_utf8(&mut buf); - let max_capacity = self.buf.remaining_capacity() / filler.len(); - let to_write = max_capacity.min(max_count); - - for _ in 0..to_write { - self.buf.push_str(filler); - } - - to_write - } -} - -impl Default for BufEncoder { - fn default() -> Self { Self::new(Case::Lower) } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn empty() { - let encoder = BufEncoder::<2>::new(Case::Lower); - assert_eq!(encoder.as_str(), ""); - assert!(!encoder.is_full()); - - let encoder = BufEncoder::<2>::new(Case::Upper); - assert_eq!(encoder.as_str(), ""); - assert!(!encoder.is_full()); - } - - #[test] - fn single_byte_exact_buf() { - let mut encoder = BufEncoder::<2>::new(Case::Lower); - assert_eq!(encoder.space_remaining(), 1); - encoder.put_byte(42); - assert_eq!(encoder.as_str(), "2a"); - assert_eq!(encoder.space_remaining(), 0); - assert!(encoder.is_full()); - encoder.clear(); - assert_eq!(encoder.space_remaining(), 1); - assert!(!encoder.is_full()); - - let mut encoder = BufEncoder::<2>::new(Case::Upper); - assert_eq!(encoder.space_remaining(), 1); - encoder.put_byte(42); - assert_eq!(encoder.as_str(), "2A"); - assert_eq!(encoder.space_remaining(), 0); - assert!(encoder.is_full()); - encoder.clear(); - assert_eq!(encoder.space_remaining(), 1); - assert!(!encoder.is_full()); - } - - #[test] - fn single_byte_oversized_buf() { - let mut encoder = BufEncoder::<4>::new(Case::Lower); - assert_eq!(encoder.space_remaining(), 2); - encoder.put_byte(42); - assert_eq!(encoder.space_remaining(), 1); - assert_eq!(encoder.as_str(), "2a"); - assert!(!encoder.is_full()); - encoder.clear(); - assert_eq!(encoder.space_remaining(), 2); - assert!(!encoder.is_full()); - - let mut encoder = BufEncoder::<4>::new(Case::Upper); - assert_eq!(encoder.space_remaining(), 2); - encoder.put_byte(42); - assert_eq!(encoder.space_remaining(), 1); - assert_eq!(encoder.as_str(), "2A"); - assert!(!encoder.is_full()); - encoder.clear(); - assert_eq!(encoder.space_remaining(), 2); - assert!(!encoder.is_full()); - } - - #[test] - fn two_bytes() { - let mut encoder = BufEncoder::<4>::new(Case::Lower); - assert_eq!(encoder.space_remaining(), 2); - encoder.put_byte(42); - assert_eq!(encoder.space_remaining(), 1); - encoder.put_byte(255); - assert_eq!(encoder.space_remaining(), 0); - assert_eq!(encoder.as_str(), "2aff"); - assert!(encoder.is_full()); - encoder.clear(); - assert_eq!(encoder.space_remaining(), 2); - assert!(!encoder.is_full()); - - let mut encoder = BufEncoder::<4>::new(Case::Upper); - assert_eq!(encoder.space_remaining(), 2); - encoder.put_byte(42); - assert_eq!(encoder.space_remaining(), 1); - encoder.put_byte(255); - assert_eq!(encoder.space_remaining(), 0); - assert_eq!(encoder.as_str(), "2AFF"); - assert!(encoder.is_full()); - encoder.clear(); - assert_eq!(encoder.space_remaining(), 2); - assert!(!encoder.is_full()); - } - - #[test] - fn put_bytes_min() { - let mut encoder = BufEncoder::<2>::new(Case::Lower); - let remainder = encoder.put_bytes_min(b""); - assert_eq!(remainder, b""); - assert_eq!(encoder.as_str(), ""); - let remainder = encoder.put_bytes_min(b"*"); - assert_eq!(remainder, b""); - assert_eq!(encoder.as_str(), "2a"); - encoder.clear(); - let remainder = encoder.put_bytes_min(&[42, 255]); - assert_eq!(remainder, &[255]); - assert_eq!(encoder.as_str(), "2a"); - } - - #[test] - fn same_as_fmt() { - use core::fmt::{self, Write}; - - struct Writer { - buf: [u8; 2], - pos: usize, - } - - impl Writer { - fn as_str(&self) -> &str { core::str::from_utf8(&self.buf[..self.pos]).unwrap() } - } - - impl Write for Writer { - fn write_str(&mut self, s: &str) -> fmt::Result { - assert!(self.pos <= 2); - if s.len() > 2 - self.pos { - Err(fmt::Error) - } else { - self.buf[self.pos..(self.pos + s.len())].copy_from_slice(s.as_bytes()); - self.pos += s.len(); - Ok(()) - } - } - } - - let mut writer = Writer { buf: [0u8; 2], pos: 0 }; - - let mut encoder = BufEncoder::<2>::new(Case::Lower); - for i in 0..=255 { - write!(writer, "{:02x}", i).unwrap(); - encoder.put_byte(i); - assert_eq!(encoder.as_str(), writer.as_str()); - writer.pos = 0; - encoder.clear(); - } - - let mut encoder = BufEncoder::<2>::new(Case::Upper); - for i in 0..=255 { - write!(writer, "{:02X}", i).unwrap(); - encoder.put_byte(i); - assert_eq!(encoder.as_str(), writer.as_str()); - writer.pos = 0; - encoder.clear(); - } - } -} diff --git a/src/display.rs b/src/display.rs deleted file mode 100644 index 403da48..0000000 --- a/src/display.rs +++ /dev/null @@ -1,1023 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//! Helpers for displaying bytes as hex strings. -//! -//! This module provides a trait for displaying things as hex as well as an implementation for -//! `&[u8]`. -//! -//! For arrays and slices we support padding and precision for length < 512 bytes. -//! -//! # Examples -//! -//! ``` -//! use hex_conservative::DisplayHex; -//! -//! // Display as hex. -//! let v = vec![0xde, 0xad, 0xbe, 0xef]; -//! assert_eq!(format!("{}", v.as_hex()), "deadbeef"); -//! -//! // Get the most significant bytes. -//! let v = vec![0x01, 0x23, 0x45, 0x67]; -//! assert_eq!(format!("{0:.4}", v.as_hex()), "0123"); -//! -//! // Padding with zeros -//! let v = vec![0xab; 2]; -//! assert_eq!(format!("{:0>8}", v.as_hex()), "0000abab"); -//!``` - -#[cfg(all(feature = "alloc", not(feature = "std")))] -use alloc::string::String; -use core::borrow::Borrow; -use core::fmt; - -use super::Case; -#[cfg(any(test, feature = "std"))] -use super::Table; -use crate::buf_encoder::BufEncoder; - -/// Extension trait for types that can be displayed as hex. -/// -/// Types that have a single, obvious text representation being hex should **not** implement this -/// trait and simply implement `Display` instead. -/// -/// This trait should be generally implemented for references only. We would prefer to use GAT but -/// that is beyond our MSRV. As a lint we require the `IsRef` trait which is implemented for all -/// references. -pub trait DisplayHex: Copy + sealed::IsRef + sealed::Sealed { - /// The type providing [`fmt::Display`] implementation. - /// - /// This is usually a wrapper type holding a reference to `Self`. - type Display: fmt::Display + fmt::Debug + fmt::LowerHex + fmt::UpperHex; - - /// Display `Self` as a continuous sequence of ASCII hex chars. - fn as_hex(self) -> Self::Display; - - /// Create a lower-hex-encoded string. - /// - /// A shorthand for `to_hex_string(Case::Lower)`, so that `Case` doesn't need to be imported. - /// - /// This may be faster than `.display_hex().to_string()` because it uses `reserve_suggestion`. - #[cfg(feature = "alloc")] - fn to_lower_hex_string(self) -> String { self.to_hex_string(Case::Lower) } - - /// Create an upper-hex-encoded string. - /// - /// A shorthand for `to_hex_string(Case::Upper)`, so that `Case` doesn't need to be imported. - /// - /// This may be faster than `.display_hex().to_string()` because it uses `reserve_suggestion`. - #[cfg(feature = "alloc")] - fn to_upper_hex_string(self) -> String { self.to_hex_string(Case::Upper) } - - /// Create a hex-encoded string. - /// - /// This may be faster than `.display_hex().to_string()` because it uses `reserve_suggestion`. - #[cfg(feature = "alloc")] - fn to_hex_string(self, case: Case) -> String { - let mut string = String::new(); - self.append_hex_to_string(case, &mut string); - string - } - - /// Appends hex-encoded content to an existing `String`. - /// - /// This may be faster than `write!(string, "{:x}", self.as_hex())` because it uses - /// `hex_reserve_sugggestion`. - #[cfg(feature = "alloc")] - fn append_hex_to_string(self, case: Case, string: &mut String) { - use fmt::Write; - - string.reserve(self.hex_reserve_suggestion()); - match case { - Case::Lower => write!(string, "{:x}", self.as_hex()), - Case::Upper => write!(string, "{:X}", self.as_hex()), - } - .unwrap_or_else(|_| { - let name = core::any::type_name::(); - // We don't expect `std` to ever be buggy, so the bug is most likely in the `Display` - // impl of `Self::Display`. - panic!("The implementation of Display for {} returned an error when it shouldn't", name) - }) - } - - /// Hints how many bytes to reserve when creating a `String`. - /// - /// If you don't know you can just return 0 and take the perf hit. - // We prefix the name with `hex_` to avoid potential collision with other methods. - fn hex_reserve_suggestion(self) -> usize; -} - -fn internal_display(bytes: &[u8], f: &mut fmt::Formatter, case: Case) -> fmt::Result { - use fmt::Write; - // There are at least two optimizations left: - // - // * Reusing the buffer (encoder) which may decrease the number of virtual calls - // * Not recursing, avoiding another 1024B allocation and zeroing - // - // This would complicate the code so I was too lazy to do them but feel free to send a PR! - - let mut encoder = BufEncoder::<1024>::new(case); - let pad_right = write_pad_left(f, bytes.len(), &mut encoder)?; - - if f.alternate() { - f.write_str("0x")?; - } - match f.precision() { - Some(max) if bytes.len() > max / 2 => { - write!(f, "{}", bytes[..(max / 2)].as_hex())?; - if max % 2 == 1 { - f.write_char(case.table().byte_to_chars(bytes[max / 2])[0])?; - } - } - Some(_) | None => { - let mut chunks = bytes.chunks_exact(512); - for chunk in &mut chunks { - encoder.put_bytes(chunk); - f.write_str(encoder.as_str())?; - encoder.clear(); - } - encoder.put_bytes(chunks.remainder()); - f.write_str(encoder.as_str())?; - } - } - - write_pad_right(f, pad_right, &mut encoder) -} - -fn write_pad_left( - f: &mut fmt::Formatter, - bytes_len: usize, - encoder: &mut BufEncoder<1024>, -) -> Result { - let pad_right = if let Some(width) = f.width() { - // Add space for 2 characters if the '#' flag is set - let full_string_len = if f.alternate() { bytes_len * 2 + 2 } else { bytes_len * 2 }; - let string_len = match f.precision() { - Some(max) => core::cmp::min(max, full_string_len), - None => full_string_len, - }; - - if string_len < width { - let (left, right) = match f.align().unwrap_or(fmt::Alignment::Left) { - fmt::Alignment::Left => (0, width - string_len), - fmt::Alignment::Right => (width - string_len, 0), - fmt::Alignment::Center => ((width - string_len) / 2, (width - string_len + 1) / 2), - }; - // Avoid division by zero and optimize for common case. - if left > 0 { - let c = f.fill(); - let chunk_len = encoder.put_filler(c, left); - let padding = encoder.as_str(); - for _ in 0..(left / chunk_len) { - f.write_str(padding)?; - } - f.write_str(&padding[..((left % chunk_len) * c.len_utf8())])?; - encoder.clear(); - } - right - } else { - 0 - } - } else { - 0 - }; - Ok(pad_right) -} - -fn write_pad_right( - f: &mut fmt::Formatter, - pad_right: usize, - encoder: &mut BufEncoder<1024>, -) -> fmt::Result { - // Avoid division by zero and optimize for common case. - if pad_right > 0 { - encoder.clear(); - let c = f.fill(); - let chunk_len = encoder.put_filler(c, pad_right); - let padding = encoder.as_str(); - for _ in 0..(pad_right / chunk_len) { - f.write_str(padding)?; - } - f.write_str(&padding[..((pad_right % chunk_len) * c.len_utf8())])?; - } - Ok(()) -} - -mod sealed { - /// Trait marking a shared reference. - pub trait IsRef: Copy {} - - impl IsRef for &'_ T {} - - /// Used to seal the `DisplayHex` trait. - pub trait Sealed {} - - impl Sealed for &'_ [u8] {} - - #[cfg(feature = "alloc")] - impl Sealed for &'_ alloc::vec::Vec {} - - macro_rules! impl_array_sealed { - ($($len:expr),*) => { - $( - impl<'a> Sealed for &'a [u8; $len] {} - )* - } - } - - // Same as call to `impl_array_as_hex` below. - #[rustfmt::skip] - impl_array_sealed!( - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 20, 32, 33, 64, 65, - 128, 256, 512, 1024, 2048, 4096 - ); -} - -impl<'a> DisplayHex for &'a [u8] { - type Display = DisplayByteSlice<'a>; - - #[inline] - fn as_hex(self) -> Self::Display { DisplayByteSlice { bytes: self } } - - #[inline] - fn hex_reserve_suggestion(self) -> usize { - // Since the string wouldn't fit into address space if this overflows (actually even for - // smaller amounts) it's better to panic right away. It should also give the optimizer - // better opportunities. - self.len().checked_mul(2).expect("the string wouldn't fit into address space") - } -} - -#[cfg(feature = "alloc")] -impl<'a> DisplayHex for &'a alloc::vec::Vec { - type Display = DisplayByteSlice<'a>; - - #[inline] - fn as_hex(self) -> Self::Display { DisplayByteSlice { bytes: self } } - - #[inline] - fn hex_reserve_suggestion(self) -> usize { - // Since the string wouldn't fit into address space if this overflows (actually even for - // smaller amounts) it's better to panic right away. It should also give the optimizer - // better opportunities. - self.len().checked_mul(2).expect("the string wouldn't fit into address space") - } -} - -/// Displays byte slice as hex. -/// -/// Created by [`<&[u8] as DisplayHex>::as_hex`](DisplayHex::as_hex). -pub struct DisplayByteSlice<'a> { - // pub because we want to keep lengths in sync - pub(crate) bytes: &'a [u8], -} - -impl DisplayByteSlice<'_> { - fn display(&self, f: &mut fmt::Formatter, case: Case) -> fmt::Result { - internal_display(self.bytes, f, case) - } -} - -impl fmt::Display for DisplayByteSlice<'_> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) } -} - -impl fmt::Debug for DisplayByteSlice<'_> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) } -} - -impl fmt::LowerHex for DisplayByteSlice<'_> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.display(f, Case::Lower) } -} - -impl fmt::UpperHex for DisplayByteSlice<'_> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.display(f, Case::Upper) } -} - -/// Displays byte array as hex. -/// -/// Created by [`<&[u8; CAP / 2] as DisplayHex>::as_hex`](DisplayHex::as_hex). -pub struct DisplayArray<'a, const CAP: usize> { - array: &'a [u8], -} - -impl<'a, const CAP: usize> DisplayArray<'a, CAP> { - /// Creates the wrapper. - /// - /// # Panics - /// - /// When the length of array is greater than capacity / 2. - #[inline] - fn new(array: &'a [u8]) -> Self { - assert!(array.len() <= CAP / 2); - DisplayArray { array } - } - - fn display(&self, f: &mut fmt::Formatter, case: Case) -> fmt::Result { - internal_display(self.array, f, case) - } -} - -impl fmt::Display for DisplayArray<'_, LEN> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) } -} - -impl fmt::Debug for DisplayArray<'_, LEN> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) } -} - -impl fmt::LowerHex for DisplayArray<'_, LEN> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.display(f, Case::Lower) } -} - -impl fmt::UpperHex for DisplayArray<'_, LEN> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.display(f, Case::Upper) } -} - -macro_rules! impl_array_as_hex { - ($($len:expr),*) => { - $( - impl<'a> DisplayHex for &'a [u8; $len] { - type Display = DisplayArray<'a, {$len * 2}>; - - fn as_hex(self) -> Self::Display { - DisplayArray::new(self) - } - - fn hex_reserve_suggestion(self) -> usize { $len * 2 } - } - )* - } -} - -// Same as call to `impl_array_sealed` above. -#[rustfmt::skip] -impl_array_as_hex!( - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 20, 32, 33, 64, 65, - 128, 256, 512, 1024, 2048, 4096 -); - -/// Format known-length array as hex. -/// -/// This supports all formatting options of formatter and may be faster than calling `as_hex()` on -/// an arbitrary `&[u8]`. Note that the implementation intentionally keeps leading zeros even when -/// not requested. This is designed to display values such as hashes and keys and removing leading -/// zeros would be confusing. -/// -/// Note that the bytes parameter is `IntoIterator` this means that if you would like to do some -/// manipulation to the byte array before formatting then you can. For example `bytes.iter().rev()` -/// to print the array backwards. -/// -/// ## Parameters -/// -/// * `$formatter` - a [`fmt::Formatter`]. -/// * `$len` known length of `$bytes`, must be a const expression. -/// * `$bytes` - bytes to be encoded, most likely a reference to an array. -/// * `$case` - value of type [`Case`] determining whether to format as lower or upper case. -/// -/// ## Panics -/// -/// This macro panics if `$len` is not equal to `$bytes.len()`. It also fails to compile if `$len` -/// is more than half of `usize::MAX`. -#[macro_export] -macro_rules! fmt_hex_exact { - ($formatter:expr, $len:expr, $bytes:expr, $case:expr) => {{ - // statically check $len - #[allow(deprecated)] - const _: () = [()][($len > usize::MAX / 2) as usize]; - assert_eq!($bytes.len(), $len); - $crate::display::fmt_hex_exact_fn::<_, { $len * 2 }>($formatter, $bytes, $case) - }}; -} -pub use fmt_hex_exact; - -/// Adds `core::fmt` trait implementations to type `$ty`. -/// -/// Implements: -/// -/// - `fmt::{LowerHex, UpperHex}` using [`fmt_hex_exact`]. -/// - `fmt::{Display, Debug}` by calling `LowerHex`. -/// -/// Requires: -/// -/// - `$ty` must implement `IntoIterator>`. -/// -/// ## Parameters -/// -/// * `$ty` - the type to implement traits on. -/// * `$len` - known length of `$bytes`, must be a const expression. -/// * `$bytes` - bytes to be encoded, most likely a reference to an array. -/// * `$reverse` - true if you want the array to be displayed backwards. -/// * `$gen: $gent` - optional generic type(s) and trait bound(s) to put on `$ty` e.g, `F: Foo`. -/// -/// ## Examples -/// -/// ``` -/// # use core::borrow::Borrow; -/// # use hex_conservative::impl_fmt_traits; -/// struct Wrapper([u8; 4]); -/// -/// impl Borrow<[u8]> for Wrapper { -/// fn borrow(&self) -> &[u8] { &self.0[..] } -/// } -/// -/// impl_fmt_traits! { -/// impl fmt_traits for Wrapper { -/// const LENGTH: usize = 4; -/// } -/// } -/// -/// let w = Wrapper([0x12, 0x34, 0x56, 0x78]); -/// assert_eq!(format!("{}", w), "12345678"); -/// ``` -/// -/// We support generics on `$ty`: -/// -/// ``` -/// # use core::borrow::Borrow; -/// # use core::marker::PhantomData; -/// # use hex_conservative::impl_fmt_traits; -/// struct Wrapper([u8; 4], PhantomData); -/// -/// // `Clone` is just some arbitrary trait. -/// impl Borrow<[u8]> for Wrapper { -/// fn borrow(&self) -> &[u8] { &self.0[..] } -/// } -/// -/// impl_fmt_traits! { -/// impl fmt_traits for Wrapper { -/// const LENGTH: usize = 4; -/// } -/// } -/// -/// let w = Wrapper([0x12, 0x34, 0x56, 0x78], PhantomData::); -/// assert_eq!(format!("{}", w), "12345678"); -/// ``` -/// -/// And also, as is required by `rust-bitcoin`, we support displaying -/// the hex string byte-wise backwards: -/// -/// ``` -/// # use core::borrow::Borrow; -/// # use hex_conservative::impl_fmt_traits; -/// struct Wrapper([u8; 4]); -/// -/// impl Borrow<[u8]> for Wrapper { -/// fn borrow(&self) -> &[u8] { &self.0[..] } -/// } -/// -/// impl_fmt_traits! { -/// #[display_backward(true)] -/// impl fmt_traits for Wrapper { -/// const LENGTH: usize = 4; -/// } -/// } -/// let w = Wrapper([0x12, 0x34, 0x56, 0x78]); -/// assert_eq!(format!("{}", w), "78563412"); -/// ``` -#[macro_export] -macro_rules! impl_fmt_traits { - // Without generic and trait bounds and without display_backward attribute. - (impl fmt_traits for $ty:ident { const LENGTH: usize = $len:expr; }) => { - $crate::impl_fmt_traits! { - #[display_backward(false)] - impl<> fmt_traits for $ty<> { - const LENGTH: usize = $len; - } - } - }; - // Without generic and trait bounds and with display_backward attribute. - (#[display_backward($reverse:expr)] impl fmt_traits for $ty:ident { const LENGTH: usize = $len:expr; }) => { - $crate::impl_fmt_traits! { - #[display_backward($reverse)] - impl<> fmt_traits for $ty<> { - const LENGTH: usize = $len; - } - } - }; - // With generic and trait bounds and without display_backward attribute. - (impl<$($gen:ident: $gent:ident),*> fmt_traits for $ty:ident<$($unused:ident),*> { const LENGTH: usize = $len:expr; }) => { - $crate::impl_fmt_traits! { - #[display_backward(false)] - impl<$($gen: $gent),*> fmt_traits for $ty<$($unused),*> { - const LENGTH: usize = $len; - } - } - }; - // With generic and trait bounds and display_backward attribute. - (#[display_backward($reverse:expr)] impl<$($gen:ident: $gent:ident),*> fmt_traits for $ty:ident<$($unused:ident),*> { const LENGTH: usize = $len:expr; }) => { - impl<$($gen: $gent),*> $crate::_export::_core::fmt::LowerHex for $ty<$($gen),*> { - #[inline] - fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result { - let case = $crate::Case::Lower; - - if $reverse { - let bytes = $crate::_export::_core::borrow::Borrow::<[u8]>::borrow(self).iter().rev(); - $crate::fmt_hex_exact!(f, $len, bytes, case) - } else { - let bytes = $crate::_export::_core::borrow::Borrow::<[u8]>::borrow(self).iter(); - $crate::fmt_hex_exact!(f, $len, bytes, case) - } - } - } - - impl<$($gen: $gent),*> $crate::_export::_core::fmt::UpperHex for $ty<$($gen),*> { - #[inline] - fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result { - let case = $crate::Case::Upper; - - if $reverse { - let bytes = $crate::_export::_core::borrow::Borrow::<[u8]>::borrow(self).iter().rev(); - $crate::fmt_hex_exact!(f, $len, bytes, case) - } else { - let bytes = $crate::_export::_core::borrow::Borrow::<[u8]>::borrow(self).iter(); - $crate::fmt_hex_exact!(f, $len, bytes, case) - } - } - } - - impl<$($gen: $gent),*> $crate::_export::_core::fmt::Display for $ty<$($gen),*> { - #[inline] - fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result { - $crate::_export::_core::fmt::LowerHex::fmt(self, f) - } - } - - impl<$($gen: $gent),*> $crate::_export::_core::fmt::Debug for $ty<$($gen),*> { - #[inline] - fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result { - $crate::_export::_core::fmt::LowerHex::fmt(&self, f) - } - } - }; -} -pub use impl_fmt_traits; - -// Implementation detail of `write_hex_exact` macro to de-duplicate the code -// -// Whether hex is an integer or a string is debatable, we cater a little bit to each. -// - We support users adding `0x` prefix using "{:#}" (treating hex like an integer). -// - We support limiting the output using precision "{:.10}" (treating hex like a string). -// -// This assumes `bytes.len() * 2 == N`. -#[doc(hidden)] -#[inline] -pub fn fmt_hex_exact_fn( - f: &mut fmt::Formatter, - bytes: I, - case: Case, -) -> fmt::Result -where - I: IntoIterator, - I::Item: Borrow, -{ - let mut padding_encoder = BufEncoder::<1024>::new(case); - let pad_right = write_pad_left(f, N / 2, &mut padding_encoder)?; - - if f.alternate() { - f.write_str("0x")?; - } - let mut encoder = BufEncoder::::new(case); - let encoded = match f.precision() { - Some(p) if p < N => { - let n = (p + 1) / 2; - encoder.put_bytes(bytes.into_iter().take(n)); - &encoder.as_str()[..p] - } - _ => { - encoder.put_bytes(bytes); - encoder.as_str() - } - }; - f.write_str(encoded)?; - - write_pad_right(f, pad_right, &mut padding_encoder) -} - -/// Given a `T:` [`fmt::Write`], `HexWriter` implements [`std::io::Write`] -/// and writes the source bytes to its inner `T` as hex characters. -#[cfg(any(test, feature = "std"))] -#[cfg_attr(docsrs, doc(cfg(any(test, feature = "std"))))] -#[derive(Debug)] -pub struct HexWriter { - writer: T, - table: &'static Table, -} - -#[cfg(any(test, feature = "std"))] -#[cfg_attr(docsrs, doc(cfg(any(test, feature = "std"))))] -impl HexWriter { - /// Creates a `HexWriter` that writes the source bytes to `dest` as hex characters - /// in the given `case`. - /// - /// Note even though we take ownership of the writer one can also call this with `&mut dest`. - pub fn new(dest: T, case: Case) -> Self { Self { writer: dest, table: case.table() } } - /// Consumes this `HexWriter` returning the inner `T`. - pub fn into_inner(self) -> T { self.writer } -} - -#[cfg(any(test, feature = "std"))] -#[cfg_attr(docsrs, doc(cfg(any(test, feature = "std"))))] -impl std::io::Write for HexWriter -where - T: core::fmt::Write, -{ - /// # Errors - /// - /// If no bytes could be written to this `HexWriter`, and the provided buffer is not empty, - /// returns [`std::io::ErrorKind::Other`], otherwise returns `Ok`. - fn write(&mut self, buf: &[u8]) -> Result { - let mut n = 0; - for byte in buf { - let mut hex_chars = [0u8; 2]; - let hex_str = self.table.byte_to_str(&mut hex_chars, *byte); - if self.writer.write_str(hex_str).is_err() { - break; - } - n += 1; - } - if n == 0 && !buf.is_empty() { - Err(std::io::ErrorKind::Other.into()) - } else { - Ok(n) - } - } - fn flush(&mut self) -> Result<(), std::io::Error> { Ok(()) } -} - -#[cfg(test)] -mod tests { - #[cfg(feature = "alloc")] - use super::*; - - #[cfg(feature = "alloc")] - mod alloc { - use core::marker::PhantomData; - - use super::*; - - fn check_encoding(bytes: &[u8]) { - use core::fmt::Write; - - let s1 = bytes.to_lower_hex_string(); - let mut s2 = String::with_capacity(bytes.len() * 2); - for b in bytes { - write!(s2, "{:02x}", b).unwrap(); - } - assert_eq!(s1, s2); - } - - #[test] - fn empty() { check_encoding(b""); } - - #[test] - fn single() { check_encoding(b"*"); } - - #[test] - fn two() { check_encoding(b"*x"); } - - #[test] - fn just_below_boundary() { check_encoding(&[42; 512]); } - - #[test] - fn just_above_boundary() { check_encoding(&[42; 513]); } - - #[test] - fn just_above_double_boundary() { check_encoding(&[42; 1025]); } - - #[test] - fn fmt_exact_macro() { - use crate::alloc::string::ToString; - - struct Dummy([u8; 32]); - - impl fmt::Display for Dummy { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt_hex_exact!(f, 32, &self.0, Case::Lower) - } - } - let dummy = Dummy([42; 32]); - assert_eq!(dummy.to_string(), "2a".repeat(32)); - assert_eq!(format!("{:.10}", dummy), "2a".repeat(5)); - assert_eq!(format!("{:.11}", dummy), "2a".repeat(5) + "2"); - assert_eq!(format!("{:.65}", dummy), "2a".repeat(32)); - } - - macro_rules! define_dummy { - ($len:literal) => { - struct Dummy([u8; $len]); - impl fmt::Debug for Dummy { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt_hex_exact!(f, $len, &self.0, Case::Lower) - } - } - impl fmt::Display for Dummy { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt_hex_exact!(f, $len, &self.0, Case::Lower) - } - } - }; - } - - macro_rules! test_display_hex { - ($fs: expr, $a: expr, $check: expr) => { - let array = $a; - let slice = &$a; - let vec = Vec::from($a); - let dummy = Dummy($a); - assert_eq!(format!($fs, array.as_hex()), $check); - assert_eq!(format!($fs, slice.as_hex()), $check); - assert_eq!(format!($fs, vec.as_hex()), $check); - assert_eq!(format!($fs, dummy), $check); - }; - } - - #[test] - fn alternate_flag() { - define_dummy!(4); - - test_display_hex!("{:#?}", [0xc0, 0xde, 0xca, 0xfe], "0xc0decafe"); - test_display_hex!("{:#}", [0xc0, 0xde, 0xca, 0xfe], "0xc0decafe"); - } - - #[test] - fn display_short_with_padding() { - define_dummy!(2); - - test_display_hex!("Hello {:<8}!", [0xbe, 0xef], "Hello beef !"); - test_display_hex!("Hello {:-<8}!", [0xbe, 0xef], "Hello beef----!"); - test_display_hex!("Hello {:^8}!", [0xbe, 0xef], "Hello beef !"); - test_display_hex!("Hello {:>8}!", [0xbe, 0xef], "Hello beef!"); - - test_display_hex!("Hello {:<#8}!", [0xbe, 0xef], "Hello 0xbeef !"); - test_display_hex!("Hello {:-<#8}!", [0xbe, 0xef], "Hello 0xbeef--!"); - test_display_hex!("Hello {:^#8}!", [0xbe, 0xef], "Hello 0xbeef !"); - test_display_hex!("Hello {:>#8}!", [0xbe, 0xef], "Hello 0xbeef!"); - } - - #[test] - fn display_long() { - define_dummy!(512); - // Note this string is shorter than the one above. - let a = [0xab; 512]; - - let mut want = "0".repeat(2000 - 1024); - want.extend(core::iter::repeat("ab").take(512)); - test_display_hex!("{:0>2000}", a, want); - - let mut want = "0".repeat(2000 - 1026); - want.push_str("0x"); - want.extend(core::iter::repeat("ab").take(512)); - test_display_hex!("{:0>#2000}", a, want); - } - - // Precision and padding act the same as for strings in the stdlib (because we use `Formatter::pad`). - - #[test] - fn precision_truncates() { - // Precision gets the most significant bytes. - // Remember the integer is number of hex chars not number of bytes. - define_dummy!(4); - - test_display_hex!("{0:.4}", [0x12, 0x34, 0x56, 0x78], "1234"); - test_display_hex!("{0:.5}", [0x12, 0x34, 0x56, 0x78], "12345"); - - test_display_hex!("{0:#.4}", [0x12, 0x34, 0x56, 0x78], "0x1234"); - test_display_hex!("{0:#.5}", [0x12, 0x34, 0x56, 0x78], "0x12345"); - } - - #[test] - fn precision_with_padding_truncates() { - // Precision gets the most significant bytes. - define_dummy!(4); - - test_display_hex!("{0:10.4}", [0x12, 0x34, 0x56, 0x78], "1234 "); - test_display_hex!("{0:10.5}", [0x12, 0x34, 0x56, 0x78], "12345 "); - - test_display_hex!("{0:#10.4}", [0x12, 0x34, 0x56, 0x78], "0x1234 "); - test_display_hex!("{0:#10.5}", [0x12, 0x34, 0x56, 0x78], "0x12345 "); - } - - #[test] - fn precision_with_padding_pads_right() { - define_dummy!(4); - - test_display_hex!("{0:10.20}", [0x12, 0x34, 0x56, 0x78], "12345678 "); - test_display_hex!("{0:10.14}", [0x12, 0x34, 0x56, 0x78], "12345678 "); - - test_display_hex!("{0:#12.20}", [0x12, 0x34, 0x56, 0x78], "0x12345678 "); - test_display_hex!("{0:#12.14}", [0x12, 0x34, 0x56, 0x78], "0x12345678 "); - } - - #[test] - fn precision_with_padding_pads_left() { - define_dummy!(4); - - test_display_hex!("{0:>10.20}", [0x12, 0x34, 0x56, 0x78], " 12345678"); - - test_display_hex!("{0:>#12.20}", [0x12, 0x34, 0x56, 0x78], " 0x12345678"); - } - - #[test] - fn precision_with_padding_pads_center() { - define_dummy!(4); - - test_display_hex!("{0:^10.20}", [0x12, 0x34, 0x56, 0x78], " 12345678 "); - - test_display_hex!("{0:^#12.20}", [0x12, 0x34, 0x56, 0x78], " 0x12345678 "); - } - - #[test] - fn precision_with_padding_pads_center_odd() { - define_dummy!(4); - - test_display_hex!("{0:^11.20}", [0x12, 0x34, 0x56, 0x78], " 12345678 "); - - test_display_hex!("{0:^#13.20}", [0x12, 0x34, 0x56, 0x78], " 0x12345678 "); - } - - #[test] - fn precision_does_not_extend() { - define_dummy!(4); - - test_display_hex!("{0:.16}", [0x12, 0x34, 0x56, 0x78], "12345678"); - - test_display_hex!("{0:#.16}", [0x12, 0x34, 0x56, 0x78], "0x12345678"); - } - - #[test] - fn padding_extends() { - define_dummy!(2); - - test_display_hex!("{:0>8}", [0xab; 2], "0000abab"); - - test_display_hex!("{:0>#8}", [0xab; 2], "000xabab"); - } - - #[test] - fn padding_does_not_truncate() { - define_dummy!(4); - - test_display_hex!("{:0>4}", [0x12, 0x34, 0x56, 0x78], "12345678"); - test_display_hex!("{:0>4}", [0x12, 0x34, 0x56, 0x78], "12345678"); - - test_display_hex!("{:0>#4}", [0x12, 0x34, 0x56, 0x78], "0x12345678"); - test_display_hex!("{:0>#4}", [0x12, 0x34, 0x56, 0x78], "0x12345678"); - } - - // Tests `impl_fmt_traits` in module scope. - // ref: https://rust-lang.github.io/api-guidelines/macros.html#c-anywhere - #[allow(dead_code)] - struct Wrapper([u8; 4]); - - impl Borrow<[u8]> for Wrapper { - fn borrow(&self) -> &[u8] { &self.0[..] } - } - - impl_fmt_traits! { - #[display_backward(false)] - impl fmt_traits for Wrapper { - const LENGTH: usize = 4; - } - } - - #[test] - fn hex_fmt_impl_macro_forward() { - struct Wrapper([u8; 4]); - - impl Borrow<[u8]> for Wrapper { - fn borrow(&self) -> &[u8] { &self.0[..] } - } - - impl_fmt_traits! { - #[display_backward(false)] - impl fmt_traits for Wrapper { - const LENGTH: usize = 4; - } - } - - let tc = Wrapper([0x12, 0x34, 0x56, 0x78]); - - let want = "12345678"; - let got = format!("{}", tc); - assert_eq!(got, want); - } - - #[test] - fn hex_fmt_impl_macro_backwards() { - struct Wrapper([u8; 4]); - - impl Borrow<[u8]> for Wrapper { - fn borrow(&self) -> &[u8] { &self.0[..] } - } - - impl_fmt_traits! { - #[display_backward(true)] - impl fmt_traits for Wrapper { - const LENGTH: usize = 4; - } - } - - let tc = Wrapper([0x12, 0x34, 0x56, 0x78]); - - let want = "78563412"; - let got = format!("{}", tc); - assert_eq!(got, want); - } - - #[test] - fn hex_fmt_impl_macro_gen_forward() { - struct Wrapper([u8; 4], PhantomData); - - impl Borrow<[u8]> for Wrapper { - fn borrow(&self) -> &[u8] { &self.0[..] } - } - - impl_fmt_traits! { - #[display_backward(false)] - impl fmt_traits for Wrapper { - const LENGTH: usize = 4; - } - } - - // We just use `u32` here as some arbitrary type that implements some arbitrary trait. - let tc = Wrapper([0x12, 0x34, 0x56, 0x78], PhantomData::); - - let want = "12345678"; - let got = format!("{}", tc); - assert_eq!(got, want); - } - - #[test] - fn hex_fmt_impl_macro_gen_backwards() { - struct Wrapper([u8; 4], PhantomData); - - impl Borrow<[u8]> for Wrapper { - fn borrow(&self) -> &[u8] { &self.0[..] } - } - - impl_fmt_traits! { - #[display_backward(true)] - impl fmt_traits for Wrapper { - const LENGTH: usize = 4; - } - } - - // We just use `u32` here as some arbitrary type that implements some arbitrary trait. - let tc = Wrapper([0x12, 0x34, 0x56, 0x78], PhantomData::); - - let want = "78563412"; - let got = format!("{}", tc); - assert_eq!(got, want); - } - } - - #[cfg(feature = "std")] - mod std { - use std::io::Write as _; - - use arrayvec::ArrayString; - - use super::{Case, DisplayHex, HexWriter}; - - #[test] - fn hex_writer() { - use std::io::{ErrorKind, Result, Write}; - - use super::Case::{Lower, Upper}; - - macro_rules! test_hex_writer { - ($cap:expr, $case: expr, $src: expr, $want: expr, $hex_result: expr) => { - let dest_buf = ArrayString::<$cap>::new(); - let mut dest = HexWriter::new(dest_buf, $case); - let got = dest.write($src); - match $want { - Ok(n) => assert_eq!(got.unwrap(), n), - Err(e) => assert_eq!(got.unwrap_err().kind(), e.kind()), - } - assert_eq!(dest.into_inner().as_str(), $hex_result); - }; - } - - test_hex_writer!(0, Lower, &[], Result::Ok(0), ""); - test_hex_writer!(0, Lower, &[0xab, 0xcd], Result::Err(ErrorKind::Other.into()), ""); - test_hex_writer!(1, Lower, &[0xab, 0xcd], Result::Err(ErrorKind::Other.into()), ""); - test_hex_writer!(2, Lower, &[0xab, 0xcd], Result::Ok(1), "ab"); - test_hex_writer!(3, Lower, &[0xab, 0xcd], Result::Ok(1), "ab"); - test_hex_writer!(4, Lower, &[0xab, 0xcd], Result::Ok(2), "abcd"); - test_hex_writer!(8, Lower, &[0xab, 0xcd], Result::Ok(2), "abcd"); - test_hex_writer!(8, Upper, &[0xab, 0xcd], Result::Ok(2), "ABCD"); - - let vec: Vec<_> = (0u8..32).collect(); - let mut writer = HexWriter::new(String::new(), Lower); - writer.write_all(&vec[..]).unwrap(); - assert_eq!(writer.into_inner(), vec.to_lower_hex_string()); - } - - #[test] - fn hex_writer_accepts_and_mut() { - let mut dest_buf = ArrayString::<64>::new(); - let mut dest = HexWriter::new(&mut dest_buf, Case::Lower); - let _got = dest.write(b"some data").unwrap(); - } - } -} diff --git a/src/iter.rs b/src/iter.rs index 3897937..e62defb 100644 --- a/src/iter.rs +++ b/src/iter.rs @@ -363,16 +363,6 @@ mod tests { assert_eq!(Table::UPPER.byte_to_chars(0xad), ['A', 'D']); assert_eq!(Table::UPPER.byte_to_chars(0xff), ['F', 'F']); - let mut buf = [0u8; 2]; - assert_eq!(Table::LOWER.byte_to_str(&mut buf, 0x00), "00"); - assert_eq!(Table::LOWER.byte_to_str(&mut buf, 0x0a), "0a"); - assert_eq!(Table::LOWER.byte_to_str(&mut buf, 0xad), "ad"); - assert_eq!(Table::LOWER.byte_to_str(&mut buf, 0xff), "ff"); - - assert_eq!(Table::UPPER.byte_to_str(&mut buf, 0x00), "00"); - assert_eq!(Table::UPPER.byte_to_str(&mut buf, 0x0a), "0A"); - assert_eq!(Table::UPPER.byte_to_str(&mut buf, 0xad), "AD"); - assert_eq!(Table::UPPER.byte_to_str(&mut buf, 0xff), "FF"); } #[test] diff --git a/src/lib.rs b/src/lib.rs index 1d56ad3..399a8b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,35 +4,11 @@ //! //! General purpose hex encoding/decoding library with a conservative MSRV and dependency policy. //! -//! ## Basic Usage -//! ``` -//! # #[cfg(feature = "alloc")] { -//! // In your manifest use the `package` key to improve import ergonomics. -//! // hex = { package = "hex-conservative", version = "*" } -//! # use hex_conservative as hex; // No need for this if using `package` as above. -//! use hex::prelude::*; +//! ## Stabalization strategy //! -//! // Decode an arbitrary length hex string into a vector. -//! let v = Vec::from_hex("deadbeef").expect("valid hex digits"); -//! // Or a known length hex string into a fixed size array. -//! let a = <[u8; 4]>::from_hex("deadbeef").expect("valid length and valid hex digits"); -//! -//! // We support `LowerHex` and `UpperHex` out of the box for `[u8]` slices. -//! println!("An array as lower hex: {:x}", a.as_hex()); -//! // And for vecs since `Vec` derefs to byte slice. -//! println!("A vector as upper hex: {:X}", v.as_hex()); -//! -//! // Allocate a new string (also `to_upper_hex_string`). -//! let s = v.to_lower_hex_string(); -//! -//! // Please note, mixed case strings will still parse successfully but we only -//! // support displaying hex in a single case. -//! assert_eq!( -//! Vec::from_hex("dEaDbEeF").expect("valid mixed case hex digits"), -//! Vec::from_hex("deadbeef").expect("valid hex digits"), -//! ); -//! # } -//! ``` +//! In an effort to release stable 1.0 crates that are forward compatible we are striving +//! relentlessly to release the bare minimum amount of code. This 1.0 version currently holds +//! only the parsing logic and error types. #![cfg_attr(all(not(test), not(feature = "std")), no_std)] // Experimental features we need. @@ -52,18 +28,14 @@ pub mod _export { } } -pub mod buf_encoder; -pub mod display; -pub mod error; +mod error; mod iter; pub mod parse; -#[cfg(feature = "serde")] -pub mod serde; /// Re-exports of the common crate traits. pub mod prelude { #[doc(inline)] - pub use crate::{display::DisplayHex, parse::FromHex}; + pub use crate::parse::FromHex; } pub(crate) use table::Table; @@ -71,7 +43,6 @@ pub(crate) use table::Table; #[rustfmt::skip] // Keep public re-exports separate. #[doc(inline)] pub use self::{ - display::DisplayHex, error::{ HexToArrayError, HexToBytesError, InvalidCharError, InvalidLengthError, OddLengthStringError, ToArrayError, ToBytesError, @@ -138,19 +109,6 @@ mod table { let right = self.0[usize::from(byte & 0x0F)]; [char::from(left), char::from(right)] } - - /// Writes the single byte as two ASCII chars in the provided buffer, and returns a `&str` - /// to that buffer. - /// - /// The function guarantees only returning values from the provided table. - #[inline] - pub(crate) fn byte_to_str<'a>(&self, dest: &'a mut [u8; 2], byte: u8) -> &'a str { - dest[0] = self.0[usize::from(byte >> 4)]; - dest[1] = self.0[usize::from(byte & 0x0F)]; - // SAFETY: Table inner array contains only valid ascii - let hex_str = unsafe { core::str::from_utf8_unchecked(dest) }; - hex_str - } } } diff --git a/src/parse.rs b/src/parse.rs index 2ef6807..13f874e 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -131,18 +131,4 @@ mod tests { InvalidLengthError { invalid: 16, expected: 8 }.into() ) } - - #[test] - #[cfg(feature = "alloc")] - fn mixed_case() { - use crate::display::DisplayHex as _; - - let s = "DEADbeef0123"; - let want_lower = "deadbeef0123"; - let want_upper = "DEADBEEF0123"; - - let v = Vec::::from_hex(s).expect("valid hex"); - assert_eq!(format!("{:x}", v.as_hex()), want_lower); - assert_eq!(format!("{:X}", v.as_hex()), want_upper); - } } diff --git a/src/serde.rs b/src/serde.rs deleted file mode 100644 index 8cd2f9c..0000000 --- a/src/serde.rs +++ /dev/null @@ -1,147 +0,0 @@ -//! Hex encoding with `serde`. -//! -//! The functions in this module de/serialize as hex _only_ when the serializer is human readable. -//! -//! # Examples -//! -//! ``` -//! # #[cfg(feature = "std")] { -//! use hex_conservative as hex; -//! use serde::{Serialize, Deserialize}; -//! -//! #[derive(Debug, Serialize, Deserialize)] -//! struct Foo { -//! #[serde(with = "hex::serde")] -//! bar: Vec, -//! } -//! # } -//! ``` - -use core::fmt; -use core::marker::PhantomData; - -use serde::de::{Error, Visitor}; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; - -use crate::prelude::*; - -/// Serializes `data` as a hex string using lowercase characters. -/// -/// We only serialize as hex if the serializer is human readable, if not we call through to the -/// `Serialize` implementation for `data`. -pub fn serialize(data: T, s: S) -> Result -where - S: Serializer, - T: Serialize + DisplayHex, -{ - serialize_lower(data, s) -} - -/// Serializes `data` as a hex string using lowercase characters. -/// -/// We only serialize as hex if the serializer is human readable, if not we call through to the -/// `Serialize` implementation for `data`. -pub fn serialize_lower(data: T, serializer: S) -> Result -where - S: Serializer, - T: Serialize + DisplayHex, -{ - // Don't do anything special when not human readable. - if !serializer.is_human_readable() { - serde::Serialize::serialize(&data, serializer) - } else { - serializer.collect_str(&format_args!("{:x}", data.as_hex())) - } -} - -/// Serializes `data` as hex string using uppercase characters. -/// -/// We only serialize as hex if the serializer is human readable, if not we call through to the -/// `Serialize` implementation for `data`. -pub fn serialize_upper(data: T, serializer: S) -> Result -where - S: Serializer, - T: Serialize + DisplayHex, -{ - // Don't do anything special when not human readable. - if !serializer.is_human_readable() { - serde::Serialize::serialize(&data, serializer) - } else { - serializer.collect_str(&format_args!("{:X}", data.as_hex())) - } -} - -/// Byte slice wrapper to serialize as a hex string in lowercase characters. -#[derive(Debug)] -pub struct SerializeBytesAsHex<'a>(pub &'a [u8]); - -impl serde::Serialize for SerializeBytesAsHex<'_> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serialize_lower(self.0, serializer) - } -} - -/// Byte slice wrapper to serialize as a hex string in lowercase characters. -#[derive(Debug)] -pub struct SerializeBytesAsHexLower<'a>(pub &'a [u8]); - -impl serde::Serialize for SerializeBytesAsHexLower<'_> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serialize_lower(self.0, serializer) - } -} - -/// Byte slice wrapper to serialize as a hex string in uppercase characters. -#[derive(Debug)] -pub struct SerializeBytesAsHexUpper<'a>(pub &'a [u8]); - -impl serde::Serialize for SerializeBytesAsHexUpper<'_> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serialize_upper(self.0, serializer) - } -} - -/// Deserializes a hex string into raw bytes. -/// -/// Allows upper, lower, and mixed case characters (e.g. `a5b3c1`, `A5B3C1` and `A5b3C1`). -/// -/// We only deserialize from hex if the serializer is human readable, if not we call through to the -/// `Deserialize` implementation for `T`. -pub fn deserialize<'de, D, T>(d: D) -> Result -where - D: Deserializer<'de>, - T: Deserialize<'de> + FromHex, -{ - struct HexVisitor(PhantomData); - - impl Visitor<'_> for HexVisitor - where - T: FromHex, - { - type Value = T; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("an ASCII hex string") - } - - fn visit_str(self, data: &str) -> Result { - FromHex::from_hex(data).map_err(Error::custom) - } - } - - // Don't do anything special when not human readable. - if !d.is_human_readable() { - serde::Deserialize::deserialize(d) - } else { - d.deserialize_map(HexVisitor(PhantomData)) - } -} diff --git a/tests/api.rs b/tests/api.rs index 2ed4622..e1388d0 100644 --- a/tests/api.rs +++ b/tests/api.rs @@ -13,11 +13,9 @@ use core::borrow::Borrow; use core::marker::PhantomData; use core::{fmt, slice}; -#[cfg(feature = "serde")] -use hex_conservative::serde; // These imports test "typical" usage by user code. use hex_conservative::{ - buf_encoder, display, BytesToHexIter, Case, DisplayHex as _, HexToArrayError, HexToBytesError, + BytesToHexIter, Case, HexToArrayError, HexToBytesError, InvalidCharError, InvalidLengthError, OddLengthStringError, ToArrayError, ToBytesError, }; @@ -40,44 +38,20 @@ const CAP: usize = 16; // BYTES.len() * 2 /// A struct that includes all public non-error structs. #[derive(Debug)] // All public types implement Debug (C-DEBUG). -struct Structs<'a, I, T> +struct Structs where I: Iterator, I::Item: Borrow, { a: BytesToHexIter, - b: buf_encoder::BufEncoder, - c: display::DisplayArray<'a, CAP>, - d: display::DisplayByteSlice<'a>, - #[cfg(feature = "std")] - e: display::HexWriter, - #[cfg(feature = "serde")] - f: serde::SerializeBytesAsHex<'a>, - #[cfg(feature = "serde")] - g: serde::SerializeBytesAsHexLower<'a>, - #[cfg(feature = "serde")] - h: serde::SerializeBytesAsHexUpper<'a>, - _i: PhantomData, // For when `std` is not enabled. } -impl Structs<'_, slice::Iter<'_, u8>, String> { +impl Structs> { /// Constructs an arbitrary instance. fn new() -> Self { let iter = BYTES.iter(); Self { a: BytesToHexIter::new(iter, Case::Lower), - b: buf_encoder::BufEncoder::new(Case::Lower), - c: BYTES.as_hex(), - d: BYTES[..].as_hex(), - #[cfg(feature = "std")] - e: display::HexWriter::new(String::new(), Case::Lower), - #[cfg(feature = "serde")] - f: serde::SerializeBytesAsHex(&BYTES), - #[cfg(feature = "serde")] - g: serde::SerializeBytesAsHexLower(&BYTES), - #[cfg(feature = "serde")] - h: serde::SerializeBytesAsHexUpper(&BYTES), - _i: PhantomData, } } } @@ -105,24 +79,6 @@ fn api_all_non_error_types_have_non_empty_debug() { let debug = format!("{:?}", t.a); assert!(!debug.is_empty()); - let debug = format!("{:?}", t.b); - assert!(!debug.is_empty()); - let debug = format!("{:?}", t.c); - assert!(!debug.is_empty()); - let debug = format!("{:?}", t.d); - assert!(!debug.is_empty()); - #[cfg(feature = "std")] - let debug = format!("{:?}", t.e); - assert!(!debug.is_empty()); - #[cfg(feature = "serde")] - { - let debug = format!("{:?}", t.f); - assert!(!debug.is_empty()); - let debug = format!("{:?}", t.g); - assert!(!debug.is_empty()); - let debug = format!("{:?}", t.h); - assert!(!debug.is_empty()); - } } #[test] @@ -133,8 +89,8 @@ fn all_types_implement_send_sync() { // Types are `Send` and `Sync` where possible (C-SEND-SYNC). assert_send::(); assert_sync::(); - assert_send::, String>>(); - assert_sync::, String>>(); + assert_send::>>(); + assert_sync::>>(); // Error types should implement the Send and Sync traits (C-GOOD-ERR). assert_send::();