diff --git a/CHANGELOG b/CHANGELOG index 9f7599a..034724b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,8 +1,17 @@ -## v7.1.0 +### v9.0.0 +- Expose more friendly APIs, `Hexify` and `DeHexify` traits. +- Un-public some tiny functions to encourage using `Hexify` and `DeHexify` traits. +- Improve docs. +- Restructure the code. + +### v8.0.0 +- Improve performance. + +### v7.1.0 - Rename `se_hex` to `ser_hex`. - Rename `se_hex_without_prefix` to `ser_hex_without_prefix`. -## v7.0.0 +### v7.0.0 - Improve docs. - Improve tests. - Bump dependencies. @@ -10,105 +19,105 @@ - Rename `de_hex2num` to `de_try_from_hex` and make it support more types. - Remove `de_hex2bytes`. -## v6.2.3 +### v6.2.3 - Add `slice2array_ref` and `slice2array_ref_unchecked`. - Bump dependencies. -## v6.2.2 +### v6.2.2 - Improve documentation. -## v6.2.1 +### v6.2.1 - Add `prefix_with` and `suffix_with`. - Bump dependencies. -## v6.2.0 +### v6.2.0 - Adjust generics order. - Bump dependencies. -## v6.1.0 +### v6.1.0 - Improve expression. - Improve `TryFromHex` and add `Hex`. -## v6.0.0 +### v6.0.0 - Optimize algorithm. - Bump dependencies. -## v5.1.0 +### v5.1.0 - Rename error fields. -## v5.0.0 +### v5.0.0 - Optimize algorithm. - Improve documentation. - Support `AsRef` input. - Add `hex2slice` and `hex2slice_unchecked`. -## v4.2.0 +### v4.2.0 - Bump dependencies. - Update CI. - Update license. -## v4.1.0 +### v4.1.0 - Mark `hex_bytes2hex_str_unchecked` as unsafe. -## v4.0.0 +### v4.0.0 - Use `is_hex_ascii` to optimize performance. - Add benchmark results. - Add `hex_bytes2hex_str` and `hex_bytes2hex_str_unchecked`. - Add fuzzing. -## v3.0.0 +### v3.0.0 - Break `hex_into` into `hex_into` and `hex_n_into`. - Break `hex_into_unchecked` into `hex_into_unchecked` and `hex_n_into_unchecked`. -## v2.0.2 +### v2.0.2 - Bump dependencies. - Update README. - Update CI. -## v2.0.1 +### v2.0.1 - Fix tests. -## v2.0.0 +### v2.0.0 - Split `dyn_*` to `slice_*` and `vec_*`. - Remove all the unsafe usage. -## v1.6.0 +### v1.6.0 - Disable generic input. (people should know what are they going to do) - Bump dependencies. -## v1.5.2 +### v1.5.2 - Update documentation. - Update code format. - Bump dependencies. -## v1.5.1 +### v1.5.1 - Update description. -## v1.5.0 +### v1.5.0 - Revert *"Use `String` instead `&str` in `serde`"*. - Bump dependencies. - Rust edition 2021. -## v1.4.1 +### v1.4.1 - Use `String` instead `&str` in `serde`. -## v1.4.0 +### v1.4.0 - Bump `serde`. - Add more documentation. - Add more tests. - Rename `hexd2num` to `de_hex2num`, `hexd2bytes` to `de_hex2bytes`. -## v1.3.3 +### v1.3.3 - Allow explicit generic argument. -## v1.3.2 +### v1.3.2 - Add `dyn_into`. -## v1.3.0 +### v1.3.0 - Add `hex2array`, `hex_try_into` and `hex_into_unchecked`. - Support `serde`. -## v1.2.0 +### v1.2.0 - Deprecated macro `hex2array_unchecked`. - Introduce function `hex2array_unchecked`. - Require at least Rust `1.51.0`. diff --git a/Cargo.lock b/Cargo.lock index 62efd60..49847ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,7 +25,7 @@ checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "array-bytes" -version = "8.0.0" +version = "9.0.0" dependencies = [ "const-hex", "criterion", @@ -427,9 +427,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -545,18 +545,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", @@ -565,9 +565,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" dependencies = [ "itoa", "memchr", @@ -589,9 +589,9 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "syn" -version = "2.0.90" +version = "2.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "70ae51629bf965c5c098cc9e87908a3df5301051a9e087d6f9bef5c9771ed126" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 12b0947..58c63eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,13 @@ [package] authors = ["Xavier Lau "] categories = [ + "algorithms", "encoding", + "parsing", "no-std", + "wasm", ] -description = "A collection of array/bytes/hex utilities." +description = "A collection of Array/Bytes/Hex utilities with full No-STD compatibility." edition = "2021" homepage = "https://hack.ink/array-bytes" keywords = [ @@ -18,7 +21,7 @@ license = "Apache-2.0/GPL-3.0" name = "array-bytes" readme = "README.md" repository = "https://github.com/hack-ink/array-bytes" -version = "8.0.0" +version = "9.0.0" [profile.ci-dev] incremental = false @@ -32,7 +35,7 @@ smallvec = { version = "1.13" } const-hex = { version = "1.14" } criterion = { version = "0.5" } faster-hex = { version = "0.10" } -hex = { version = "0.4" } +hex_crate = { package = "hex", version = "0.4" } rustc-hex = { version = "2.1" } serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0" } diff --git a/README.md b/README.md index f6fbe9d..60822c9 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@
# array-bytes -### A Collection of Array/Bytes/Hex Utilities. +### A collection of Array/Bytes/Hex utilities with full No-STD compatibility. [![License GPLv3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) [![License MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) @@ -10,128 +10,117 @@ [![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/hack-ink/array-bytes)](https://github.com/hack-ink/array-bytes/tags) [![GitHub code lines](https://tokei.rs/b1/github/hack-ink/array-bytes)](https://github.com/hack-ink/array-bytes) [![GitHub last commit](https://img.shields.io/github/last-commit/hack-ink/array-bytes?color=red&style=plastic)](https://github.com/hack-ink/array-bytes) -
-## Abilities -#### `TryFromHex` trait -- Convert hex to num - - type `AsRef<[u8]> -> isize` - - type `AsRef<[u8]> -> i8` - - type `AsRef<[u8]> -> i16` - - type `AsRef<[u8]> -> i32` - - type `AsRef<[u8]> -> i64` - - type `AsRef<[u8]> -> i128` - - type `AsRef<[u8]> -> usize` - - type `AsRef<[u8]> -> u8` - - type `AsRef<[u8]> -> u16` - - type `AsRef<[u8]> -> u32` - - type `AsRef<[u8]> -> u64` - - type `AsRef<[u8]> -> u128` -- Convert hex to array - - type `AsRef<[u8]> -> [u8; N]` - - type `AsRef<[u8]> -> SmallVec<[u8; 64]>` - - type `AsRef<[u8]> -> Vec` - -#### `Hex` trait -- Convert num to hex - - type `isize -> String` - - type `i8 -> String` - - type `i16 -> String` - - type `i32 -> String` - - type `i64 -> String` - - type `i128 -> String` - - type `usize -> String` - - type `u8 -> String` - - type `u16 -> String` - - type `u32 -> String` - - type `u64 -> String` - - type `u128 -> String` -- Convert array to hex - - type `[u8; N] -> String` - - type `&[u8; N] -> String` - - type `&[u8] -> String` - - type `Vec -> String` - - type `&Vec -> String` - -#### `slice` prefixed functions -- Build fixed length `Array` from `Slice` - - type `&[T] -> [T; N]` - - type `&[T] -> &[T; N]` -- Transform `Slice` to `G` - - type `&[T] -> G` - - e.g. `&[0_u8, ...] -> [u8; 20] -> H160` -#### `prefix` and `suffix` functions -- Prefixes/suffixes the given element to the given slice to make it a fixed-size array of length `N`. +## Usage +Here are a few quick examples of the most commonly used operations: hexifying and dehexifying. -#### `bytes` prefixed functions -- Convert bytes to hex - - type `AsRef<[u8]> -> String` +However, this crate also offers many other utilities for Array/Bytes/Hex, each with comprehensive documentation and examples. Check them out on [docs.rs](https://docs.rs/array-bytes)! -#### `hex` prefixed functions -- Convert `HexBytes` to hex - - type `&[u8] -> &str` - - e.g. `b"0x..." -> "0x..."` -- Transform hex from `Array` - - type `&str -> [u8; N]` -- Convert hex to bytes - - type `AsRef<[u8]> -> SmallVec<[u8; 64]>` -- Convert hex to `Slice` - - type `AsRef<[u8]> -> &[u8]` -- Transform hex to `T` - - type `AsRef<[u8]> -> T` - - e.g. `"0x..." -> [u8; 20] -> H160` - -#### `vec` prefixed functions -- Build fixed length `Array` from `Vec` - - type `Vec -> [T; N]` -- Transform `Vec` to `G` - - type `Vec -> G` - - e.g. `vec![0_u8, ...] -> [u8; 20] -> H160` +```rs +use array_bytes::{Hexify, Dehexify}; +use smallvec::SmallVec; + +// Hexify. +// Unsigned. +assert_eq!(52_u8.hexify(), "34"); +assert_eq!(520_u16.hexify_upper(), "208"); +assert_eq!(5_201_314_u32.hexify_prefixed(), "0x4f5da2"); +assert_eq!(5_201_314_u64.hexify_prefixed_upper(), "0x4F5DA2"); +assert_eq!(5_201_314_u128.hexify(), "4f5da2"); +assert_eq!(5_201_314_usize.hexify_upper(), "4F5DA2"); +// `[u8; N]`. +assert_eq!(*b"Love Jane Forever".hexify(), String::from("4c6f7665204a616e6520466f7265766572")); +// `&[u8; N]`. +assert_eq!( + b"Love Jane Forever".hexify_upper(), + String::from("4C6F7665204A616E6520466F7265766572") +); +// `&[u8]`. +assert_eq!( + b"Love Jane Forever".as_slice().hexify_prefixed(), + String::from("0x4c6f7665204a616e6520466f7265766572") +); +// `Vec`. +assert_eq!( + b"Love Jane Forever".to_vec().hexify_prefixed_upper(), + String::from("0x4C6F7665204A616E6520466F7265766572") +); +// `&Vec`. +assert_eq!( + (&b"Love Jane Forever".to_vec()).hexify(), + String::from("4c6f7665204a616e6520466f7265766572") +); +// Dehexify. +// Unsigned. +assert_eq!(u8::dehexify("34"), Ok(52)); +assert_eq!(u16::dehexify("208"), Ok(520)); +assert_eq!(u32::dehexify("0x4f5da2"), Ok(5_201_314)); +assert_eq!(u64::dehexify("0x4F5DA2"), Ok(5_201_314)); +assert_eq!(u128::dehexify("4f5da2"), Ok(5_201_314)); +assert_eq!(usize::dehexify("4F5DA2"), Ok(5_201_314)); +// Array. +assert_eq!( + <[u8; 17]>::dehexify("0x4c6f7665204a616e6520466f7265766572"), + Ok(*b"Love Jane Forever") +); +// SmallVec. +assert_eq!( + SmallVec::dehexify("0x4c6f7665204a616e6520466f7265766572").unwrap().into_vec(), + b"Love Jane Forever".to_vec() +); +assert_eq!(SmallVec::dehexify("我爱你"), Err(Error::InvalidLength)); +assert_eq!(SmallVec::dehexify("0x我爱你"), Err(Error::InvalidLength)); +// Vec. +assert_eq!( + >::dehexify("0x4c6f7665204a616e6520466f7265766572"), + Ok(b"Love Jane Forever".to_vec()) +); +assert_eq!( + >::dehexify("我爱你 "), + Err(Error::InvalidCharacter { character: 'æ', index: 0 }) +); +assert_eq!( + >::dehexify(" 我爱你"), + Err(Error::InvalidCharacter { character: ' ', index: 0 }) +); +``` -#### Serde support (require feature `serde`) -- `#[serde(deserialize_with = "array_bytes::hex_deserialize_n_into")]` - - type `S -> T` - - e.g. `"0x..." -> H160` -- `#[serde(deserialize_with = "array_bytes::de_try_from_hex")]` - - type `S -> impl TryFromHex` - - e.g. `"0xA" -> 10_u32` -- `#[serde(serialize_with = "array_bytes::ser_hex/array_bytes::ser_hex_without_prefix")]` - - type `S -> impl Hex` - - e.g. `"0x00" -> vec![0_u8]` +## Benchmark +The following benchmarks were run on a `Apple M4 Max 64GB - macOS 15.2 (24C101)`. -## Benchmark results -
Wed, Dec 18th, 2024
+
Sun, Dec 29th, 2024
```rs -array_bytes::bytes2hex time: [11.175 µs 11.198 µs 11.219 µs] -const_hex::encode time: [1.2195 µs 1.2381 µs 1.2564 µs] -faster_hex::hex_string time: [12.058 µs 12.089 µs 12.123 µs] -faster_hex::hex_encode_fallback - time: [12.055 µs 12.095 µs 12.135 µs] -hex::encode time: [73.787 µs 75.290 µs 76.798 µs] -rustc_hex::to_hex time: [43.948 µs 44.517 µs 45.504 µs] ---- -array_bytes::hex2bytes time: [19.294 µs 19.383 µs 19.500 µs] -array_bytes::hex2bytes_unchecked - time: [19.507 µs 19.666 µs 19.850 µs] -array_bytes::hex2slice time: [23.608 µs 24.087 µs 24.598 µs] -array_bytes::hex2slice_unchecked - time: [21.853 µs 22.428 µs 23.048 µs] -const_hex::decode time: [13.999 µs 14.018 µs 14.037 µs] -faster_hex::hex_decode time: [28.983 µs 29.028 µs 29.075 µs] -faster_hex::hex_decode_unchecked - time: [11.908 µs 11.926 µs 11.947 µs] -faster_hex::hex_decode_fallback - time: [11.909 µs 11.924 µs 11.940 µs] -hex::decode time: [96.566 µs 99.398 µs 102.23 µs] -hex::decode_to_slice time: [41.424 µs 42.312 µs 43.448 µs] +// Hexify. +array_bytes::Hexify::hexify time: [11.195 µs 11.227 µs 11.264 µs] +const_hex::encode time: [1.0546 µs 1.0823 µs 1.1099 µs] +faster_hex::hex_string time: [12.054 µs 12.103 µs 12.154 µs] +faster_hex::hex_encode_fallback time: [12.170 µs 12.209 µs 12.245 µs] +hex::encode time: [87.014 µs 87.164 µs 87.312 µs] +rustc_hex::to_hex time: [45.022 µs 45.616 µs 46.304 µs] +// Dehexify. +array_bytes::Dehexify::dehexify time: [19.601 µs 19.815 µs 20.061 µs] +array_bytes::dehexify_slice_mut time: [20.455 µs 20.471 µs 20.489 µs] +const_hex::decode time: [14.098 µs 14.118 µs 14.137 µs] +faster_hex::hex_decode time: [29.356 µs 29.395 µs 29.435 µs] +faster_hex::hex_decode_unchecked time: [12.089 µs 12.134 µs 12.208 µs] +faster_hex::hex_decode_fallback time: [12.067 µs 12.082 µs 12.098 µs] +hex::decode time: [97.005 µs 98.854 µs 100.65 µs] +hex::decode_to_slice time: [39.262 µs 40.562 µs 42.064 µs] +rustc_hex::from_hex time: [108.91 µs 110.77 µs 112.53 µs] +``` + +To run the benchmarks yourself: +```sh +git clone https://github.com/hack-ink/array-bytes +cd array-bytes +cargo bench ```
-#### License +## License Licensed under either of Apache-2.0 or GPL-3.0 at your option. -
diff --git a/audit-report.md b/audit-report.md index f37a7c1..c9da015 100644 --- a/audit-report.md +++ b/audit-report.md @@ -1,226 +1,289 @@ -# Audit Report Ver.1 for Rust Crate: `array_bytes` - -## Table of Contents -1. [Introduction](#introduction) -2. [Code Quality](#code-quality) -3. [Security Analysis](#security-analysis) -4. [Performance Considerations](#performance-considerations) -5. [Testing and Coverage](#testing-and-coverage) -6. [Dependency Management](#dependency-management) -7. [Documentation](#documentation) -8. [Conclusions and Recommendations](#conclusions-and-recommendations) +# Audit Report for `array-bytes` Crate + +**Crate Name:** `array-bytes` +**Version:** `9.0.0` +**Repository:** [https://github.com/hack-ink/array-bytes](https://github.com/hack-ink/array-bytes) +**License:** Apache-2.0/GPL-3.0 +**Authors:** Xavier Lau +**Published Date:** 2024-12-29 +**Rust Edition:** 2021 +**Categories:** decoding, encoding, no-std +**Keywords:** array, hex, no-std, slice, vec --- -## Introduction - -This audit report evaluates the Rust crate `array_bytes`, which provides a collection of array, bytes, and hex utilities optimized for blockchain development, particularly targeting the Substrate framework. The crate emphasizes `no_std` compatibility, ensuring suitability for constrained environments typical in blockchain applications. - -The assessment covers code quality, security vulnerabilities, performance optimizations, testing adequacy, dependency management, and documentation completeness. +## Table of Contents +- [Audit Report for `array-bytes` Crate](#audit-report-for-array-bytes-crate) + - [Table of Contents](#table-of-contents) + - [Overview](#overview) + - [Dependency Review](#dependency-review) + - [Direct Dependencies](#direct-dependencies) + - [Dev Dependencies](#dev-dependencies) + - [Benchmark Configuration](#benchmark-configuration) + - [Code Quality](#code-quality) + - [Safety](#safety) + - [Performance](#performance) + - [Best Practices](#best-practices) + - [Documentation](#documentation) + - [README](#readme) + - [Testing](#testing) + - [Unit Tests](#unit-tests) + - [Benchmarks](#benchmarks) + - [Fuzzing](#fuzzing) + - [Security Considerations](#security-considerations) + - [License Compliance](#license-compliance) + - [Recommendations](#recommendations) + - [Conclusion](#conclusion) --- -## Code Quality - -### **Linting and Compiler Directives** -- **Clippy Lints**: The crate enforces strict linting by denying all Clippy lints (`clippy::all`), missing documentation (`missing_docs`), and unused crate dependencies (`unused_crate_dependencies`). However, it allows specific lints like `clippy::tabs_in_doc_comments` and `clippy::uninit_vec`. This balance ensures high code quality while accommodating necessary exceptions. - -- **`no_std` Compliance**: By specifying `#![no_std]`, the crate avoids dependencies on the Rust standard library, enhancing its suitability for embedded and blockchain environments where resources are limited. - -### **Modular Structure** -- The crate is well-organized with clear module separation: - - Core functionality resides in `lib.rs`. - - Tests are encapsulated within the `test` module, conditionally compiled. - - Fuzz testing is handled in `fuzz.rs`, ensuring robustness against unexpected inputs. +## Overview -### **Use of Macros** -- **Macro Utilization**: Macros like `impl_num_try_from_hex!` and `impl_num_hex!` reduce code duplication by implementing traits for multiple numeric types succinctly. +`array-bytes` is a Rust crate providing a collection of utilities for handling arrays, bytes, and hexadecimal encoding/decoding. It is optimized for blockchain development, particularly with the Polkadot-SDK, and operates in a `no-std` environment. -- **Inline Helper Functions**: Functions such as `strip_0x`, `hex_ascii2digit`, `hex2byte`, and `pad_array` are marked `#[inline(always)]`, hinting to the compiler to always inline these small, frequently called functions for performance gains. +## Dependency Review -### **Error Handling** -- **Custom Error Enum**: The `Error` enum comprehensively covers various error scenarios, including invalid lengths, characters, mismatched lengths, UTF-8 errors, and integer parsing errors. This explicit error handling facilitates easier debugging and more precise error reporting. +### Direct Dependencies -### **Type Definitions and Traits** -- **Type Aliases**: The `Result` alias simplifies result handling across the crate. +- **serde (optional):** + - **Version:** `"1.0"` + - **Usage:** Conditional serialization/deserialization support. + - **Review:** Properly marked as optional with `default-features = false`. Ensure that consumers enable the `serde` feature when needed. -- **Traits for Conversion**: The crate defines `TryFromHex` and `Hex` traits, providing flexible and type-safe methods for hex conversions applicable to a wide range of types, including primitive integers, arrays, and vectors. +- **smallvec:** + - **Version:** `"1.13"` + - **Usage:** Provides `SmallVec` for optimized storage. + - **Review:** Well-maintained and widely used. No immediate concerns. -### **Const Generics** -- The use of const generics (e.g., `impl Hex for [u8; N]`) enhances the crate's flexibility, allowing compile-time determination of array sizes and improving type safety. +### Dev Dependencies -### **Safety Considerations** -- **Unsafe Code**: The crate judiciously uses `unsafe` blocks when manipulating raw pointers and setting lengths for `SmallVec`. Each unsafe operation is accompanied by comments justifying its safety, adhering to Rust's safety guarantees. - -- **Unchecked Functions**: Functions like `hex2byte_unchecked` and various `_unchecked` variants assume that inputs are valid, offering performance benefits at the cost of safety. These functions should be used with caution, ensuring that inputs are pre-validated. - ---- +- **const-hex, criterion, faster-hex, hex_crate (`hex`), rustc-hex, serde_json:** + - **Usage:** Used for testing, benchmarking, and fuzzing. + - **Review:** Ensure that none of these are unintentionally exposed or required for end-users. -## Security Analysis +### Benchmark Configuration -### **Input Validation** -- **Hex Parsing**: The crate meticulously validates hex strings, checking for even lengths and ensuring all characters are valid hex digits. Errors are returned for invalid inputs, preventing potential vulnerabilities like buffer overflows or unexpected behavior. +- **Bench Harness:** + - **Settings:** `harness = false` + - **Review:** Disables the default benchmarking harness. Ensure custom benchmarks are adequately implemented. -### **Use of `unsafe` Code** -- While the crate employs `unsafe` code for performance, each usage is well-documented and justified: - - **Memory Safety**: Operations involving raw pointers and manual length settings are enclosed within `unsafe` blocks with accompanying comments to ensure they uphold Rust's safety guarantees. - - - **Unchecked Variants**: Functions that bypass validation (`*_unchecked`) can introduce vulnerabilities if misused. It is imperative that these functions are only invoked with guaranteed valid inputs. - -### **Error Propagation** -- The crate consistently propagates errors using the `Result` type, enabling upstream handling of unexpected or malicious inputs without causing panics or undefined behavior. - -### **Dependency Management** -- **Minimal Dependencies**: The crate primarily relies on `core`, `alloc`, and conditionally on `serde` and `smallvec`. This minimal dependency footprint reduces the attack surface. +## Code Quality -- **Dependency Features**: Features like `serde` are gated, ensuring that only necessary dependencies are included based on feature flags. +### Safety -### **Serde Integration** -- **Deserialization and Serialization**: Custom deserializers and serializers handle hex conversions, ensuring that data is correctly parsed and formatted. However, care must be taken to prevent deserialization of malformed hex strings that could lead to inconsistent internal states. +- **Unsafe Code Usage:** + - **Files Involved:** `src/hex/dehexify.rs`, `src/hex/hexify.rs` + - **Details:** + - **Setting Length Unsafely:** + ```rust + unsafe { + bytes.set_len(cap); + } + ``` + - **Risk:** Potential undefined behavior if not correctly managed. + - **Mitigation:** Reviewed the logic to ensure that the capacity is correctly set and that all indices are within bounds before assignment. ---- + - **Converting Bytes to Strings Unsafely:** + ```rust + unsafe { String::from_utf8_unchecked(hex_bytes.into_vec()) } + ``` + - **Risk:** If `hex_bytes` contains non-UTF-8 data, this leads to undefined behavior. + - **Mitigation:** Prior validation ensures that only valid hex characters are present, making the unchecked conversion safe. -## Performance Considerations +- **Error Handling:** + - Proper error handling is implemented using the `Error` enum, ensuring that all potential issues are gracefully managed. -### **Optimized Operations** -- **`no_std` and `alloc`**: By avoiding the standard library and utilizing `alloc`, the crate is optimized for low-resource environments without sacrificing functionality. +### Performance -- **Use of `SmallVec`**: Leveraging `SmallVec` allows for stack allocation of small buffers, reducing heap allocations and improving cache locality for frequent operations. +- **Optimized Hex Encoding/Decoding:** + - Utilizes lookup tables (`HEX2DIGIT`) and pre-allocated buffers (`SmallVec`) to enhance performance. -### **Inlining** -- Functions marked with `#[inline(always)]` are prime candidates for inlining, reducing function call overhead and potentially enhancing performance, especially in tight loops or critical paths. +- **Bit Manipulation:** + - Efficient bitwise operations are used for hex conversions, minimizing computational overhead. -### **Efficient Memory Manipulation** -- **Pointer Arithmetic**: Direct manipulation of pointers for hex encoding and decoding operations minimizes overhead compared to higher-level abstractions. +- **Inlining Critical Functions:** + - Functions like `dehexify_array`, `dehexify_bytes`, and `strip_0x` are marked with `#[inline(always)]` to suggest inlining for performance-critical paths. -- **Pre-allocation**: Functions like `bytes2hex` pre-allocate memory based on expected sizes, preventing repeated reallocations and improving throughput. +### Best Practices -### **Macro-Generated Code** -- By generating trait implementations for multiple types via macros, the crate ensures that each implementation is optimized individually, avoiding unnecessary generic abstractions that could hinder performance. +- **No-Std Compatibility:** + - The crate is compatible with `no-std`, making it suitable for embedded and blockchain environments. ---- +- **Clippy Lints:** + - Uses `#![deny(clippy::all, missing_docs, unused_crate_dependencies)]` to enforce code quality. + - Allows specific clippy lints where necessary, e.g., `items_after_test_module`, `tabs_in_doc_comments`. -## Testing and Coverage +- **Modular Structure:** + - Organized into modules (`hex`, `op`, `serde`), promoting maintainability and clarity. -### **Unit Tests** -- **Comprehensive Coverage**: The `test.rs` file includes extensive unit tests covering: - - Trait implementations (`TryFromHex`, `Hex`) for various types. - - Array and slice conversions. - - Hex encoding and decoding functions. - - Edge cases such as invalid inputs and boundary conditions. +- **Generics and Traits:** + - Utilizes Rust's generics and traits effectively for flexibility and type safety. -- **Macro-Driven Tests**: Macros like `assert_try_from_hex_array!` and `assert_hex_array!` streamline testing across multiple array sizes, ensuring consistency and reducing boilerplate. +## Documentation -### **Fuzz Testing** -- **Fuzz.rs**: The `fuzz.rs` file employs fuzz testing using `libfuzzer_sys`, targeting critical functions like `try_from_hex` and hex conversion utilities. This approach helps uncover unexpected behaviors and potential vulnerabilities by subjecting the functions to a wide range of random inputs. +- **Comprehensive Documentation:** + - All public functions, traits, and types are documented with clear explanations and examples. + +- **Doc Tests:** + - Extensive use of `#[test]` and doc tests to ensure examples work as intended. + +- **README:** + - Detailed README provides an overview, usage examples, benchmark results, and licensing information. It includes badges for licenses, CI checks, documentation, and repository statistics, enhancing visibility and credibility. + +### README + +- **Structure and Content:** + - **Header:** Center-aligned title with a brief description. + - **Badges:** Multiple badges indicating licenses, CI status, documentation, version tags, code lines, and last commit date. + - **Usage Section:** Provides quick examples of hexifying and dehexifying operations with code snippets. + - **Benchmark Section:** Displays benchmark results with performance metrics. + - **License Section:** Clearly states the dual licensing under Apache-2.0 and GPL-3.0. + +- **Strengths:** + - **Visual Appeal:** Badges and structured layout make the README visually appealing and informative. + - **Practical Examples:** Usage examples help users quickly understand how to integrate the crate. + - **Benchmark Results:** Including benchmark results provides transparency regarding performance. + - **Ease of Access:** Links to documentation and repository make navigation straightforward. + +- **Areas for Improvement:** + - **Contribution Guidelines:** Including a section on how to contribute can encourage community involvement. + - **Advanced Usage:** Adding more advanced examples or use cases can help users leverage the crate's full potential. + - **Installation Instructions:** While cloning the repository is mentioned, providing `cargo` commands for adding as a dependency can be beneficial. -### **Serialization Tests** -- **Serde Integration**: Tests verify the correctness of serialization and deserialization processes, ensuring that data round-trips accurately between hex strings and internal representations. +## Testing -### **Performance Benchmarks** -- **Criterion Integration**: Although not detailed in the provided snippets, the inclusion of `criterion` suggests that performance benchmarks are in place, allowing for quantitative assessment of function execution times and facilitating performance regressions detection. +### Unit Tests -### **Edge Case Handling** -- The tests cover various edge cases, including: - - Empty inputs. - - Inputs with prefixes like `0x`. - - Inputs with invalid characters or lengths. - - Large data inputs to test scalability. +- **Coverage:** + - Each module contains unit tests covering various scenarios, including edge cases. ---- +- **Test Quality:** + - Tests are thorough and cover both typical usage and potential edge cases, ensuring reliability. -## Dependency Management +### Benchmarks -### **Crate Dependencies** -- **Core Dependencies**: - - `core`: Fundamental Rust library for no_std environments. - - `alloc`: Provides memory allocation facilities necessary for dynamic data structures in no_std contexts. +- **Benchmarking Setup:** + - **File:** `bench.rs` + - **Description:** Benchmarks the performance of encoding and decoding functions against other crates like `const-hex`, `faster_hex`, `hex`, and `rustc_hex`. + - **Source Attribution:** + ```rust + //! The origin benchmark comes from [rust-hex](https://github.com/KokaKiwi/rust-hex/blob/main/benches/hex.rs). + //! Thanks for their previous works. + ``` + - **Review:** Properly credits the source of the benchmarking methodology. -- **Optional Dependencies**: - - `serde`: Conditional feature enabling serialization and deserialization functionalities. - - `smallvec`: Utilized for optimized small vector operations, reducing heap allocations. +- **Benchmark Analysis:** + - **Encoding Benchmarks:** + - Compares `array_bytes::Hexify` with `const_hex`, `faster_hex`, and `hex` crates. + - Measures the time taken to encode a predefined data set. -### **Dependency Features** -- **Feature Flags**: The crate leverages feature flags to conditionally include dependencies like `serde`, ensuring that only necessary dependencies are included based on user requirements. This strategy minimizes the crate's footprint and reduces compilation times for projects that do not require serialization capabilities. + - **Decoding Benchmarks:** + - Compares `array_bytes::Dehexify` and `array_bytes::dehexify_slice_mut` with `const_hex`, `faster_hex`, `hex`, and `rustc_hex` crates. + - Evaluates the performance of decoding operations, including unchecked variants. -### **Unused Dependencies** -- The crate enforces the denial of `unused_crate_dependencies`, ensuring that all included dependencies are actively utilized. This practice helps maintain a clean dependency graph, avoiding bloat and potential security risks from unused packages. + - **Performance Insights:** + - The benchmarks provide valuable insights into the performance characteristics of `array-bytes` relative to other established crates. + - Identifies areas where `array-bytes` excels or may require optimization. -### **Versioning and Compatibility** -- **Version Constraints**: While specific versions are not detailed in the snippets, it is essential to manage dependency versions carefully to avoid incompatibilities and ensure compatibility with no_std environments. +- **Benchmark Results:** + - **Hexify:** + - `array_bytes::Hexify::hexify`: ~11.2 µs + - `const_hex::encode`: ~1.05 µs + - `faster_hex::hex_string`: ~12.1 µs + - `faster_hex::hex_encode_fallback`: ~12.2 µs + - `hex::encode`: ~87 µs + - `rustc_hex::to_hex`: ~45 µs + - **Dehexify:** + - `array_bytes::Dehexify::dehexify`: ~19.6 µs + - `array_bytes::dehexify_slice_mut`: ~20.5 µs + - `const_hex::decode`: ~14.1 µs + - `faster_hex::hex_decode`: ~29.4 µs + - `faster_hex::hex_decode_unchecked`: ~12.1 µs + - `faster_hex::hex_decode_fallback`: ~12.1 µs + - `hex::decode`: ~97 µs + - `hex::decode_to_slice`: ~39.3 µs + - `rustc_hex::from_hex`: ~109 µs -- **Minimal Dependencies**: By keeping dependencies minimal and leveraging widely-used crates like `serde` and `smallvec`, the crate maintains a balance between functionality and simplicity. + - **Analysis:** + - `const_hex` outperforms `array-bytes` in encoding and decoding. + - `array-bytes` shows competitive performance compared to `faster_hex` and significantly outperforms `hex` and `rustc_hex`. + - There is room for optimization, especially in encoding performance. ---- +### Fuzzing -## Documentation +- **Fuzzing Setup:** + - **File:** `fuzz.rs` + - **Description:** Implements fuzz testing to ensure robustness against malformed or unexpected input data. -### **Inline Documentation** -- **Module-Level Docs**: The `lib.rs` file contains module-level documentation (`//!` comments) that provides an overview of the crate's purpose and functionalities, specifically highlighting its optimization for blockchain development and Substrate. +- **Fuzzing Strategy:** + - Utilizes the `libfuzzer_sys` crate to define fuzz targets. + - Tests the `Dehexify` trait implementations for various unsigned integer types (`usize`, `u8`, `u16`, `u32`, `u64`, `u128`). + - Additionally tests the `hexify` function and `dehexify_slice_mut` function with arbitrary byte slices. -- **Item-Level Docs**: Public types, traits, functions, and enums are documented with `///` comments, offering clear explanations and usage examples. For instance: - - The `TryFromHex` and `Hex` traits include detailed documentation and code examples demonstrating their usage. - - Functions like `slice2array`, `prefix_with`, `hex2bytes`, etc., are well-documented with descriptions, examples, and explanations of their behavior. +- **Coverage and Effectiveness:** + - Fuzzing enhances the crate's reliability by uncovering potential edge cases and vulnerabilities that unit tests might miss. + - Ensures that the crate gracefully handles a wide range of input scenarios without panicking or causing undefined behavior. -### **Examples** -- **Code Examples**: Each public interface includes `# Examples` sections with code snippets that illustrate typical usage scenarios. This approach aids developers in understanding how to integrate the crate's functionalities into their projects. +## Security Considerations -### **Error Handling Documentation** -- **Error Enum**: The `Error` enum variants are documented with explanations of what each error represents, enhancing clarity for users handling potential failures. +- **Input Validation:** + - Hex decoding functions validate input lengths and characters, preventing invalid data from causing undefined behavior. -### **Conditional Documentation** -- **Serde Features**: Documentation for serde-related functionalities is conditionally included based on the `serde` feature flag (`#[cfg(feature = "serde")]`). This strategy ensures that documentation remains relevant and avoids confusion for users who may not enable serde support. +- **Error Reporting:** + - Detailed error variants (`InvalidLength`, `InvalidCharacter`, etc.) aid in precise error handling and debugging. -### **Testing Documentation** -- **Test Cases as Documentation**: The `test.rs` file includes extensive test cases that serve as practical examples of how to use the crate's functionalities. These tests effectively double as additional documentation, showcasing real-world usage patterns and edge case handling. +- **Avoiding Buffer Overflows:** + - Safe indexing and boundary checks prevent buffer overflows during encoding and decoding. -### **Missing Documentation** -- **Internal Helper Functions**: While public interfaces are thoroughly documented, internal helper functions like `hex_ascii2digit` and `hex2byte` lack documentation. Although marked as `#[inline(always)]` and seemingly simple, documenting these can aid in maintenance and future audits. +- **Unsafe Code Audits:** + - Reviewed all unsafe code blocks to ensure they uphold Rust's safety guarantees. No apparent vulnerabilities detected. ---- +- **Fuzz Testing:** + - The inclusion of fuzzing tests significantly strengthens security by ensuring that the crate can handle unexpected or malicious inputs without compromising stability or safety. -## Conclusions and Recommendations +## License Compliance -### **Strengths** -- **Comprehensive Functionality**: The crate offers a wide range of utilities for hex and byte conversions, catering to various data types and use cases within blockchain development. +- **Dual Licensing:** + - The crate is dual-licensed under Apache-2.0 and GPL-3.0. -- **Performance Optimizations**: Strategic use of `unsafe` code, macros, and efficient memory manipulation ensures high performance, critical for blockchain applications. +- **Dependency Licenses:** + - Ensure that all dependencies are compatible with these licenses. -- **Robust Testing**: Extensive unit tests and fuzz testing provide confidence in the crate's reliability and resilience against malformed inputs. +- **License Documentation:** + - The `LICENSE` files should be present and correctly referenced in the repository. -- **Minimal and Conditional Dependencies**: By limiting dependencies and using feature flags, the crate maintains a lean and adaptable codebase suitable for constrained environments. +## Recommendations -- **Thorough Documentation**: Clear and detailed documentation facilitates ease of use and integration, enhancing developer experience. +1. **Benchmark Analysis Reporting:** + - Include benchmark results in the repository or documentation to provide users with performance expectations. -### **Areas for Improvement** -- **Documentation for Internal Components**: Providing documentation for internal helper functions can aid in code maintenance and future audits, ensuring comprehensive coverage. +2. **Feature Documentation:** + - Clearly document optional features (e.g., `serde`) and how to enable them. -- **Safety Guarantees for Unchecked Functions**: While unchecked functions offer performance benefits, reinforcing guidelines and best practices for their safe usage can prevent potential misuse. +3. **Continuous Integration:** + - Ensure that CI pipelines run clippy, tests, benchmarks, and fuzzing to maintain code quality and performance standards. -- **Consistent Error Handling in Serde**: Enhancing error messages in serde deserializers to provide more context can improve debugging and user experience when encountering deserialization issues. +4. **Example Usage:** + - Provide more example usages in the README to help users understand how to integrate the crate. -- **Benchmarking Results**: Including benchmark results within the documentation or as part of the test suite can offer insights into performance characteristics and help identify areas for further optimization. +5. **Safety Comments:** + - Add comments around unsafe blocks explaining why they are safe, aiding future maintainers. -### **Recommendations** -1. **Enhance Documentation**: - - Document internal helper functions to provide a complete understanding of the crate's mechanics. - - Include guidelines on when to use checked vs. unchecked functions to prevent inadvertent misuse. +6. **Version Pinning:** + - Consider pinning dependencies more precisely to avoid unexpected breakages from dependency updates. -2. **Improve Error Messaging**: - - In serde deserializers, provide more descriptive error messages that include the offending input or specific failure reasons. +7. **Error Messages:** + - Enhance error messages to provide more context where applicable, especially in deserialization functions. -3. **Expand Test Coverage**: - - Incorporate property-based testing to ensure functions behave correctly across a broader range of inputs. - - Add benchmarks to assess performance and track improvements over time. +8. **Fuzzing Expansion:** + - Expand fuzz targets to cover more functions and edge cases, ensuring even greater robustness. -4. **Security Best Practices**: - - Review and audit all `unsafe` code segments periodically to ensure ongoing compliance with Rust's safety guarantees. - - Consider implementing additional validation or safeguards for functions that bypass standard checks. +9. **Benchmark Optimization:** + - Based on benchmark results, identify and optimize any bottlenecks to further enhance performance. -5. **Continuous Integration (CI)**: - - Integrate CI pipelines that automatically run tests, lints, and fuzzing on code changes to maintain code quality and reliability. +10. **Documentation of Benchmarks and Fuzzing:** + - Document the benchmarking and fuzzing strategies and results to provide transparency and build user trust. -6. **Documentation Examples**: - - Expand code examples to cover more complex or real-world scenarios, demonstrating the crate's capabilities in diverse contexts. +## Conclusion -By addressing these areas, the `array_bytes` crate can further solidify its position as a reliable and efficient utility library for blockchain development in Rust. +The `array-bytes` crate is well-structured, with a focus on performance and safety, making it suitable for blockchain and embedded development. It adheres to Rust best practices and includes thorough documentation, testing, benchmarking, and fuzzing. Addressing the recommendations can further enhance its reliability, performance, and usability. diff --git a/benches/bench.rs b/benches/bench.rs index 16d3645..3ee2a06 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -2,13 +2,15 @@ //! Thanks for their previous works. // crates.io +use array_bytes::{Dehexify, Hexify}; use criterion::Criterion; +use hex_crate as hex; use rustc_hex::{FromHex, ToHex}; const DATA: &[u8] = include_bytes!("../LICENSE-GPL3"); fn bench_encode(c: &mut Criterion) { - c.bench_function("array_bytes::bytes2hex", |b| b.iter(|| array_bytes::bytes2hex("", DATA))); + c.bench_function("array_bytes::Hexify::hexify", |b| b.iter(|| DATA.hexify())); c.bench_function("const_hex::encode", |b| b.iter(|| const_hex::encode(DATA))); @@ -30,75 +32,41 @@ fn bench_encode(c: &mut Criterion) { } fn bench_decode(c: &mut Criterion) { - c.bench_function("array_bytes::hex2bytes", |b| { - let hex = array_bytes::bytes2hex("", DATA); + let hex = DATA.hexify(); - b.iter(|| array_bytes::hex2bytes(&hex).unwrap()) + c.bench_function("array_bytes::Dehexify::dehexify", |b| { + b.iter(|| >::dehexify(&hex).unwrap()) }); - c.bench_function("array_bytes::hex2bytes_unchecked", |b| { - let hex = array_bytes::bytes2hex("", DATA); - - b.iter(|| array_bytes::hex2bytes_unchecked(&hex)) - }); - c.bench_function("array_bytes::hex2slice", |b| { - let hex = array_bytes::bytes2hex("", DATA); - - b.iter(|| { - let mut v = vec![0; DATA.len()]; - - array_bytes::hex2slice(&hex, &mut v).unwrap(); - - v - }) - }); - c.bench_function("array_bytes::hex2slice_unchecked", |b| { - let hex = array_bytes::bytes2hex("", DATA); - + c.bench_function("array_bytes::dehexify_slice_mut", |b| { b.iter(|| { let mut v = vec![0; DATA.len()]; - array_bytes::hex2slice_unchecked(&hex, &mut v); + array_bytes::dehexify_slice_mut(&hex, &mut v).unwrap(); v }) }); - c.bench_function("const_hex::decode", |b| { - let hex = const_hex::encode(DATA); + c.bench_function("const_hex::decode", |b| b.iter(|| const_hex::decode(&hex).unwrap())); - b.iter(|| const_hex::decode(&hex).unwrap()) - }); + c.bench_function("faster_hex::hex_decode", |b| { + let mut v = vec![0; DATA.len()]; - c.bench_function("faster_hex::hex_decode", move |b| { - let hex = faster_hex::hex_string(DATA); - let len = DATA.len(); - let mut dst = vec![0; len]; - - b.iter(|| faster_hex::hex_decode(hex.as_bytes(), &mut dst).unwrap()) + b.iter(|| faster_hex::hex_decode(hex.as_bytes(), &mut v).unwrap()) }); c.bench_function("faster_hex::hex_decode_unchecked", |b| { - let hex = faster_hex::hex_string(DATA); - let len = DATA.len(); - let mut dst = vec![0; len]; + let mut dst = vec![0; DATA.len()]; b.iter(|| faster_hex::hex_decode_unchecked(hex.as_bytes(), &mut dst)) }); c.bench_function("faster_hex::hex_decode_fallback", |b| { - let hex = faster_hex::hex_string(DATA); - let len = DATA.len(); - let mut dst = vec![0; len]; + let mut dst = vec![0; DATA.len()]; b.iter(|| faster_hex::hex_decode_fallback(hex.as_bytes(), &mut dst)) }); - c.bench_function("hex::decode", |b| { - let hex = hex::encode(DATA); - - b.iter(|| hex::decode(&hex).unwrap()) - }); + c.bench_function("hex::decode", |b| b.iter(|| hex::decode(&hex).unwrap())); c.bench_function("hex::decode_to_slice", |b| { - let hex = array_bytes::bytes2hex("", DATA); - b.iter(|| { let mut v = vec![0; DATA.len()]; @@ -108,11 +76,7 @@ fn bench_decode(c: &mut Criterion) { }) }); - c.bench_function("rustc_hex::from_hex", |b| { - let hex = DATA.to_hex::(); - - b.iter(|| hex.from_hex::>().unwrap()) - }); + c.bench_function("rustc_hex::from_hex", |b| b.iter(|| hex.from_hex::>().unwrap())); } criterion::criterion_group!(benches, bench_encode, bench_decode); diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index d5baed0..15e7faf 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -10,14 +10,14 @@ checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" [[package]] name = "array-bytes" -version = "8.0.0" +version = "9.0.0" dependencies = [ "smallvec", ] [[package]] name = "array-bytes-fuzz" -version = "6.1.0" +version = "9.0.0" dependencies = [ "array-bytes", "libfuzzer-sys", diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index e5523df..6cb8e80 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Xavier Lau "] edition = "2021" name = "array-bytes-fuzz" publish = false -version = "6.1.0" +version = "9.0.0" [package.metadata] cargo-fuzz = true @@ -12,7 +12,7 @@ cargo-fuzz = true all-features = true [dependencies] -array-bytes = { version = "8.0", path = ".." } +array-bytes = { version = "9.0", path = ".." } libfuzzer-sys = { version = "0.4" } [workspace] @@ -20,6 +20,6 @@ members = ["."] [[bin]] doc = false -name = "bytes-hex-conversion" -path = "fuzz_targets/bytes_hex_conversion.rs" +name = "hexify-dehexify" +path = "fuzz_targets/hexify_dehexify.rs" test = false diff --git a/fuzz/fuzz_targets/bytes_hex_conversion.rs b/fuzz/fuzz_targets/bytes_hex_conversion.rs deleted file mode 100644 index 649e7c2..0000000 --- a/fuzz/fuzz_targets/bytes_hex_conversion.rs +++ /dev/null @@ -1,39 +0,0 @@ -#![no_main] - -macro_rules! fuzz_try_from_hex { - ($data:ident,$($t:ident,)+) => { - $({ - // array-bytes - use array_bytes::TryFromHex; - - let _ = $t::try_from_hex($data); - })+ - }; -} - -libfuzzer_sys::fuzz_target!(|data: &[u8]| { - fuzz_try_from_hex! { - data, - isize, - i8, - i16, - i32, - i64, - i128, - usize, - u8, - u16, - u32, - u64, - u128, - } - - let _ = array_bytes::bytes2hex("", data); - let _ = array_bytes::hex_bytes2hex_str(data); - let _ = array_bytes::hex2bytes(data); - - { - let mut v = vec![0; data.len() / 2]; - let _ = array_bytes::hex2slice(data, &mut v); - } -}); diff --git a/fuzz/fuzz_targets/hexify_dehexify.rs b/fuzz/fuzz_targets/hexify_dehexify.rs new file mode 100644 index 0000000..f119c58 --- /dev/null +++ b/fuzz/fuzz_targets/hexify_dehexify.rs @@ -0,0 +1,32 @@ +#![no_main] + +// self +use array_bytes::{Dehexify, Hexify}; + +macro_rules! fuzz_dehexify { + ($data:ident,$($t:ident,)+) => { + $({ + let _ = $t::dehexify($data); + })+ + }; +} + +libfuzzer_sys::fuzz_target!(|data: &[u8]| { + fuzz_dehexify! { + data, + usize, + u8, + u16, + u32, + u64, + u128, + } + + let _ = data.hexify(); + let _ = >::dehexify(data); + + { + let mut v = vec![0; data.len() / 2]; + let _ = array_bytes::dehexify_slice_mut(data, &mut v); + } +}); diff --git a/src/hex.rs b/src/hex.rs new file mode 100644 index 0000000..57c2a62 --- /dev/null +++ b/src/hex.rs @@ -0,0 +1,51 @@ +mod hexify; +pub use hexify::*; + +mod dehexify; +pub use dehexify::*; + +// self +#[cfg(test)] use crate::prelude::*; + +#[test] +fn random_input_should_work() { + let data = [ + include_bytes!("../Cargo.lock").as_slice(), + include_bytes!("../LICENSE-APACHE2"), + include_bytes!("../LICENSE-GPL3"), + ] + .concat(); + + [8, 16, 32, 64, 128, 256, 512, 1024].into_iter().for_each(|chunks_size| { + let mut data_pieces = Vec::new(); + + data.chunks(chunks_size).enumerate().for_each(|(i, chunk)| { + let data_piece = match i % 4 { + 0 => chunk.hexify(), + 1 => chunk.hexify_upper(), + 2 => chunk.hexify_prefixed(), + 3 => chunk.hexify_prefixed_upper(), + _ => unreachable!(), + }; + + data_pieces.push(data_piece); + }); + + let data_pieces = data_pieces + .into_iter() + .map(|piece| match strip_0x(piece.as_bytes()).len() { + 8 => <[u8; 4]>::dehexify(&piece).unwrap().to_vec(), + 32 => <[u8; 16]>::dehexify(&piece).unwrap().to_vec(), + 64 => <[u8; 32]>::dehexify(&piece).unwrap().to_vec(), + 128 => <[u8; 64]>::dehexify(&piece).unwrap().to_vec(), + 256 => <[u8; 128]>::dehexify(&piece).unwrap().to_vec(), + 512 => <[u8; 256]>::dehexify(&piece).unwrap().to_vec(), + 1024 => <[u8; 512]>::dehexify(&piece).unwrap().to_vec(), + 2048 => <[u8; 1024]>::dehexify(&piece).unwrap().to_vec(), + _ => >::dehexify(&piece).unwrap(), + }) + .collect::>(); + + assert_eq!(data_pieces.concat(), data); + }); +} diff --git a/src/hex/dehexify.rs b/src/hex/dehexify.rs new file mode 100644 index 0000000..0c4f51a --- /dev/null +++ b/src/hex/dehexify.rs @@ -0,0 +1,391 @@ +// core +use core::str; +// self +use crate::prelude::*; + +static HEX2DIGIT: [Option; 256] = { + let mut table = [None; 256]; + let mut i = 0; + + while i <= 9 { + table[b'0' as usize + i] = Some(i as u8); + + i += 1; + } + + i = 0; + + while i <= 5 { + table[b'a' as usize + i] = Some((10 + i) as u8); + table[b'A' as usize + i] = Some((10 + i) as u8); + + i += 1; + } + + table +}; + +/// Dehexify the given hex to `Self`. +/// +/// # Examples +/// ``` +/// // Unsigned. +/// assert_eq!(u8::dehexify("34"), Ok(52)); +/// assert_eq!(u16::dehexify("208"), Ok(520)); +/// assert_eq!(u32::dehexify("0x4f5da2"), Ok(5_201_314)); +/// assert_eq!(u64::dehexify("0x4F5DA2"), Ok(5_201_314)); +/// assert_eq!(u128::dehexify("4f5da2"), Ok(5_201_314)); +/// assert_eq!(usize::dehexify("4F5DA2"), Ok(5_201_314)); +/// // Array. +/// assert_eq!( +/// <[u8; 17]>::dehexify("0x4c6f7665204a616e6520466f7265766572"), +/// Ok(*b"Love Jane Forever") +/// ); +/// // SmallVec. +/// assert_eq!( +/// SmallVec::dehexify("0x4c6f7665204a616e6520466f7265766572").unwrap().into_vec(), +/// b"Love Jane Forever".to_vec() +/// ); +/// assert_eq!(SmallVec::dehexify("我爱你"), Err(Error::InvalidLength)); +/// assert_eq!(SmallVec::dehexify("0x我爱你"), Err(Error::InvalidLength)); +/// // Vec. +/// assert_eq!( +/// >::dehexify("0x4c6f7665204a616e6520466f7265766572"), +/// Ok(b"Love Jane Forever".to_vec()) +/// ); +/// assert_eq!( +/// >::dehexify("我爱你 "), +/// Err(Error::InvalidCharacter { character: 'æ', index: 0 }) +/// ); +/// assert_eq!( +/// >::dehexify(" 我爱你"), +/// Err(Error::InvalidCharacter { character: ' ', index: 0 }) +/// ); +/// ``` +pub trait Dehexify +where + Self: Sized, +{ + /// Dehexify `Self` from hex. + fn dehexify(hex: H) -> Result + where + H: AsRef<[u8]>; +} +macro_rules! impl_dehexify_for_unsigned { + ($($t:ty,)+) => { + $(impl Dehexify for $t { + fn dehexify(hex: H) -> Result + where + H: AsRef<[u8]>, + { + let hex = strip_0x(hex.as_ref()); + let hex = str::from_utf8(hex).map_err(Error::Utf8Error)?; + + Self::from_str_radix(hex, 16).map_err(Error::ParseIntError) + } + })+ + }; +} +impl_dehexify_for_unsigned! { + usize, + u8, + u16, + u32, + u64, + u128, +} +impl Dehexify for [u8; N] { + fn dehexify(hex: H) -> Result + where + H: AsRef<[u8]>, + { + dehexify_array(hex) + } +} +impl Dehexify for SmallVec<[u8; 64]> { + fn dehexify(hex: H) -> Result + where + H: AsRef<[u8]>, + { + dehexify_bytes(hex) + } +} +impl Dehexify for Vec { + fn dehexify(hex: H) -> Result + where + H: AsRef<[u8]>, + { + dehexify_bytes(hex).map(|sv| sv.into_vec()) + } +} +#[test] +fn dehexify_should_work() { + // Unsigned. + assert_eq!(u8::dehexify("34"), Ok(52)); + assert_eq!(u16::dehexify("208"), Ok(520)); + assert_eq!(u32::dehexify("0x4f5da2"), Ok(5_201_314)); + assert_eq!(u64::dehexify("0x4F5DA2"), Ok(5_201_314)); + assert_eq!(u128::dehexify("4f5da2"), Ok(5_201_314)); + assert_eq!(usize::dehexify("4F5DA2"), Ok(5_201_314)); + // Array. + assert_eq!( + <[u8; 17]>::dehexify("0x4c6f7665204a616e6520466f7265766572"), + Ok(*b"Love Jane Forever") + ); + // SmallVec. + assert_eq!( + SmallVec::dehexify("0x4c6f7665204a616e6520466f7265766572").unwrap().into_vec(), + b"Love Jane Forever".to_vec() + ); + assert_eq!(SmallVec::dehexify("我爱你"), Err(Error::InvalidLength)); + assert_eq!(SmallVec::dehexify("0x我爱你"), Err(Error::InvalidLength)); + // Vec. + assert_eq!( + >::dehexify("0x4c6f7665204a616e6520466f7265766572"), + Ok(b"Love Jane Forever".to_vec()) + ); + assert_eq!( + >::dehexify("我爱你 "), + Err(Error::InvalidCharacter { character: 'æ', index: 0 }) + ); + assert_eq!( + >::dehexify(" 我爱你"), + Err(Error::InvalidCharacter { character: ' ', index: 0 }) + ); +} + +/// Dehexify hex into a mutable slice source. +/// +/// # Examples +/// ``` +/// let mut array = [0; 17]; +/// +/// assert_eq!( +/// array_bytes::dehexify_slice_mut("0x4c6f7665204a616e6520466f7265766572", &mut array), +/// Ok(b"Love Jane Forever".as_slice()) +/// ); +/// assert_eq!(array, *b"Love Jane Forever"); +/// ``` +pub fn dehexify_slice_mut(hex: H, slice_src: &mut [u8]) -> Result<&[u8]> +where + H: AsRef<[u8]>, +{ + let hex = strip_0x(hex.as_ref()); + + if hex.len() % 2 != 0 { + Err(Error::InvalidLength)?; + } + + let expected_len = hex.len() >> 1; + + if expected_len != slice_src.len() { + Err(Error::MismatchedLength { expect: expected_len })?; + } + + for (byte, i) in slice_src.iter_mut().zip((0..hex.len()).step_by(2)) { + *byte = dehexify_ascii((&hex[i], i), (&hex[i + 1], i + 1))?; + } + + Ok(slice_src) +} +#[test] +fn dehexify_slice_mut_should_work() { + let mut bytes = [0; 17]; + assert_eq!( + dehexify_slice_mut("0x4c6f7665204a616e6520466f7265766572", &mut bytes), + Ok(b"Love Jane Forever".as_slice()) + ); + assert_eq!(bytes, *b"Love Jane Forever"); + + let mut bytes = [0; 17]; + assert_eq!( + dehexify_slice_mut("0x4c6f7665204a616e6520466f7265766572".as_bytes(), &mut bytes), + Ok(b"Love Jane Forever".as_slice()) + ); + assert_eq!(bytes, *b"Love Jane Forever"); + + let mut bytes = [0; 17]; + assert_eq!( + dehexify_slice_mut("4c6f7665204a616e6520466f7265766572", &mut bytes), + Ok(b"Love Jane Forever".as_slice()) + ); + assert_eq!(bytes, *b"Love Jane Forever"); + + let mut bytes = [0; 17]; + assert_eq!( + dehexify_slice_mut("4c6f7665204a616e6520466f7265766572".as_bytes(), &mut bytes), + Ok(b"Love Jane Forever".as_slice()) + ); + assert_eq!(bytes, *b"Love Jane Forever"); + + assert_eq!(dehexify_slice_mut("0", &mut []), Err(Error::InvalidLength)); + assert_eq!(dehexify_slice_mut("0x0", &mut []), Err(Error::InvalidLength)); + + assert_eq!(dehexify_slice_mut("00", &mut []), Err(Error::MismatchedLength { expect: 1 })); + assert_eq!(dehexify_slice_mut("0x0001", &mut []), Err(Error::MismatchedLength { expect: 2 })); + + assert_eq!( + dehexify_slice_mut("fg", &mut [0]), + Err(Error::InvalidCharacter { character: 'g', index: 1 }) + ); + assert_eq!( + dehexify_slice_mut("0xyz", &mut [0]), + Err(Error::InvalidCharacter { character: 'y', index: 0 }) + ); +} + +/// Dehexify hex to a fixed length bytes vector then convert it to `T` where `T: From<[u8; N]>`. +/// +/// # Examples +/// ``` +/// #[derive(Debug, PartialEq)] +/// struct Ljf([u8; 17]); +/// impl From<[u8; 17]> for Ljf { +/// fn from(array: [u8; 17]) -> Self { +/// Self(array) +/// } +/// } +/// +/// assert_eq!( +/// array_bytes::dehexify_array_then_into::<_, Ljf, 17>("0x4c6f7665204a616e6520466f7265766572"), +/// Ok(Ljf(*b"Love Jane Forever")) +/// ); +/// ``` +pub fn dehexify_array_then_into(hex: H) -> Result +where + H: AsRef<[u8]>, + T: From<[u8; N]>, +{ + Ok(dehexify_array(hex)?.into()) +} +#[test] +fn dehexify_array_then_into_should_work() { + assert_eq!( + dehexify_array_then_into::<_, Ljfn, 17>("0x4c6f7665204a616e6520466f7265766572"), + Ok(Ljfn(*b"Love Jane Forever")) + ); + assert_eq!( + dehexify_array_then_into::<_, Ljfn, 17>("4c6f7665204a616e6520466f7265766572"), + Ok(Ljfn(*b"Love Jane Forever")) + ); + + assert_eq!( + dehexify_array_then_into::<_, Ljfn, 17>("0x4c6f7665204a616e6520466f7265766572".as_bytes()), + Ok(Ljfn(*b"Love Jane Forever")) + ); + assert_eq!( + dehexify_array_then_into::<_, Ljfn, 17>("4c6f7665204a616e6520466f7265766572".as_bytes()), + Ok(Ljfn(*b"Love Jane Forever")) + ); +} + +/// Dehexify hex to a bytes vector then convert it to `T` where `T: From`. +/// +/// # Examples +/// ``` +/// #[derive(Debug, PartialEq)] +/// struct Ljf(Vec); +/// impl From> for Ljf { +/// fn from(vec: Vec) -> Self { +/// Self(vec) +/// } +/// } +/// +/// assert_eq!( +/// array_bytes::dehexify_vec_then_into::<_, Ljf>("0x4c6f7665204a616e6520466f7265766572"), +/// Ok(Ljf(b"Love Jane Forever".to_vec())) +/// ); +/// ``` +pub fn dehexify_vec_then_into(hex: H) -> Result +where + H: AsRef<[u8]>, + T: From>, +{ + Ok(dehexify_bytes(hex.as_ref())?.into_vec().into()) +} +#[test] +fn dehexify_vec_then_into_should_work() { + assert_eq!( + dehexify_vec_then_into::<_, Ljf>("0x4c6f7665204a616e6520466f7265766572"), + Ok(Ljf(b"Love Jane Forever".to_vec())) + ); + assert_eq!( + dehexify_vec_then_into::<_, Ljf>("4c6f7665204a616e6520466f7265766572"), + Ok(Ljf(b"Love Jane Forever".to_vec())) + ); + + assert_eq!( + dehexify_vec_then_into::<_, Ljf>("0x4c6f7665204a616e6520466f7265766572".as_bytes()), + Ok(Ljf(b"Love Jane Forever".to_vec())) + ); + assert_eq!( + dehexify_vec_then_into::<_, Ljf>("4c6f7665204a616e6520466f7265766572".as_bytes()), + Ok(Ljf(b"Love Jane Forever".to_vec())) + ); +} + +#[inline(always)] +fn dehexify_array(hex: H) -> Result<[u8; N]> +where + H: AsRef<[u8]>, +{ + let bytes = dehexify_bytes(hex)?; + + op::slice2array(&bytes) +} + +#[inline(always)] +fn dehexify_bytes(hex: H) -> Result> +where + H: AsRef<[u8]>, +{ + let hex = strip_0x(hex.as_ref()); + + if hex.len() % 2 != 0 { + Err(Error::InvalidLength)?; + } + + let cap = hex.len() / 2; + let mut bytes = >::with_capacity(cap); + + // The capacity is fixed, it's safe to set the length; qed. + unsafe { + bytes.set_len(cap); + } + + let bytes_ptr = bytes.as_mut_ptr(); + + for i in 0..cap { + let high = HEX2DIGIT[hex[i * 2] as usize] + .ok_or(Error::InvalidCharacter { character: hex[i * 2] as char, index: i * 2 })?; + let low = HEX2DIGIT[hex[i * 2 + 1] as usize].ok_or(Error::InvalidCharacter { + character: hex[i * 2 + 1] as char, + index: i * 2 + 1, + })?; + + unsafe { + *bytes_ptr.add(i) = (high << 4) | low; + } + } + + Ok(bytes) +} + +#[inline(always)] +pub(super) fn strip_0x(hex: &[u8]) -> &[u8] { + if hex.len() >= 2 && hex[0] == b'0' && hex[1] == b'x' { + &hex[2..] + } else { + hex + } +} + +#[inline(always)] +fn dehexify_ascii((hex0, hex0_idx): (&u8, usize), (hex1, hex1_idx): (&u8, usize)) -> Result { + let ascii = HEX2DIGIT[*hex0 as usize] + .ok_or(Error::InvalidCharacter { character: *hex0 as _, index: hex0_idx })? + << 4 | HEX2DIGIT[*hex1 as usize] + .ok_or(Error::InvalidCharacter { character: *hex1 as _, index: hex1_idx })?; + + Ok(ascii) +} diff --git a/src/hex/hexify.rs b/src/hex/hexify.rs new file mode 100644 index 0000000..60cae0b --- /dev/null +++ b/src/hex/hexify.rs @@ -0,0 +1,402 @@ +// core +use core::{mem, str}; +// self +use crate::prelude::*; + +const HEX_CHARS: &[u8; 16] = b"0123456789abcdef"; +const HEX_CHARS_UPPER: &[u8; 16] = b"0123456789ABCDEF"; + +/// Hexify `Self`. +/// +/// # Examples +/// ``` +/// use array_bytes::Hexify; +/// +/// // Unsigned. +/// assert_eq!(52_u8.hexify(), "34"); +/// assert_eq!(520_u16.hexify_upper(), "208"); +/// assert_eq!(5_201_314_u32.hexify_prefixed(), "0x4f5da2"); +/// assert_eq!(5_201_314_u64.hexify_prefixed_upper(), "0x4F5DA2"); +/// assert_eq!(5_201_314_u128.hexify(), "4f5da2"); +/// assert_eq!(5_201_314_usize.hexify_upper(), "4F5DA2"); +/// // `[u8; N]`. +/// assert_eq!(*b"Love Jane Forever".hexify(), String::from("4c6f7665204a616e6520466f7265766572")); +/// // `&[u8; N]`. +/// assert_eq!( +/// b"Love Jane Forever".hexify_upper(), +/// String::from("4C6F7665204A616E6520466F7265766572") +/// ); +/// // `&[u8]`. +/// assert_eq!( +/// b"Love Jane Forever".as_slice().hexify_prefixed(), +/// String::from("0x4c6f7665204a616e6520466f7265766572") +/// ); +/// // `Vec`. +/// assert_eq!( +/// b"Love Jane Forever".to_vec().hexify_prefixed_upper(), +/// String::from("0x4C6F7665204A616E6520466F7265766572") +/// ); +/// // `&Vec`. +/// assert_eq!( +/// (&b"Love Jane Forever".to_vec()).hexify(), +/// String::from("4c6f7665204a616e6520466f7265766572") +/// ); +/// ``` +pub trait Hexify { + /// Hexify `Self`. + fn hexify(self) -> String; + + /// Hexify `Self` with uppercase. + fn hexify_upper(self) -> String; + + /// Hexify `Self` with `0x` prefix. + fn hexify_prefixed(self) -> String; + + /// Hexify `Self` with `0x` prefix and uppercase. + fn hexify_prefixed_upper(self) -> String; +} +macro_rules! hexify_unsigned { + ($self:expr, $map:expr) => {{ + match $self.highest_set_bit() { + None => "0".into(), + Some(high_bit) => { + let high_nibble = high_bit / 4; + let nibble_count = high_nibble + 1; + let mut hex = String::with_capacity(nibble_count as _); + + for nibble in (0..=high_nibble).rev() { + let shift = nibble * 4; + let digit = (($self >> shift) & 0xF) as usize; + + hex.push($map[digit] as _); + } + + hex + }, + } + }}; +} +macro_rules! hexify_unsigned_prefixed { + ($self:expr, $map:expr) => {{ + match $self.highest_set_bit() { + None => "0x0".into(), + Some(high_bit) => { + let high_nibble = high_bit / 4; + let nibble_count = high_nibble + 1; + let mut hex = String::with_capacity(2 + nibble_count as usize); + + hex.push_str("0x"); + + for nibble in (0..=high_nibble).rev() { + let shift = nibble * 4; + let digit = (($self >> shift) & 0xF) as usize; + + hex.push($map[digit] as _); + } + + hex + }, + } + }}; +} +macro_rules! impl_hexify_for_unsigned { + ($($t:ty,)+) => { + $( + impl Hexify for $t { + fn hexify(self) -> String { + hexify_unsigned!(self, HEX_CHARS) + } + + fn hexify_upper(self) -> String { + hexify_unsigned!(self, HEX_CHARS_UPPER) + } + + fn hexify_prefixed(self) -> String { + hexify_unsigned_prefixed!(self, HEX_CHARS) + } + + fn hexify_prefixed_upper(self) -> String { + hexify_unsigned_prefixed!(self, HEX_CHARS_UPPER) + } + } + + impl Hexify for &$t { + fn hexify(self) -> String { + (*self).hexify() + } + + fn hexify_upper(self) -> String { + (*self).hexify_upper() + } + + fn hexify_prefixed(self) -> String { + (*self).hexify_prefixed() + } + + fn hexify_prefixed_upper(self) -> String { + (*self).hexify_prefixed_upper() + } + } + )+ + }; +} +impl_hexify_for_unsigned! { + usize, + u8, + u16, + u32, + u64, + u128, +} +macro_rules! hexify { + ($self:expr, $map:expr) => {{ + let cap = $self.len() * 2; + let mut hex_bytes = >::with_capacity(cap); + + // The capacity is fixed, it's safe to set the length; qed. + unsafe { + hex_bytes.set_len(cap); + } + + let hex_ptr = hex_bytes.as_mut_ptr(); + + for (i, &byte) in $self.iter().enumerate() { + let high = $map[(byte >> 4) as usize]; + let low = $map[(byte & 0x0f) as usize]; + + unsafe { + *hex_ptr.add(i * 2) = high; + *hex_ptr.add(i * 2 + 1) = low; + } + } + + // All the bytes are looked up in the map, it's safe to convert to string; qed. + unsafe { String::from_utf8_unchecked(hex_bytes.into_vec()) } + }}; +} +macro_rules! hexify_prefixed { + ($self:expr, $map:expr) => {{ + let cap = 2 + $self.len() * 2; + let mut hex_bytes = >::with_capacity(cap); + + hex_bytes.extend_from_slice(b"0x"); + + // The capacity is fixed, it's safe to set the length; qed. + unsafe { + hex_bytes.set_len(cap); + } + + let hex_ptr = unsafe { hex_bytes.as_mut_ptr().add(2) }; + + for (i, &byte) in $self.iter().enumerate() { + let high = $map[(byte >> 4) as usize]; + let low = $map[(byte & 0x0f) as usize]; + + unsafe { + *hex_ptr.add(i * 2) = high; + *hex_ptr.add(i * 2 + 1) = low; + } + } + + // All the bytes are looked up in the map, it's safe to convert to string; qed. + unsafe { String::from_utf8_unchecked(hex_bytes.into_vec()) } + }}; +} +impl Hexify for [u8; N] { + fn hexify(self) -> String { + hexify!(self, HEX_CHARS) + } + + fn hexify_upper(self) -> String { + hexify!(self, HEX_CHARS_UPPER) + } + + fn hexify_prefixed(self) -> String { + hexify_prefixed!(self, HEX_CHARS) + } + + fn hexify_prefixed_upper(self) -> String { + hexify_prefixed!(self, HEX_CHARS_UPPER) + } +} +impl Hexify for &[u8; N] { + fn hexify(self) -> String { + hexify!(self, HEX_CHARS) + } + + fn hexify_upper(self) -> String { + hexify!(self, HEX_CHARS_UPPER) + } + + fn hexify_prefixed(self) -> String { + hexify_prefixed!(self, HEX_CHARS) + } + + fn hexify_prefixed_upper(self) -> String { + hexify_prefixed!(self, HEX_CHARS_UPPER) + } +} +impl Hexify for &[u8] { + fn hexify(self) -> String { + hexify!(self, HEX_CHARS) + } + + fn hexify_upper(self) -> String { + hexify!(self, HEX_CHARS_UPPER) + } + + fn hexify_prefixed(self) -> String { + hexify_prefixed!(self, HEX_CHARS) + } + + fn hexify_prefixed_upper(self) -> String { + hexify_prefixed!(self, HEX_CHARS_UPPER) + } +} +impl Hexify for Vec { + fn hexify(self) -> String { + hexify!(self, HEX_CHARS) + } + + fn hexify_upper(self) -> String { + hexify!(self, HEX_CHARS_UPPER) + } + + fn hexify_prefixed(self) -> String { + hexify_prefixed!(self, HEX_CHARS) + } + + fn hexify_prefixed_upper(self) -> String { + hexify_prefixed!(self, HEX_CHARS_UPPER) + } +} +impl Hexify for &Vec { + fn hexify(self) -> String { + hexify!(self, HEX_CHARS) + } + + fn hexify_upper(self) -> String { + hexify!(self, HEX_CHARS_UPPER) + } + + fn hexify_prefixed(self) -> String { + hexify_prefixed!(self, HEX_CHARS) + } + + fn hexify_prefixed_upper(self) -> String { + hexify_prefixed!(self, HEX_CHARS_UPPER) + } +} +#[test] +fn hexify_should_work() { + // Unsigned. + assert_eq!(52_u8.hexify(), "34"); + assert_eq!(520_u16.hexify_upper(), "208"); + assert_eq!(5_201_314_u32.hexify_prefixed(), "0x4f5da2"); + assert_eq!(5_201_314_u64.hexify_prefixed_upper(), "0x4F5DA2"); + assert_eq!(5_201_314_u128.hexify(), "4f5da2"); + assert_eq!(5_201_314_usize.hexify_upper(), "4F5DA2"); + // `[u8; N]`. + assert_eq!(*b"Love Jane Forever".hexify(), String::from("4c6f7665204a616e6520466f7265766572")); + // `&[u8; N]`. + assert_eq!( + b"Love Jane Forever".hexify_upper(), + String::from("4C6F7665204A616E6520466F7265766572") + ); + // `&[u8]`. + assert_eq!( + b"Love Jane Forever".as_slice().hexify_prefixed(), + String::from("0x4c6f7665204a616e6520466f7265766572") + ); + // `Vec`. + assert_eq!( + b"Love Jane Forever".to_vec().hexify_prefixed_upper(), + String::from("0x4C6F7665204A616E6520466F7265766572") + ); + // `&Vec`. + assert_eq!( + (&b"Love Jane Forever".to_vec()).hexify(), + String::from("4c6f7665204a616e6520466f7265766572") + ); +} + +trait HighestSetBit { + fn highest_set_bit(self) -> Option; +} +macro_rules! impl_highest_set_bit { + ($($t:ty),+ $(,)?) => { + $( + impl HighestSetBit for $t { + fn highest_set_bit(self) -> Option { + if self == 0 { + None + } else { + let n_bits = (mem::size_of::<$t>() as u32) * 8; + + Some(n_bits - 1 - self.leading_zeros()) + } + } + } + )+ + } +} +impl_highest_set_bit! { + u8, + u16, + u32, + u64, + u128, + usize +} +#[test] +fn highest_set_bit_should_work() { + assert_eq!(0_u8.highest_set_bit(), None); + assert_eq!(1_u16.highest_set_bit(), Some(0)); + assert_eq!(2_u32.highest_set_bit(), Some(1)); + assert_eq!(4_u64.highest_set_bit(), Some(2)); + assert_eq!(8_u128.highest_set_bit(), Some(3)); + assert_eq!(16_usize.highest_set_bit(), Some(4)); +} + +/// Hexify the bytes which are already in hex. +/// +/// This is useful when you are interacting with IO. +/// +/// # Examples +/// ``` +/// assert_eq!( +/// array_bytes::hexify_hex_bytes(b"4c6f7665204a616e6520466f7265766572"), +/// Ok("4c6f7665204a616e6520466f7265766572"), +/// ); +/// ``` +pub fn hexify_hex_bytes(bytes: &[u8]) -> Result<&str> { + for (i, byte) in bytes.iter().enumerate().skip(if bytes.starts_with(b"0x") { 2 } else { 0 }) { + if !byte.is_ascii_hexdigit() { + Err(Error::InvalidCharacter { character: *byte as _, index: i })?; + } + } + + Ok( + // Validated in previous step, never fails here; qed. + unsafe { str::from_utf8_unchecked(bytes) }, + ) +} +#[test] +fn hexify_hex_bytes_should_work() { + assert_eq!( + hexify_hex_bytes(b"4c6f7665204a616e6520466f7265766572"), + Ok("4c6f7665204a616e6520466f7265766572"), + ); + assert_eq!( + hexify_hex_bytes(b"4C6F7665204A616E6520466F7265766572"), + Ok("4C6F7665204A616E6520466F7265766572"), + ); + assert_eq!( + hexify_hex_bytes(b"0x4c6f7665204a616e6520466f7265766572"), + Ok("0x4c6f7665204a616e6520466f7265766572"), + ); + assert_eq!( + hexify_hex_bytes(b"0x4C6F7665204A616E6520466F7265766572"), + Ok("0x4C6F7665204A616E6520466F7265766572"), + ); +} diff --git a/src/lib.rs b/src/lib.rs index 0318bad..708ad12 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,222 +1,69 @@ #![deny(clippy::all, missing_docs, unused_crate_dependencies)] -#![allow(clippy::tabs_in_doc_comments, clippy::uninit_vec)] +#![allow(clippy::items_after_test_module, clippy::tabs_in_doc_comments)] #![no_std] -//! A collection of array/bytes/hex utilities. +//! A collection of Array/Bytes/Hex utilities with full No-STD compatibility. //! //! Completely optimized for blockchain development. -//! Especially the Substrate. +//! Especially the Polkadot-SDK. extern crate alloc; -#[cfg(test)] mod test; +mod hex; +pub use hex::*; -// core -use core::{cmp::Ordering, convert::TryInto, str}; -// alloc -use alloc::{format, string::String, vec::Vec}; -// crates.io -#[cfg(feature = "serde")] -use serde::{de::Error as DeError, Deserialize, Deserializer, Serializer}; -use smallvec::SmallVec; +mod op; +pub use op::*; -/// The main result of array-bytes. -pub type Result = core::result::Result; - -const HEX_CHARS: &[u8; 16] = b"0123456789abcdef"; - -static HEX_TO_DIGIT: [Option; 256] = { - let mut table = [None; 256]; - let mut i = 0; - - while i <= 9 { - table[b'0' as usize + i] = Some(i as u8); - - i += 1; - } - - i = 0; +#[cfg(feature = "serde")] mod serde; +#[cfg(feature = "serde")] pub use serde::*; - while i <= 5 { - table[b'a' as usize + i] = Some((10 + i) as u8); - table[b'A' as usize + i] = Some((10 + i) as u8); +mod prelude { + pub use alloc::{string::String, vec::Vec}; - i += 1; - } + pub use smallvec::SmallVec; - table -}; - -/// Try to convert the given hex to a specific type. -/// -/// # Examples -/// ``` -/// use array_bytes::TryFromHex; -/// -/// assert_eq!(u128::try_from_hex("0x5201314"), Ok(85_988_116)); -/// ``` -pub trait TryFromHex -where - Self: Sized, -{ - /// Try to convert [`Self`] from hex. - fn try_from_hex(hex: H) -> Result - where - H: AsRef<[u8]>; -} -macro_rules! impl_num_try_from_hex { - ($($t:ty,)+) => { - $(impl TryFromHex for $t { - fn try_from_hex(hex: H) -> Result - where - H: AsRef<[u8]>, - { - let hex = strip_0x(hex.as_ref()); - let hex = str::from_utf8(hex).map_err(Error::Utf8Error)?; + pub use crate::{Error, Result}; - Self::from_str_radix(hex, 16).map_err(Error::ParseIntError) - } - })+ - }; -} -impl_num_try_from_hex! { - isize, - i8, - i16, - i32, - i64, - i128, - usize, - u8, - u16, - u32, - u64, - u128, -} -impl TryFromHex for [u8; N] { - fn try_from_hex(hex: H) -> Result - where - H: AsRef<[u8]>, - { - let bytes = hex2bytes(hex)?; + pub(crate) use crate::op; - slice2array(&bytes) - } -} -impl TryFromHex for SmallVec<[u8; 64]> { - fn try_from_hex(hex: H) -> Result - where - H: AsRef<[u8]>, - { - hex2bytes(hex) - } -} -impl TryFromHex for Vec { - fn try_from_hex(hex: H) -> Result - where - H: AsRef<[u8]>, - { - hex2bytes(hex).map(|sv| sv.into_vec()) - } -} + #[cfg(test)] + mod test { + // Suppress `unused_crate_dependencies` error. + use const_hex as _; + use criterion as _; + use faster_hex as _; + use hex_crate as _; + use rustc_hex as _; + use serde as _; + use serde_json as _; -/// Convert the given type to hex. -/// -/// # Examples -/// ``` -/// use array_bytes::Hex; -/// -/// assert_eq!(5_201_314_u128.hex("0x"), "0x4f5da2"); -/// ``` -pub trait Hex { - /// Convert [`Self`] to hex with the given prefix. - fn hex

(self, prefix: P) -> String - where - P: AsRef; -} -macro_rules! impl_num_hex { - ($($t:ty,)+) => { - $( - impl Hex for $t { - fn hex

(self, prefix: P) -> String - where - P: AsRef - { - let prefix = prefix.as_ref(); + // self + use super::*; - format!("{prefix}{self:x}") - } + #[derive(Debug, PartialEq)] + pub struct Ljf(pub Vec); + impl From> for Ljf { + fn from(bytes: Vec) -> Self { + Self(bytes) } - impl Hex for &$t { - fn hex

(self, prefix: P) -> String - where - P: AsRef - { - let prefix = prefix.as_ref(); + } - format!("{prefix}{self:x}") - } + #[derive(Debug, PartialEq)] + pub struct Ljfn(pub [u8; 17]); + impl From<[u8; 17]> for Ljfn { + fn from(array: [u8; 17]) -> Self { + Self(array) } - )+ - }; -} -impl_num_hex! { - isize, - i8, - i16, - i32, - i64, - i128, - usize, - u8, - u16, - u32, - u64, - u128, -} -impl Hex for [u8; N] { - fn hex

(self, prefix: P) -> String - where - P: AsRef, - { - bytes2hex(prefix, self) + } } + #[cfg(test)] pub use test::*; } -impl Hex for &[u8; N] { - fn hex

(self, prefix: P) -> String - where - P: AsRef, - { - bytes2hex(prefix, self) - } -} -impl Hex for &[u8] { - fn hex

(self, prefix: P) -> String - where - P: AsRef, - { - bytes2hex(prefix, self) - } -} -impl Hex for Vec { - fn hex

(self, prefix: P) -> String - where - P: AsRef, - { - bytes2hex(prefix, self) - } -} -impl Hex for &Vec { - fn hex

(self, prefix: P) -> String - where - P: AsRef, - { - bytes2hex(prefix, self) - } -} +#[allow(missing_docs)] +pub type Result = core::result::Result; -/// The main error of array-bytes. +#[allow(missing_docs)] #[derive(Debug, PartialEq, Eq)] pub enum Error { /// The length must not be odd. @@ -238,846 +85,3 @@ pub enum Error { /// Failed to parse the hex number from hex string. ParseIntError(core::num::ParseIntError), } - -/// `&[T]` to `[T; N]`. -/// -/// # Examples -/// ``` -/// assert_eq!( -/// array_bytes::slice2array::<_, 8>(&[5, 2, 0, 1, 3, 1, 4, 0]), -/// Ok([5, 2, 0, 1, 3, 1, 4, 0]) -/// ); -/// ``` -pub fn slice2array(slice: &[T]) -> Result<[T; N]> -where - T: Copy, -{ - slice.try_into().map_err(|_| Error::MismatchedLength { expect: N }) -} - -/// Just like [`slice2array`] but without the checking. -/// -/// # Examples -/// ``` -/// assert_eq!( -/// array_bytes::slice2array_unchecked::<_, 8>(&[5, 2, 0, 1, 3, 1, 4, 0]), -/// [5, 2, 0, 1, 3, 1, 4, 0] -/// ); -/// ``` -pub fn slice2array_unchecked(slice: &[T]) -> [T; N] -where - T: Copy, -{ - slice2array(slice).unwrap() -} - -/// `&[T]` to `&[T; N]`. -/// -/// # Examples -/// ``` -/// assert_eq!( -/// array_bytes::slice2array_ref::<_, 8>(&[5, 2, 0, 1, 3, 1, 4, 0]), -/// Ok(&[5, 2, 0, 1, 3, 1, 4, 0]) -/// ); -/// ``` -pub fn slice2array_ref(slice: &[T]) -> Result<&[T; N]> -where - T: Copy, -{ - slice.try_into().map_err(|_| Error::MismatchedLength { expect: N }) -} - -/// Just like [`slice2array_ref`] but without the checking. -/// -/// # Examples -/// ``` -/// assert_eq!( -/// array_bytes::slice2array_ref_unchecked::<_, 8>(&[5, 2, 0, 1, 3, 1, 4, 0]), -/// &[5, 2, 0, 1, 3, 1, 4, 0] -/// ); -/// ``` -pub fn slice2array_ref_unchecked(slice: &[T]) -> &[T; N] -where - T: Copy, -{ - slice2array_ref(slice).unwrap() -} - -/// Prefixes the given element to the given array/slice/vector to make it a fixed-size array of -/// length `N`. -/// -/// If the length of the array/slice/vector is already equal to `N`, it returns the -/// array/slice/vector as a fixed-size array. -/// If the length of the array/slice/vector is greater than `N`, it returns the first `N` elements -/// of the array/slice/vector as a fixed-size array. -/// If the length of the array/slice/vector is less than `N`, it creates a new fixed-size array of -/// length `N` and copies the array/slice/vector into it, padding the remaining elements with the -/// given element. -/// -/// # Examples -/// ``` -/// assert_eq!(array_bytes::prefix_with::<_, _, 4>([5, 2, 0, 1], 0), [5, 2, 0, 1]); -/// assert_eq!(array_bytes::prefix_with::<_, _, 4>([5, 2, 0, 1, 3, 1, 4], 0), [5, 2, 0, 1]); -/// assert_eq!(array_bytes::prefix_with::<_, _, 5>([5, 2, 0], 0), [0, 0, 5, 2, 0]); -/// ``` -pub fn prefix_with(any: A, element: T) -> [T; N] -where - A: AsRef<[T]>, - T: Copy, -{ - pad_array(any, element, true) -} - -/// Suffixes the given element to the given array/slice/vector to make it a fixed-size array of -/// length `N`. -/// -/// If the length of the array/slice/vector is already equal to `N`, it returns the -/// array/slice/vector as a fixed-size array. -/// If the length of the array/slice/vector is greater than `N`, it returns the first `N` elements -/// of the array/slice/vector as a fixed-size array. -/// If the length of the array/slice/vector is less than `N`, it creates a new fixed-size array of -/// length `N` and copies the array/slice/vector into it, padding the remaining elements with the -/// given element. -/// -/// # Examples -/// ``` -/// assert_eq!(array_bytes::suffix_with::<_, _, 4>([5, 2, 0, 1], 0), [5, 2, 0, 1]); -/// assert_eq!(array_bytes::suffix_with::<_, _, 4>([5, 2, 0, 1, 3, 1, 4], 0), [5, 2, 0, 1]); -/// assert_eq!(array_bytes::suffix_with::<_, _, 5>([5, 2, 0], 0), [5, 2, 0, 0, 0]); -/// ``` -pub fn suffix_with(any: A, element: T) -> [T; N] -where - A: AsRef<[T]>, - T: Copy, -{ - pad_array(any, element, false) -} - -/// Convert `&[T]` to a type directly. -/// -/// # Examples -/// ``` -/// #[derive(Debug, PartialEq)] -/// struct Ljf([u8; 17]); -/// impl From<[u8; 17]> for Ljf { -/// fn from(array: [u8; 17]) -> Self { -/// Self(array) -/// } -/// } -/// -/// assert_eq!( -/// array_bytes::slice_n_into::(b"Love Jane Forever"), -/// Ok(Ljf(*b"Love Jane Forever")) -/// ); -/// ``` -pub fn slice_n_into(slice: &[T]) -> Result -where - T: Copy, - V: From<[T; N]>, -{ - Ok(slice2array(slice)?.into()) -} - -/// Just like [`slice_n_into`] but without the checking. -/// -/// # Examples -/// ``` -/// #[derive(Debug, PartialEq)] -/// struct Ljf([u8; 17]); -/// impl From<[u8; 17]> for Ljf { -/// fn from(array: [u8; 17]) -> Self { -/// Self(array) -/// } -/// } -/// -/// assert_eq!( -/// array_bytes::slice_n_into_unchecked::(b"Love Jane Forever"), -/// Ljf(*b"Love Jane Forever") -/// ); -/// ``` -pub fn slice_n_into_unchecked(slice: &[T]) -> V -where - T: Copy, - V: From<[T; N]>, -{ - slice2array_unchecked(slice).into() -} - -/// [`Vec`] to `[T; N]`. -/// -/// # Examples -/// ``` -/// assert_eq!(array_bytes::vec2array::<_, 8>(vec![0; 8]), Ok([0; 8])); -/// ``` -pub fn vec2array(vec: Vec) -> Result<[T; N]> { - vec.try_into().map_err(|_| Error::MismatchedLength { expect: N }) -} - -/// Just like [`vec2array`] but without the checking. -/// -/// # Examples -/// ``` -/// assert_eq!(array_bytes::vec2array_unchecked::<_, 8>(vec![0; 8]), [0; 8]); -/// ``` -pub fn vec2array_unchecked(vec: Vec) -> [T; N] { - vec2array(vec).unwrap() -} - -/// Convert [`Vec`] to a type directly. -/// -/// # Examples -/// -/// ``` -/// #[derive(Debug, PartialEq)] -/// struct Ljf([u8; 17]); -/// impl From<[u8; 17]> for Ljf { -/// fn from(array: [u8; 17]) -> Self { -/// Self(array) -/// } -/// } -/// -/// assert_eq!( -/// array_bytes::vec_n_into::(b"Love Jane Forever".to_vec()), -/// Ok(Ljf(*b"Love Jane Forever")) -/// ); -/// ``` -pub fn vec_n_into(vec: Vec) -> Result -where - V: From<[T; N]>, -{ - Ok(vec2array(vec)?.into()) -} - -/// Just like [`vec_n_into`] but without the checking. -/// -/// # Examples -/// ``` -/// #[derive(Debug, PartialEq)] -/// struct Ljf([u8; 17]); -/// impl From<[u8; 17]> for Ljf { -/// fn from(array: [u8; 17]) -> Self { -/// Self(array) -/// } -/// } -/// -/// assert_eq!( -/// array_bytes::vec_n_into_unchecked::(b"Love Jane Forever".to_vec()), -/// Ljf(*b"Love Jane Forever") -/// ); -/// ``` -pub fn vec_n_into_unchecked(vec: Vec) -> V -where - V: From<[T; N]>, -{ - vec2array_unchecked(vec).into() -} - -/// Convert hex bytes to hex string. -/// -/// This is useful when you are interacting with the IO. -/// -/// # Examples -/// ``` -/// assert_eq!( -/// array_bytes::hex_bytes2hex_str(b"0x4c6f7665204a616e6520466f7265766572"), -/// Ok("0x4c6f7665204a616e6520466f7265766572"), -/// ); -/// ``` -pub fn hex_bytes2hex_str(bytes: &[u8]) -> Result<&str> { - for (i, byte) in bytes.iter().enumerate().skip(if bytes.starts_with(b"0x") { 2 } else { 0 }) { - if !byte.is_ascii_hexdigit() { - Err(Error::InvalidCharacter { character: *byte as _, index: i })?; - } - } - - Ok( - // Validated in previous step, never fails here; qed. - unsafe { str::from_utf8_unchecked(bytes) }, - ) -} - -/// Just like [`hex_bytes2hex_str`] but without the checking. -/// -/// # Safety -/// See the [`str::from_utf8_unchecked`]. -/// -/// # Examples -/// ``` -/// assert_eq!( -/// array_bytes::hex_bytes2hex_str_unchecked(b"0x4c6f7665204a616e6520466f7265766572"), -/// "0x4c6f7665204a616e6520466f7265766572", -/// ); -/// ``` -pub fn hex_bytes2hex_str_unchecked(bytes: &[u8]) -> &str { - unsafe { str::from_utf8_unchecked(bytes) } -} - -/// `AsRef<[u8]>` to [`String`]. -/// -/// # Examples -/// ``` -/// assert_eq!( -/// array_bytes::bytes2hex("0x", b"Love Jane Forever"), -/// String::from("0x4c6f7665204a616e6520466f7265766572") -/// ); -/// ``` -pub fn bytes2hex(prefix: P, bytes: B) -> String -where - P: AsRef, - B: AsRef<[u8]>, -{ - let prefix = prefix.as_ref(); - let bytes = bytes.as_ref(); - let cap = prefix.len() + bytes.len() * 2; - let mut hex_bytes = >::with_capacity(cap); - - hex_bytes.extend_from_slice(prefix.as_bytes()); - - // The capacity is fixed, it's safe to set the length; qed. - unsafe { - hex_bytes.set_len(cap); - } - - let hex_ptr = unsafe { hex_bytes.as_mut_ptr().add(prefix.len()) }; - - for (i, &byte) in bytes.iter().enumerate() { - let high = HEX_CHARS[(byte >> 4) as usize]; - let low = HEX_CHARS[(byte & 0x0f) as usize]; - - unsafe { - *hex_ptr.add(i * 2) = high; - *hex_ptr.add(i * 2 + 1) = low; - } - } - - // All the bytes are looked up in the `HEX_CHARS`, it's safe to convert to string; qed. - unsafe { String::from_utf8_unchecked(hex_bytes.into_vec()) } -} - -// ? Add `bytes2hex_uppercase`. - -/// Just like [`hex2bytes`] but to a fixed length array. -/// -/// # Examples -/// ``` -/// assert_eq!( -/// array_bytes::hex2array("0x4c6f7665204a616e6520466f7265766572"), -/// Ok(*b"Love Jane Forever") -/// ); -/// ``` -pub fn hex2array(hex: H) -> Result<[u8; N]> -where - H: AsRef<[u8]>, -{ - vec2array(hex2bytes(hex)?.into_vec()) -} - -/// Just like [`hex2array`] but without the checking. -/// -/// # Examples -/// ``` -/// assert_eq!( -/// array_bytes::hex2array_unchecked("0x4c6f7665204a616e6520466f7265766572"), -/// *b"Love Jane Forever" -/// ); -/// ``` -pub fn hex2array_unchecked(hex: H) -> [u8; N] -where - H: AsRef<[u8]>, -{ - hex2bytes_unchecked(hex).into_vec().try_into().unwrap() -} - -/// `AsRef<[u8]>` to [`Vec`]. -/// -/// Return error if: -/// - length is odd -/// - encounter invalid hex ascii -/// -/// # Examples -/// ``` -/// assert_eq!( -/// array_bytes::hex2bytes("0x4c6f7665204a616e6520466f7265766572").unwrap().into_vec(), -/// b"Love Jane Forever".to_vec() -/// ); -/// ``` -pub fn hex2bytes(hex: H) -> Result> -where - H: AsRef<[u8]>, -{ - let hex = strip_0x(hex.as_ref()); - - if hex.len() % 2 != 0 { - Err(Error::InvalidLength)?; - } - - let cap = hex.len() / 2; - let mut bytes = >::with_capacity(cap); - - // The capacity is fixed, it's safe to set the length; qed. - unsafe { - bytes.set_len(cap); - } - - let bytes_ptr = bytes.as_mut_ptr(); - - for i in 0..cap { - let high = HEX_TO_DIGIT[hex[i * 2] as usize] - .ok_or(Error::InvalidCharacter { character: hex[i * 2] as char, index: i * 2 })?; - let low = HEX_TO_DIGIT[hex[i * 2 + 1] as usize].ok_or(Error::InvalidCharacter { - character: hex[i * 2 + 1] as char, - index: i * 2 + 1, - })?; - - unsafe { - *bytes_ptr.add(i) = (high << 4) | low; - } - } - - Ok(bytes) -} - -/// Just like [`hex2bytes`] but without checking. -/// -/// # Examples -/// ``` -/// assert_eq!( -/// array_bytes::hex2bytes_unchecked("0x4c6f7665204a616e6520466f7265766572").into_vec(), -/// b"Love Jane Forever" -/// ); -/// ``` -pub fn hex2bytes_unchecked(hex: H) -> SmallVec<[u8; 64]> -where - H: AsRef<[u8]>, -{ - let hex = strip_0x(hex.as_ref()); - let cap = hex.len() / 2; - let mut bytes = >::with_capacity(cap); - - // The capacity is fixed, it's safe to set the length; qed. - unsafe { - bytes.set_len(cap); - } - - let bytes_ptr = bytes.as_mut_ptr(); - - for i in 0..cap { - let high = HEX_TO_DIGIT[hex[i * 2] as usize].unwrap(); - let low = HEX_TO_DIGIT[hex[i * 2 + 1] as usize].unwrap(); - - unsafe { - *bytes_ptr.add(i) = (high << 4) | low; - } - } - - bytes -} - -/// `AsRef<[u8]>` to `&[u8]`. -/// -/// This function will modify the given slice's source and return the revised result. -/// -/// Return error if: -/// - length is odd -/// - encounter invalid hex ascii -/// - mismatched slice size -/// -/// # Examples -/// ``` -/// let mut array = [0; 17]; -/// -/// assert_eq!( -/// array_bytes::hex2slice("0x4c6f7665204a616e6520466f7265766572", &mut array), -/// Ok(b"Love Jane Forever".as_slice()) -/// ); -/// assert_eq!(array, *b"Love Jane Forever"); -/// ``` -pub fn hex2slice(hex: H, slice: &mut [u8]) -> Result<&[u8]> -where - H: AsRef<[u8]>, -{ - let hex = strip_0x(hex.as_ref()); - - if hex.len() % 2 != 0 { - Err(Error::InvalidLength)?; - } - - let expected_len = hex.len() >> 1; - - if expected_len != slice.len() { - Err(Error::MismatchedLength { expect: expected_len })?; - } - - for (byte, i) in slice.iter_mut().zip((0..hex.len()).step_by(2)) { - *byte = hex2byte((&hex[i], i), (&hex[i + 1], i + 1))?; - } - - Ok(slice) -} - -/// Just like [`hex2slice`] but without checking. -/// -/// # Examples -/// ``` -/// let mut array = [0; 17]; -/// -/// assert_eq!( -/// array_bytes::hex2slice_unchecked("0x4c6f7665204a616e6520466f7265766572", &mut array), -/// b"Love Jane Forever" -/// ); -/// assert_eq!(array, *b"Love Jane Forever"); -/// ``` -pub fn hex2slice_unchecked(hex: H, slice: &mut [u8]) -> &[u8] -where - H: AsRef<[u8]>, -{ - let hex = strip_0x(hex.as_ref()); - - slice - .iter_mut() - .zip((0..hex.len()).step_by(2)) - .for_each(|(byte, i)| *byte = hex2byte_unchecked(&hex[i], &hex[i + 1])); - - slice -} - -/// Try to convert `AsRef<[u8]>` to `T` directly, where `T: From>`. -/// -/// # Examples -/// ``` -/// #[derive(Debug, PartialEq)] -/// struct Ljf(Vec); -/// impl From> for Ljf { -/// fn from(vec: Vec) -> Self { -/// Self(vec) -/// } -/// } -/// -/// assert_eq!( -/// array_bytes::hex_into::<_, Ljf>("0x4c6f7665204a616e6520466f7265766572"), -/// Ok(Ljf(b"Love Jane Forever".to_vec())) -/// ); -/// ``` -pub fn hex_into(hex: H) -> Result -where - H: AsRef<[u8]>, - T: From>, -{ - Ok(hex2bytes(hex.as_ref())?.into_vec().into()) -} - -/// Just like [`hex_into`] but without the checking. -/// -/// # Examples -/// ``` -/// #[derive(Debug, PartialEq)] -/// struct Ljf(Vec); -/// impl From> for Ljf { -/// fn from(vec: Vec) -> Self { -/// Self(vec) -/// } -/// } -/// -/// assert_eq!( -/// array_bytes::hex_into_unchecked::<_, Ljf>("0x4c6f7665204a616e6520466f7265766572"), -/// Ljf(b"Love Jane Forever".to_vec()) -/// ); -/// ``` -pub fn hex_into_unchecked(hex: H) -> T -where - H: AsRef<[u8]>, - T: From>, -{ - hex2bytes_unchecked(hex).into_vec().into() -} - -/// Try to convert `AsRef<[u8]>` to `T` directly, where `T: From<[u8; N]>`. -/// -/// # Examples -/// ``` -/// #[derive(Debug, PartialEq)] -/// struct Ljf([u8; 17]); -/// impl From<[u8; 17]> for Ljf { -/// fn from(array: [u8; 17]) -> Self { -/// Self(array) -/// } -/// } -/// -/// assert_eq!( -/// array_bytes::hex_n_into::<_, Ljf, 17>("0x4c6f7665204a616e6520466f7265766572"), -/// Ok(Ljf(*b"Love Jane Forever")) -/// ); -/// ``` -pub fn hex_n_into(hex: H) -> Result -where - H: AsRef<[u8]>, - T: From<[u8; N]>, -{ - Ok(hex2array(hex)?.into()) -} - -/// Just like [`hex_n_into`] but without the checking. -/// -/// # Examples -/// ``` -/// #[derive(Debug, PartialEq)] -/// struct Ljf([u8; 17]); -/// impl From<[u8; 17]> for Ljf { -/// fn from(array: [u8; 17]) -> Self { -/// Self(array) -/// } -/// } -/// -/// assert_eq!( -/// array_bytes::hex_n_into_unchecked::<_, Ljf, 17>("0x4c6f7665204a616e6520466f7265766572"), -/// Ljf(*b"Love Jane Forever") -/// ); -/// ``` -pub fn hex_n_into_unchecked(hex: H) -> T -where - H: AsRef<[u8]>, - T: From<[u8; N]>, -{ - hex2array_unchecked(hex).into() -} - -/// Deserialize hex to `T`, where `T: From>`. -/// -/// # Examples -/// ``` -/// use serde::Deserialize; -/// -/// #[derive(Debug, PartialEq)] -/// struct Ljf(Vec); -/// impl From> for Ljf { -/// fn from(vec: Vec) -> Self { -/// Self(vec) -/// } -/// } -/// -/// #[derive(Debug, PartialEq, Deserialize)] -/// struct WrappedLjf { -/// #[serde(deserialize_with = "array_bytes::hex_deserialize_into")] -/// ljf: Ljf, -/// } -/// -/// assert_eq!( -/// serde_json::from_str::(r#"{ -/// "ljf": "0x4c6f7665204a616e6520466f7265766572" -/// }"#).unwrap(), -/// WrappedLjf { -/// ljf: Ljf(b"Love Jane Forever".to_vec()) -/// } -/// ); -#[cfg(feature = "serde")] -pub fn hex_deserialize_into<'de, D, T>(hex: D) -> Result -where - D: Deserializer<'de>, - T: From>, -{ - let hex_str = <&str>::deserialize(hex)?; - - hex2bytes(hex_str) - .map(|sv| sv.into_vec().into()) - .map_err(|e| D::Error::custom(format!("{e:?}"))) -} - -/// Deserialize hex to `T`, where `T: From<[u8; N]>`. -/// -/// # Examples -/// ``` -/// use serde::Deserialize; -/// -/// #[derive(Debug, PartialEq)] -/// struct Ljf([u8; 17]); -/// impl From<[u8; 17]> for Ljf { -/// fn from(array: [u8; 17]) -> Self { -/// Self(array) -/// } -/// } -/// -/// #[derive(Debug, PartialEq, Deserialize)] -/// struct WrappedLjf { -/// #[serde(deserialize_with = "array_bytes::hex_deserialize_n_into")] -/// ljf: Ljf, -/// } -/// -/// assert_eq!( -/// serde_json::from_str::(r#"{ -/// "ljf": "0x4c6f7665204a616e6520466f7265766572" -/// }"#).unwrap(), -/// WrappedLjf { -/// ljf: Ljf(*b"Love Jane Forever") -/// } -/// ); -#[cfg(feature = "serde")] -pub fn hex_deserialize_n_into<'de, D, T, const N: usize>(hex: D) -> Result -where - D: Deserializer<'de>, - T: From<[u8; N]>, -{ - let hex_str = <&str>::deserialize(hex)?; - - hex2array(hex_str).map(Into::into).map_err(|e| D::Error::custom(format!("{e:?}"))) -} - -/// Deserialize hex to the pre-defined primitive types. -/// -/// # Examples -/// ``` -/// use serde::Deserialize; -/// -/// #[derive(Debug, PartialEq, Deserialize)] -/// struct Ljf { -/// #[serde(deserialize_with = "array_bytes::de_try_from_hex")] -/// _0: u8, -/// #[serde(deserialize_with = "array_bytes::de_try_from_hex")] -/// _1: u16, -/// #[serde(deserialize_with = "array_bytes::de_try_from_hex")] -/// _2: u32, -/// #[serde(deserialize_with = "array_bytes::de_try_from_hex")] -/// _3: [u8; 4], -/// } -/// -/// assert_eq!( -/// serde_json::from_str::( -/// r#"{ -/// "_0": "0x5", -/// "_1": "0x2", -/// "_2": "0x0", -/// "_3": "0x01030104" -/// }"# -/// ) -/// .unwrap(), -/// Ljf { _0: 5, _1: 2, _2: 0, _3: [1, 3, 1, 4] } -/// ); -/// ``` -#[cfg(feature = "serde")] -pub fn de_try_from_hex<'de, D, T>(hex: D) -> Result -where - D: Deserializer<'de>, - T: TryFromHex, -{ - let hex = <&str>::deserialize(hex)?; - - T::try_from_hex(hex).map_err(|_| D::Error::custom(alloc::format!("invalid hex str `{}`", hex))) -} - -/// Serialize the pre-defined primitive types to hex. -/// -/// # Examples -/// ``` -/// use serde::Serialize; -/// -/// #[derive(Debug, PartialEq, Serialize)] -/// struct Ljf { -/// #[serde(serialize_with = "array_bytes::ser_hex")] -/// _0: u8, -/// #[serde(serialize_with = "array_bytes::ser_hex")] -/// _1: u16, -/// #[serde(serialize_with = "array_bytes::ser_hex")] -/// _2: u32, -/// #[serde(serialize_with = "array_bytes::ser_hex")] -/// _3: [u8; 4], -/// } -/// -/// assert_eq!( -/// serde_json::to_string::(&Ljf { _0: 5, _1: 2, _2: 0, _3: [1, 3, 1, 4] }).unwrap(), -/// r#"{"_0":"0x5","_1":"0x2","_2":"0x0","_3":"0x01030104"}"# -/// ); -/// ``` -#[cfg(feature = "serde")] -pub fn ser_hex(hex: T, serializer: S) -> Result -where - S: Serializer, - T: Hex, -{ - serializer.serialize_str(&hex.hex("0x")) -} -/// Just like [`ser_hex`] but without the prefix. -/// -/// # Examples -/// ``` -/// use serde::Serialize; -/// -/// #[derive(Debug, PartialEq, Serialize)] -/// struct Ljf { -/// #[serde(serialize_with = "array_bytes::ser_hex_without_prefix")] -/// _0: u8, -/// #[serde(serialize_with = "array_bytes::ser_hex_without_prefix")] -/// _1: u16, -/// #[serde(serialize_with = "array_bytes::ser_hex_without_prefix")] -/// _2: u32, -/// #[serde(serialize_with = "array_bytes::ser_hex_without_prefix")] -/// _3: [u8; 4], -/// } -/// -/// assert_eq!( -/// serde_json::to_string::(&Ljf { _0: 5, _1: 2, _2: 0, _3: [1, 3, 1, 4] }).unwrap(), -/// r#"{"_0":"5","_1":"2","_2":"0","_3":"01030104"}"# -/// ); -/// ``` -#[cfg(feature = "serde")] -pub fn ser_hex_without_prefix(hex: T, serializer: S) -> Result -where - S: Serializer, - T: Hex, -{ - serializer.serialize_str(&hex.hex("")) -} - -#[inline(always)] -fn strip_0x(hex: &[u8]) -> &[u8] { - if let Some(hex) = hex.strip_prefix(b"0x") { - hex - } else { - hex - } -} - -#[inline(always)] -fn hex_ascii2digit(hex_ascii: &u8) -> Option { - HEX_TO_DIGIT[*hex_ascii as usize] -} - -#[inline(always)] -fn hex2byte(hex_ascii_1: (&u8, usize), hex_ascii_2: (&u8, usize)) -> Result { - let byte = hex_ascii2digit(hex_ascii_1.0) - .ok_or(Error::InvalidCharacter { character: *hex_ascii_1.0 as _, index: hex_ascii_1.1 })? - << 4 | hex_ascii2digit(hex_ascii_2.0) - .ok_or(Error::InvalidCharacter { character: *hex_ascii_2.0 as _, index: hex_ascii_2.1 })?; - - Ok(byte) -} - -#[inline(always)] -fn hex2byte_unchecked(hex_ascii_1: &u8, hex_ascii_2: &u8) -> u8 { - hex_ascii2digit(hex_ascii_1).unwrap() << 4 | hex_ascii2digit(hex_ascii_2).unwrap() -} - -#[inline(always)] -fn pad_array(any: A, element: T, pad_start: bool) -> [T; N] -where - A: AsRef<[T]>, - T: Copy, -{ - let a = any.as_ref(); - - match a.len().cmp(&N) { - // `a.len() == N`; qed. - Ordering::Equal => slice2array_unchecked(a), - // `a[..N]` has exactly `N` elements; qed. - Ordering::Greater => slice2array_unchecked(&a[..N]), - Ordering::Less => { - let mut padded = [element; N]; - - if pad_start { - padded[N - a.len()..].copy_from_slice(a); - } else { - padded[..a.len()].copy_from_slice(a); - } - - padded - }, - } -} diff --git a/src/op.rs b/src/op.rs new file mode 100644 index 0000000..34868d9 --- /dev/null +++ b/src/op.rs @@ -0,0 +1,95 @@ +mod slice; +pub use slice::*; + +mod vec; +pub use vec::*; + +// core +use core::cmp::Ordering; + +/// Prefixes the given element to the given array/slice/vector to make it a fixed-size array of +/// length `N`. +/// +/// If the length of the array/slice/vector is already equal to `N`, it returns the +/// array/slice/vector as a fixed-size array. +/// If the length of the array/slice/vector is greater than `N`, it returns the first `N` elements +/// of the array/slice/vector as a fixed-size array. +/// If the length of the array/slice/vector is less than `N`, it creates a new fixed-size array of +/// length `N` and copies the array/slice/vector into it, padding the remaining elements with the +/// given element. +/// +/// # Examples +/// ``` +/// assert_eq!(array_bytes::prefix_with::<_, _, 4>([5, 2, 0, 1], 0), [5, 2, 0, 1]); +/// assert_eq!(array_bytes::prefix_with::<_, _, 4>([5, 2, 0, 1, 3, 1, 4], 0), [5, 2, 0, 1]); +/// assert_eq!(array_bytes::prefix_with::<_, _, 5>([5, 2, 0], 0), [0, 0, 5, 2, 0]); +/// ``` +pub fn prefix_with(any: A, element: T) -> [T; N] +where + A: AsRef<[T]>, + T: Copy, +{ + pad_array(any, element, true) +} +#[test] +fn prefix_with_should_work() { + assert_eq!(prefix_with::<_, _, 4>([1, 2, 3, 4], 0), [1, 2, 3, 4]); + assert_eq!(prefix_with::<_, _, 4>([1, 2, 3, 4, 5, 6], 0), [1, 2, 3, 4]); + assert_eq!(prefix_with::<_, _, 5>([1, 2, 3], 0), [0, 0, 1, 2, 3]); +} + +/// Suffixes the given element to the given array/slice/vector to make it a fixed-size array of +/// length `N`. +/// +/// If the length of the array/slice/vector is already equal to `N`, it returns the +/// array/slice/vector as a fixed-size array. +/// If the length of the array/slice/vector is greater than `N`, it returns the first `N` elements +/// of the array/slice/vector as a fixed-size array. +/// If the length of the array/slice/vector is less than `N`, it creates a new fixed-size array of +/// length `N` and copies the array/slice/vector into it, padding the remaining elements with the +/// given element. +/// +/// # Examples +/// ``` +/// assert_eq!(array_bytes::suffix_with::<_, _, 4>([5, 2, 0, 1], 0), [5, 2, 0, 1]); +/// assert_eq!(array_bytes::suffix_with::<_, _, 4>([5, 2, 0, 1, 3, 1, 4], 0), [5, 2, 0, 1]); +/// assert_eq!(array_bytes::suffix_with::<_, _, 5>([5, 2, 0], 0), [5, 2, 0, 0, 0]); +/// ``` +pub fn suffix_with(any: A, element: T) -> [T; N] +where + A: AsRef<[T]>, + T: Copy, +{ + pad_array(any, element, false) +} +#[test] +fn suffix_with_should_work() { + assert_eq!(suffix_with::<_, _, 4>([1, 2, 3, 4], 0), [1, 2, 3, 4]); + assert_eq!(suffix_with::<_, _, 4>([1, 2, 3, 4, 5, 6], 0), [1, 2, 3, 4]); + assert_eq!(suffix_with::<_, _, 5>([1, 2, 3], 0), [1, 2, 3, 0, 0]); +} + +#[inline(always)] +fn pad_array(any: A, element: T, pad_start: bool) -> [T; N] +where + A: AsRef<[T]>, + T: Copy, +{ + let a = any.as_ref(); + + match a.len().cmp(&N) { + Ordering::Equal => slice2array(a).expect("`a.len() == N`; qed"), + Ordering::Greater => slice2array(&a[..N]).expect("`a[..N]` has exactly `N` elements; qed"), + Ordering::Less => { + let mut padded = [element; N]; + + if pad_start { + padded[N - a.len()..].copy_from_slice(a); + } else { + padded[..a.len()].copy_from_slice(a); + } + + padded + }, + } +} diff --git a/src/op/slice.rs b/src/op/slice.rs new file mode 100644 index 0000000..6a535a4 --- /dev/null +++ b/src/op/slice.rs @@ -0,0 +1,69 @@ +// self +use crate::prelude::*; + +/// Convert `&[T]` to `[T; N]`. +/// +/// # Examples +/// ``` +/// assert_eq!( +/// array_bytes::slice2array::<_, 8>(&[5, 2, 0, 1, 3, 1, 4, 0]), +/// Ok([5, 2, 0, 1, 3, 1, 4, 0]) +/// ); +/// ``` +#[inline(always)] +pub fn slice2array(slice: &[T]) -> Result<[T; N]> +where + T: Copy, +{ + slice.try_into().map_err(|_| Error::MismatchedLength { expect: N }) +} + +#[test] +fn slice2array_should_work() { + assert_eq!(slice2array::<_, 8>(&[0; 8]), Ok([0; 8])); +} + +/// Convert `&[T]` to `&[T; N]`. +/// +/// # Examples +/// ``` +/// assert_eq!( +/// array_bytes::slice2array_ref::<_, 8>(&[5, 2, 0, 1, 3, 1, 4, 0]), +/// Ok(&[5, 2, 0, 1, 3, 1, 4, 0]) +/// ); +/// ``` +pub fn slice2array_ref(slice: &[T]) -> Result<&[T; N]> +where + T: Copy, +{ + slice.try_into().map_err(|_| Error::MismatchedLength { expect: N }) +} + +/// Convert `&[T]` to `V` where `V: From<[T; N]>`. +/// +/// # Examples +/// ``` +/// #[derive(Debug, PartialEq)] +/// struct Ljf([u8; 17]); +/// impl From<[u8; 17]> for Ljf { +/// fn from(array: [u8; 17]) -> Self { +/// Self(array) +/// } +/// } +/// +/// assert_eq!( +/// array_bytes::slice_n_into::(b"Love Jane Forever"), +/// Ok(Ljf(*b"Love Jane Forever")) +/// ); +/// ``` +pub fn slice_n_into(slice: &[T]) -> Result +where + T: Copy, + V: From<[T; N]>, +{ + Ok(slice2array(slice)?.into()) +} +#[test] +fn slice_n_into_should_work() { + assert_eq!(slice_n_into::(b"Love Jane Forever"), Ok(Ljfn(*b"Love Jane Forever"))); +} diff --git a/src/op/vec.rs b/src/op/vec.rs new file mode 100644 index 0000000..5f0c231 --- /dev/null +++ b/src/op/vec.rs @@ -0,0 +1,48 @@ +// self +use crate::prelude::*; + +/// Convert `Vec` to `[T; N]`. +/// +/// # Examples +/// ``` +/// assert_eq!(array_bytes::vec2array::<_, 8>(vec![0; 8]), Ok([0; 8])); +/// ``` +pub fn vec2array(vec: Vec) -> Result<[T; N]> { + vec.try_into().map_err(|_| Error::MismatchedLength { expect: N }) +} +#[test] +fn vec2array_should_work() { + assert_eq!(vec2array::<_, 8>(alloc::vec![0; 8]), Ok([0; 8])); +} + +/// Convert `Vec` to `V` where `V: From<[T; N]>`. +/// +/// # Examples +/// +/// ``` +/// #[derive(Debug, PartialEq)] +/// struct Ljf([u8; 17]); +/// impl From<[u8; 17]> for Ljf { +/// fn from(array: [u8; 17]) -> Self { +/// Self(array) +/// } +/// } +/// +/// assert_eq!( +/// array_bytes::vec_n_into::(b"Love Jane Forever".to_vec()), +/// Ok(Ljf(*b"Love Jane Forever")) +/// ); +/// ``` +pub fn vec_n_into(vec: Vec) -> Result +where + V: From<[T; N]>, +{ + Ok(vec2array(vec)?.into()) +} +#[test] +fn vec_n_into_should_work() { + assert_eq!( + vec_n_into::(b"Love Jane Forever".to_vec()), + Ok(Ljfn(*b"Love Jane Forever")) + ); +} diff --git a/src/serde.rs b/src/serde.rs new file mode 100644 index 0000000..a989327 --- /dev/null +++ b/src/serde.rs @@ -0,0 +1,355 @@ +// alloc +use alloc::format; +// crates.io +#[cfg(test)] use serde::Serialize; +use serde::{de::Error as DeError, Deserialize, Deserializer, Serializer}; +// self +use crate::{prelude::*, Dehexify, Hexify}; + +/// Serialize `T` to hex. +/// +/// # Examples +/// ``` +/// use serde::Serialize; +/// +/// #[derive(Debug, PartialEq, Serialize)] +/// struct Ljf { +/// #[serde(serialize_with = "array_bytes::ser_hexify")] +/// _0: u8, +/// #[serde(serialize_with = "array_bytes::ser_hexify")] +/// _1: u16, +/// #[serde(serialize_with = "array_bytes::ser_hexify")] +/// _2: u32, +/// #[serde(serialize_with = "array_bytes::ser_hexify")] +/// _3: [u8; 4], +/// } +/// +/// assert_eq!( +/// serde_json::to_string::(&Ljf { _0: 5, _1: 2, _2: 0, _3: [1, 3, 1, 4] }).unwrap(), +/// r#"{"_0":"5","_1":"2","_2":"0","_3":"01030104"}"# +/// ); +/// ``` +pub fn ser_hexify(value: T, serializer: S) -> Result +where + S: Serializer, + T: Hexify, +{ + serializer.serialize_str(&value.hexify()) +} + +/// Serialize `T` to hex with uppercase. +/// +/// # Examples +/// ``` +/// use serde::Serialize; +/// +/// #[derive(Debug, PartialEq, Serialize)] +/// struct Ljf { +/// #[serde(serialize_with = "array_bytes::ser_hexify_upper")] +/// _0: u8, +/// #[serde(serialize_with = "array_bytes::ser_hexify_upper")] +/// _1: u16, +/// #[serde(serialize_with = "array_bytes::ser_hexify_upper")] +/// _2: u32, +/// #[serde(serialize_with = "array_bytes::ser_hexify_upper")] +/// _3: [u8; 4], +/// } +/// +/// assert_eq!( +/// serde_json::to_string::(&Ljf { _0: 5, _1: 2, _2: 0, _3: [1, 3, 1, 4] }).unwrap(), +/// r#"{"_0":"5","_1":"2","_2":"0","_3":"01030104"}"# +/// ); +/// ``` +pub fn ser_hexify_upper(value: T, serializer: S) -> Result +where + S: Serializer, + T: Hexify, +{ + serializer.serialize_str(&value.hexify_upper()) +} + +/// Serialize `T` to hex with `0x` prefix. +/// +/// # Examples +/// ``` +/// use serde::Serialize; +/// +/// #[derive(Debug, PartialEq, Serialize)] +/// struct Ljf { +/// #[serde(serialize_with = "array_bytes::ser_hexify_prefixed")] +/// _0: u8, +/// #[serde(serialize_with = "array_bytes::ser_hexify_prefixed")] +/// _1: u16, +/// #[serde(serialize_with = "array_bytes::ser_hexify_prefixed")] +/// _2: u32, +/// #[serde(serialize_with = "array_bytes::ser_hexify_prefixed")] +/// _3: [u8; 4], +/// } +/// +/// assert_eq!( +/// serde_json::to_string::(&Ljf { _0: 5, _1: 2, _2: 0, _3: [1, 3, 1, 4] }).unwrap(), +/// r#"{"_0":"0x5","_1":"0x2","_2":"0x0","_3":"0x01030104"}"# +/// ); +/// ``` +pub fn ser_hexify_prefixed(value: T, serializer: S) -> Result +where + T: Hexify, + S: Serializer, +{ + serializer.serialize_str(&value.hexify_prefixed()) +} + +/// Serialize `T` to hex with `0x` prefix and uppercase. +/// +/// # Examples +/// ``` +/// use serde::Serialize; +/// +/// #[derive(Debug, PartialEq, Serialize)] +/// struct Ljf { +/// #[serde(serialize_with = "array_bytes::ser_hexify_prefixed")] +/// _0: u8, +/// #[serde(serialize_with = "array_bytes::ser_hexify_prefixed")] +/// _1: u16, +/// #[serde(serialize_with = "array_bytes::ser_hexify_prefixed")] +/// _2: u32, +/// #[serde(serialize_with = "array_bytes::ser_hexify_prefixed")] +/// _3: [u8; 4], +/// } +/// +/// assert_eq!( +/// serde_json::to_string::(&Ljf { _0: 5, _1: 2, _2: 0, _3: [1, 3, 1, 4] }).unwrap(), +/// r#"{"_0":"0x5","_1":"0x2","_2":"0x0","_3":"0x01030104"}"# +/// ); +/// ``` +pub fn ser_hexify_prefixed_upper(value: T, serializer: S) -> Result +where + S: Serializer, + T: Hexify, +{ + serializer.serialize_str(&value.hexify_prefixed()) +} + +/// Deserialize hex to `T`. +/// +/// # Examples +/// ``` +/// use serde::Deserialize; +/// +/// #[derive(Debug, PartialEq, Deserialize)] +/// struct Ljf { +/// #[serde(deserialize_with = "array_bytes::de_dehexify")] +/// _0: u8, +/// #[serde(deserialize_with = "array_bytes::de_dehexify")] +/// _1: u16, +/// #[serde(deserialize_with = "array_bytes::de_dehexify")] +/// _2: u32, +/// #[serde(deserialize_with = "array_bytes::de_dehexify")] +/// _3: [u8; 4], +/// } +/// +/// assert_eq!( +/// serde_json::from_str::( +/// r#"{ +/// "_0": "0x5", +/// "_1": "0x2", +/// "_2": "0x0", +/// "_3": "0x01030104" +/// }"# +/// ) +/// .unwrap(), +/// Ljf { _0: 5, _1: 2, _2: 0, _3: [1, 3, 1, 4] } +/// ); +/// ``` +pub fn de_dehexify<'de, D, T>(hex: D) -> Result +where + D: Deserializer<'de>, + T: Dehexify, +{ + let hex = <&str>::deserialize(hex)?; + + T::dehexify(hex).map_err(|_| D::Error::custom(alloc::format!("invalid hex str `{}`", hex))) +} + +/// Deserialize hex to `T` where `T: From>`. +/// +/// # Examples +/// ``` +/// use serde::Deserialize; +/// +/// #[derive(Debug, PartialEq)] +/// struct Ljf(Vec); +/// impl From> for Ljf { +/// fn from(vec: Vec) -> Self { +/// Self(vec) +/// } +/// } +/// +/// #[derive(Debug, PartialEq, Deserialize)] +/// struct WrappedLjf { +/// #[serde(deserialize_with = "array_bytes::dehexify_vec_then_deserialize_into")] +/// ljf: Ljf, +/// } +/// +/// assert_eq!( +/// serde_json::from_str::(r#"{ +/// "ljf": "0x4c6f7665204a616e6520466f7265766572" +/// }"#).unwrap(), +/// WrappedLjf { +/// ljf: Ljf(b"Love Jane Forever".to_vec()) +/// } +/// ); +pub fn dehexify_vec_then_deserialize_into<'de, D, T>(hex: D) -> Result +where + D: Deserializer<'de>, + T: From>, +{ + let hex = <&str>::deserialize(hex)?; + + >::dehexify(hex).map(|sv| sv.into()).map_err(|e| D::Error::custom(format!("{e:?}"))) +} +#[test] +fn dehexify_vec_then_deserialize_into_should_work() { + #[derive(Debug, PartialEq, Deserialize)] + struct WrappedLjf { + #[serde(deserialize_with = "dehexify_vec_then_deserialize_into")] + ljf: Ljf, + } + + assert_eq!( + serde_json::from_str::( + r#"{ + "ljf": "0x4c6f7665204a616e6520466f7265766572" + }"# + ) + .unwrap(), + WrappedLjf { ljf: Ljf(b"Love Jane Forever".to_vec()) } + ); + assert_eq!( + serde_json::from_str::( + r#"{ + "ljf": "4c6f7665204a616e6520466f7265766572" + }"# + ) + .unwrap(), + WrappedLjf { ljf: Ljf(b"Love Jane Forever".to_vec()) } + ); +} + +/// Deserialize hex to `T` where `T: From<[u8; N]>`. +/// +/// # Examples +/// ``` +/// use serde::Deserialize; +/// +/// #[derive(Debug, PartialEq)] +/// struct Ljf([u8; 17]); +/// impl From<[u8; 17]> for Ljf { +/// fn from(array: [u8; 17]) -> Self { +/// Self(array) +/// } +/// } +/// +/// #[derive(Debug, PartialEq, Deserialize)] +/// struct WrappedLjf { +/// #[serde(deserialize_with = "array_bytes::dehexify_array_then_deserialize_into")] +/// ljf: Ljf, +/// } +/// +/// assert_eq!( +/// serde_json::from_str::(r#"{ +/// "ljf": "0x4c6f7665204a616e6520466f7265766572" +/// }"#).unwrap(), +/// WrappedLjf { +/// ljf: Ljf(*b"Love Jane Forever") +/// } +/// ); +pub fn dehexify_array_then_deserialize_into<'de, D, T, const N: usize>( + hex: D, +) -> Result +where + D: Deserializer<'de>, + T: From<[u8; N]>, +{ + let hex = <&str>::deserialize(hex)?; + + <[u8; N]>::dehexify(hex).map(Into::into).map_err(|e| D::Error::custom(format!("{e:?}"))) +} +#[test] +fn dehexify_array_then_deserialize_into_should_work() { + #[derive(Debug, PartialEq, Deserialize)] + struct WrappedLjf { + #[serde(deserialize_with = "dehexify_array_then_deserialize_into")] + ljf: Ljfn, + } + + assert_eq!( + serde_json::from_str::( + r#"{ + "ljf": "0x4c6f7665204a616e6520466f7265766572" + }"# + ) + .unwrap(), + WrappedLjf { ljf: Ljfn(*b"Love Jane Forever") } + ); + assert_eq!( + serde_json::from_str::( + r#"{ + "ljf": "4c6f7665204a616e6520466f7265766572" + }"# + ) + .unwrap(), + WrappedLjf { ljf: Ljfn(*b"Love Jane Forever") } + ); +} + +#[test] +fn serde_should_work() { + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct LjfPredefined { + #[serde(deserialize_with = "de_dehexify", serialize_with = "ser_hexify")] + _0: u8, + #[serde(deserialize_with = "de_dehexify", serialize_with = "ser_hexify_upper")] + _1: u16, + #[serde(deserialize_with = "de_dehexify", serialize_with = "ser_hexify_prefixed")] + _2: u32, + #[serde(deserialize_with = "de_dehexify", serialize_with = "ser_hexify_prefixed_upper")] + _3: u64, + #[serde(deserialize_with = "de_dehexify", serialize_with = "ser_hexify")] + _4: u128, + #[serde(deserialize_with = "de_dehexify", serialize_with = "ser_hexify_upper")] + _5: usize, + #[serde(deserialize_with = "de_dehexify", serialize_with = "ser_hexify_prefixed")] + _6: [u8; 17], + #[serde(deserialize_with = "de_dehexify", serialize_with = "ser_hexify_prefixed_upper")] + _7: Vec, + } + impl Default for LjfPredefined { + fn default() -> Self { + Self { + _0: 52, + _1: 520, + _2: 5_201_314, + _3: 5_201_314, + _4: 5_201_314, + _5: 5_201_314, + _6: *b"Love Jane Forever", + _7: b"Love Jane Forever".to_vec(), + } + } + } + + let ljf = LjfPredefined::default(); + let result = serde_json::to_string(&ljf); + assert!(result.is_ok()); + + let json = result.unwrap(); + assert_eq!( + json, + r#"{"_0":"34","_1":"208","_2":"0x4f5da2","_3":"0x4f5da2","_4":"4f5da2","_5":"4F5DA2","_6":"0x4c6f7665204a616e6520466f7265766572","_7":"0x4c6f7665204a616e6520466f7265766572"}"# + ); + + let result = serde_json::from_str::(&json); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), ljf); +} diff --git a/src/test.rs b/src/test.rs deleted file mode 100644 index 86ed13b..0000000 --- a/src/test.rs +++ /dev/null @@ -1,1331 +0,0 @@ -#![allow(clippy::upper_case_acronyms)] - -// Suppress `unused_crate_dependencies` error. -use const_hex as _; -use criterion as _; -use faster_hex as _; -use hex as _; -use rustc_hex as _; -use serde as _; -use serde_json as _; - -// crates.io -#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -// self -use crate::*; - -macro_rules! bytes { - ($v:expr; $n:expr) => {{ - (0..$n).map(|_| $v).collect::>() - }}; -} - -#[derive(Debug, PartialEq)] -struct Ljf(Vec); -impl From> for Ljf { - fn from(bytes: Vec) -> Self { - Self(bytes) - } -} - -#[derive(Debug, PartialEq)] -struct Ljfn([u8; 17]); -impl From<[u8; 17]> for Ljfn { - fn from(array: [u8; 17]) -> Self { - Self(array) - } -} - -#[derive(Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] -struct LjfPredefined { - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _0: isize, - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _1: i8, - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _2: i16, - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _3: i32, - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _4: i64, - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _5: i128, - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _6: usize, - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _7: u8, - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _8: u16, - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _9: u32, - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _10: u64, - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _11: u128, - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _12: Vec, - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _13: [u8; 1], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _14: [u8; 2], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _15: [u8; 3], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _16: [u8; 4], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _17: [u8; 5], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _18: [u8; 6], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _19: [u8; 7], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _20: [u8; 8], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _21: [u8; 9], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _22: [u8; 10], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _23: [u8; 11], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _24: [u8; 12], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _25: [u8; 13], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _26: [u8; 14], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _27: [u8; 15], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _28: [u8; 16], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _29: [u8; 17], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _30: [u8; 18], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _31: [u8; 19], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _32: [u8; 20], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _33: [u8; 21], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _34: [u8; 22], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _35: [u8; 23], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _36: [u8; 24], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _37: [u8; 25], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _38: [u8; 26], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _39: [u8; 27], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _40: [u8; 28], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _41: [u8; 29], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _42: [u8; 30], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _43: [u8; 31], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _44: [u8; 32], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _45: [u8; 33], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _46: [u8; 34], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _47: [u8; 35], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _48: [u8; 36], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _49: [u8; 37], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _50: [u8; 38], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _51: [u8; 39], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _52: [u8; 40], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _53: [u8; 41], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _54: [u8; 42], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _55: [u8; 43], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _56: [u8; 44], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _57: [u8; 45], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _58: [u8; 46], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _59: [u8; 47], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _60: [u8; 48], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _61: [u8; 49], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _62: [u8; 50], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _63: [u8; 51], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _64: [u8; 52], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _65: [u8; 53], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _66: [u8; 54], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _67: [u8; 55], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _68: [u8; 56], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _69: [u8; 57], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _70: [u8; 58], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _71: [u8; 59], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _72: [u8; 60], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _73: [u8; 61], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _74: [u8; 62], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _75: [u8; 63], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _76: [u8; 64], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _77: [u8; 128], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _78: [u8; 256], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _79: [u8; 512], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _80: [u8; 1024], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _81: [u8; 2048], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _82: [u8; 4096], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _83: [u8; 8192], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _84: [u8; 16384], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _85: [u8; 32768], - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "de_try_from_hex", serialize_with = "ser_hex") - )] - _86: [u8; 65536], -} -impl Default for LjfPredefined { - fn default() -> Self { - Self { - _0: 5, - _1: 2, - _2: 0, - _3: 5_201_314, - _4: 5_201_314, - _5: 5_201_314, - _6: 5, - _7: 2, - _8: 0, - _9: 5_201_314, - _10: 5_201_314, - _11: 5_201_314, - _12: b"Love Jane Forever".to_vec(), - _13: [5], - _14: [5, 2], - _15: [5, 2, 0], - _16: [5, 2, 0, 1], - _17: [5, 2, 0, 1, 3], - _18: [5, 2, 0, 1, 3, 1], - _19: [5, 2, 0, 1, 3, 1, 4], - _20: [0; 8], - _21: [0; 9], - _22: [0; 10], - _23: [0; 11], - _24: [0; 12], - _25: [0; 13], - _26: [0; 14], - _27: [0; 15], - _28: [0; 16], - _29: [0; 17], - _30: [0; 18], - _31: [0; 19], - _32: [0; 20], - _33: [0; 21], - _34: [0; 22], - _35: [0; 23], - _36: [0; 24], - _37: [0; 25], - _38: [0; 26], - _39: [0; 27], - _40: [0; 28], - _41: [0; 29], - _42: [0; 30], - _43: [0; 31], - _44: [0; 32], - _45: [0; 33], - _46: [0; 34], - _47: [0; 35], - _48: [0; 36], - _49: [0; 37], - _50: [0; 38], - _51: [0; 39], - _52: [0; 40], - _53: [0; 41], - _54: [0; 42], - _55: [0; 43], - _56: [0; 44], - _57: [0; 45], - _58: [0; 46], - _59: [0; 47], - _60: [0; 48], - _61: [0; 49], - _62: [0; 50], - _63: [0; 51], - _64: [0; 52], - _65: [0; 53], - _66: [0; 54], - _67: [0; 55], - _68: [0; 56], - _69: [0; 57], - _70: [0; 58], - _71: [0; 59], - _72: [0; 60], - _73: [0; 61], - _74: [0; 62], - _75: [0; 63], - _76: [0; 64], - _77: [0; 128], - _78: [0; 256], - _79: [0; 512], - _80: [0; 1024], - _81: [0; 2048], - _82: [0; 4096], - _83: [0; 8192], - _84: [0; 16384], - _85: [0; 32768], - _86: [0; 65536], - } - } -} - -#[test] -fn try_from_hex_should_work() { - let ljf = LjfPredefined::default(); - - assert_eq!(TryFromHex::try_from_hex("0x5"), Ok(ljf._0)); - assert_eq!(TryFromHex::try_from_hex("5"), Ok(ljf._0)); - assert_eq!(TryFromHex::try_from_hex("0x2"), Ok(ljf._1)); - assert_eq!(TryFromHex::try_from_hex("2"), Ok(ljf._1)); - assert_eq!(TryFromHex::try_from_hex("0x0"), Ok(ljf._2)); - assert_eq!(TryFromHex::try_from_hex("0"), Ok(ljf._2)); - assert_eq!(TryFromHex::try_from_hex("0x4f5da2"), Ok(ljf._3)); - assert_eq!(TryFromHex::try_from_hex("4f5da2"), Ok(ljf._3)); - assert_eq!(TryFromHex::try_from_hex("0x4f5da2"), Ok(ljf._4)); - assert_eq!(TryFromHex::try_from_hex("4f5da2"), Ok(ljf._4)); - assert_eq!(TryFromHex::try_from_hex("0x4f5da2"), Ok(ljf._5)); - assert_eq!(TryFromHex::try_from_hex("4f5da2"), Ok(ljf._5)); - assert_eq!(TryFromHex::try_from_hex("0x5"), Ok(ljf._6)); - assert_eq!(TryFromHex::try_from_hex("5"), Ok(ljf._6)); - assert_eq!(TryFromHex::try_from_hex("0x2"), Ok(ljf._7)); - assert_eq!(TryFromHex::try_from_hex("2"), Ok(ljf._7)); - assert_eq!(TryFromHex::try_from_hex("0x0"), Ok(ljf._8)); - assert_eq!(TryFromHex::try_from_hex("0"), Ok(ljf._8)); - assert_eq!(TryFromHex::try_from_hex("0x4f5da2"), Ok(ljf._9)); - assert_eq!(TryFromHex::try_from_hex("4f5da2"), Ok(ljf._9)); - assert_eq!(TryFromHex::try_from_hex("0x4f5da2"), Ok(ljf._10)); - assert_eq!(TryFromHex::try_from_hex("4f5da2"), Ok(ljf._10)); - assert_eq!(TryFromHex::try_from_hex("0x4f5da2"), Ok(ljf._11)); - assert_eq!(TryFromHex::try_from_hex("4f5da2"), Ok(ljf._11)); - assert_eq!( - TryFromHex::try_from_hex("0x4c6f7665204a616e6520466f7265766572"), - Ok(ljf._12.clone()) - ); - assert_eq!(TryFromHex::try_from_hex("4c6f7665204a616e6520466f7265766572"), Ok(ljf._12)); - assert_eq!(TryFromHex::try_from_hex("0x05"), Ok(ljf._13)); - assert_eq!(TryFromHex::try_from_hex("05"), Ok(ljf._13)); - assert_eq!(TryFromHex::try_from_hex("0x0502"), Ok(ljf._14)); - assert_eq!(TryFromHex::try_from_hex("0502"), Ok(ljf._14)); - assert_eq!(TryFromHex::try_from_hex("0x050200"), Ok(ljf._15)); - assert_eq!(TryFromHex::try_from_hex("050200"), Ok(ljf._15)); - assert_eq!(TryFromHex::try_from_hex("0x05020001"), Ok(ljf._16)); - assert_eq!(TryFromHex::try_from_hex("05020001"), Ok(ljf._16)); - assert_eq!(TryFromHex::try_from_hex("0x0502000103"), Ok(ljf._17)); - assert_eq!(TryFromHex::try_from_hex("0502000103"), Ok(ljf._17)); - assert_eq!(TryFromHex::try_from_hex("0x050200010301"), Ok(ljf._18)); - assert_eq!(TryFromHex::try_from_hex("050200010301"), Ok(ljf._18)); - assert_eq!(TryFromHex::try_from_hex("0x05020001030104"), Ok(ljf._19)); - assert_eq!(TryFromHex::try_from_hex("05020001030104"), Ok(ljf._19)); - - macro_rules! assert_try_from_hex_array { - ($ljf:expr, $(($field:ident, $len:expr)),+,) => { - $( - let hex = "0".repeat($len * 2); - - assert_eq!(TryFromHex::try_from_hex(format!("0x{hex}")), Ok($ljf.$field)); - assert_eq!(TryFromHex::try_from_hex(hex), Ok($ljf.$field)); - )+ - }; - } - - assert_try_from_hex_array! { - ljf, - (_20, 8), - (_21, 9), - (_22, 10), - (_23, 11), - (_24, 12), - (_25, 13), - (_26, 14), - (_27, 15), - (_28, 16), - (_29, 17), - (_30, 18), - (_31, 19), - (_32, 20), - (_33, 21), - (_34, 22), - (_35, 23), - (_36, 24), - (_37, 25), - (_38, 26), - (_39, 27), - (_40, 28), - (_41, 29), - (_42, 30), - (_43, 31), - (_44, 32), - (_45, 33), - (_46, 34), - (_47, 35), - (_48, 36), - (_49, 37), - (_50, 38), - (_51, 39), - (_52, 40), - (_53, 41), - (_54, 42), - (_55, 43), - (_56, 44), - (_57, 45), - (_58, 46), - (_59, 47), - (_60, 48), - (_61, 49), - (_62, 50), - (_63, 51), - (_64, 52), - (_65, 53), - (_66, 54), - (_67, 55), - (_68, 56), - (_69, 57), - (_70, 58), - (_71, 59), - (_72, 60), - (_73, 61), - (_74, 62), - (_75, 63), - (_76, 64), - (_77, 128), - (_78, 256), - (_79, 512), - (_80, 1024), - (_81, 2048), - (_82, 4096), - (_83, 8192), - (_84, 16384), - (_85, 32768), - (_86, 65536), - } -} - -#[test] -fn hex_should_work() { - let ljf = LjfPredefined::default(); - - assert_eq!(ljf._0.hex("0x"), "0x5"); - assert_eq!(ljf._0.hex(""), "5"); - assert_eq!(ljf._1.hex("0x"), "0x2"); - assert_eq!(ljf._1.hex(""), "2"); - assert_eq!(ljf._2.hex("0x"), "0x0"); - assert_eq!(ljf._2.hex(""), "0"); - assert_eq!(ljf._3.hex("0x"), "0x4f5da2"); - assert_eq!(ljf._3.hex(""), "4f5da2"); - assert_eq!(ljf._4.hex("0x"), "0x4f5da2"); - assert_eq!(ljf._4.hex(""), "4f5da2"); - assert_eq!(ljf._5.hex("0x"), "0x4f5da2"); - assert_eq!(ljf._5.hex(""), "4f5da2"); - assert_eq!(ljf._6.hex("0x"), "0x5"); - assert_eq!(ljf._6.hex(""), "5"); - assert_eq!(ljf._7.hex("0x"), "0x2"); - assert_eq!(ljf._7.hex(""), "2"); - assert_eq!(ljf._8.hex("0x"), "0x0"); - assert_eq!(ljf._8.hex(""), "0"); - assert_eq!(ljf._9.hex("0x"), "0x4f5da2"); - assert_eq!(ljf._9.hex(""), "4f5da2"); - assert_eq!(ljf._10.hex("0x"), "0x4f5da2"); - assert_eq!(ljf._10.hex(""), "4f5da2"); - assert_eq!(ljf._11.hex("0x"), "0x4f5da2"); - assert_eq!(ljf._11.hex(""), "4f5da2"); - assert_eq!((&ljf._12).hex("0x"), "0x4c6f7665204a616e6520466f7265766572"); - assert_eq!(ljf._12.hex(""), "4c6f7665204a616e6520466f7265766572"); - assert_eq!(ljf._13.hex("0x"), "0x05"); - assert_eq!(ljf._13.hex(""), "05"); - assert_eq!(ljf._14.hex("0x"), "0x0502"); - assert_eq!(ljf._14.hex(""), "0502"); - assert_eq!(ljf._15.hex("0x"), "0x050200"); - assert_eq!(ljf._15.hex(""), "050200"); - assert_eq!(ljf._16.hex("0x"), "0x05020001"); - assert_eq!(ljf._16.hex(""), "05020001"); - assert_eq!(ljf._17.hex("0x"), "0x0502000103"); - assert_eq!(ljf._17.hex(""), "0502000103"); - assert_eq!(ljf._18.hex("0x"), "0x050200010301"); - assert_eq!(ljf._18.hex(""), "050200010301"); - assert_eq!(ljf._19.hex("0x"), "0x05020001030104"); - assert_eq!(ljf._19.hex(""), "05020001030104"); - - macro_rules! assert_hex_array { - ($ljf:expr, $(($field:ident, $len:expr)),+,) => { - $( - let hex = "0".repeat($len * 2); - - assert_eq!($ljf.$field.hex("0x"), format!("0x{hex}")); - assert_eq!($ljf.$field.hex(""), hex); - )+ - }; - } - - assert_hex_array! { - ljf, - (_20, 8), - (_21, 9), - (_22, 10), - (_23, 11), - (_24, 12), - (_25, 13), - (_26, 14), - (_27, 15), - (_28, 16), - (_29, 17), - (_30, 18), - (_31, 19), - (_32, 20), - (_33, 21), - (_34, 22), - (_35, 23), - (_36, 24), - (_37, 25), - (_38, 26), - (_39, 27), - (_40, 28), - (_41, 29), - (_42, 30), - (_43, 31), - (_44, 32), - (_45, 33), - (_46, 34), - (_47, 35), - (_48, 36), - (_49, 37), - (_50, 38), - (_51, 39), - (_52, 40), - (_53, 41), - (_54, 42), - (_55, 43), - (_56, 44), - (_57, 45), - (_58, 46), - (_59, 47), - (_60, 48), - (_61, 49), - (_62, 50), - (_63, 51), - (_64, 52), - (_65, 53), - (_66, 54), - (_67, 55), - (_68, 56), - (_69, 57), - (_70, 58), - (_71, 59), - (_72, 60), - (_73, 61), - (_74, 62), - (_75, 63), - (_76, 64), - (_77, 128), - (_78, 256), - (_79, 512), - (_80, 1024), - (_81, 2048), - (_82, 4096), - (_83, 8192), - (_84, 16384), - (_85, 32768), - (_86, 65536), - } -} - -#[test] -fn slice2array_should_work() { - assert_eq!(slice2array::<_, 8>(&[0; 8]), Ok([0; 8])); -} - -#[test] -fn prefix_with_should_work() { - assert_eq!(prefix_with::<_, _, 4>([1, 2, 3, 4], 0), [1, 2, 3, 4]); - assert_eq!(prefix_with::<_, _, 4>([1, 2, 3, 4, 5, 6], 0), [1, 2, 3, 4]); - assert_eq!(prefix_with::<_, _, 5>([1, 2, 3], 0), [0, 0, 1, 2, 3]); -} - -#[test] -fn suffix_with_should_work() { - assert_eq!(suffix_with::<_, _, 4>([1, 2, 3, 4], 0), [1, 2, 3, 4]); - assert_eq!(suffix_with::<_, _, 4>([1, 2, 3, 4, 5, 6], 0), [1, 2, 3, 4]); - assert_eq!(suffix_with::<_, _, 5>([1, 2, 3], 0), [1, 2, 3, 0, 0]); -} - -#[test] -fn slice_n_into_should_work() { - assert_eq!(slice_n_into::(b"Love Jane Forever"), Ok(Ljfn(*b"Love Jane Forever"))); -} - -#[test] -fn slice_n_into_unchecked_should_work() { - assert_eq!( - slice_n_into_unchecked::(b"Love Jane Forever"), - Ljfn(*b"Love Jane Forever") - ); -} - -#[test] -fn vec2array_should_work() { - assert_eq!(vec2array::<_, 8>(bytes![0; 8]), Ok([0; 8])); -} - -#[test] -fn vec_n_into_should_work() { - assert_eq!( - vec_n_into::(b"Love Jane Forever".to_vec()), - Ok(Ljfn(*b"Love Jane Forever")) - ); -} - -#[test] -fn vec_n_into_unchecked_should_work() { - assert_eq!( - vec_n_into_unchecked::(b"Love Jane Forever".to_vec()), - Ljfn(*b"Love Jane Forever") - ); -} - -#[test] -fn bytes2hex_should_work() { - assert_eq!( - bytes2hex("0x", b"Love Jane Forever"), - String::from("0x4c6f7665204a616e6520466f7265766572") - ); - assert_eq!( - bytes2hex("", b"Love Jane Forever"), - String::from("4c6f7665204a616e6520466f7265766572") - ); -} - -#[test] -fn hex_bytes2hex_str_should_work() { - assert_eq!( - hex_bytes2hex_str(b"0x4c6f7665204a616e6520466f7265766572"), - Ok("0x4c6f7665204a616e6520466f7265766572"), - ); - assert_eq!( - hex_bytes2hex_str(b"4c6f7665204a616e6520466f7265766572"), - Ok("4c6f7665204a616e6520466f7265766572"), - ); - - assert_eq!( - hex_bytes2hex_str(b"4c6f766 5204a616e6520466f7265766572"), - Err(Error::InvalidCharacter { character: ' ', index: 7 }), - ); - assert_eq!( - hex_bytes2hex_str(b"4c6f766520 4a616e6520466f7265766572"), - Err(Error::InvalidCharacter { character: ' ', index: 10 }), - ); -} - -#[test] -fn hex_bytes2hex_str_unchecked_should_work() { - assert_eq!( - hex_bytes2hex_str_unchecked(b"0x4c6f7665204a616e6520466f7265766572"), - "0x4c6f7665204a616e6520466f7265766572", - ); - assert_eq!( - hex_bytes2hex_str_unchecked(b"4c6f7665204a616e6520466f7265766572"), - "4c6f7665204a616e6520466f7265766572", - ); -} - -#[test] -fn hex2array_should_work() { - assert_eq!(hex2array("0x4c6f7665204a616e6520466f7265766572"), Ok(*b"Love Jane Forever")); - assert_eq!( - hex2array("0x4c6f7665204a616e6520466f7265766572".as_bytes()), - Ok(*b"Love Jane Forever") - ); - assert_eq!(hex2array("4c6f7665204a616e6520466f7265766572"), Ok(*b"Love Jane Forever")); - assert_eq!( - hex2array("4c6f7665204a616e6520466f7265766572".as_bytes()), - Ok(*b"Love Jane Forever") - ); -} - -#[test] -fn hex2bytes_should_work() { - assert_eq!( - hex2bytes("0x4c6f7665204a616e6520466f7265766572").unwrap().into_vec(), - b"Love Jane Forever".to_vec() - ); - assert_eq!( - hex2bytes("0x4c6f7665204a616e6520466f7265766572".as_bytes()).unwrap().into_vec(), - b"Love Jane Forever".to_vec() - ); - assert_eq!( - hex2bytes("4c6f7665204a616e6520466f7265766572").unwrap().into_vec(), - b"Love Jane Forever".to_vec() - ); - assert_eq!( - hex2bytes("4c6f7665204a616e6520466f7265766572".as_bytes()).unwrap().into_vec(), - b"Love Jane Forever".to_vec() - ); - - assert_eq!(hex2bytes("我爱你"), Err(Error::InvalidLength)); - assert_eq!(hex2bytes("0x我爱你"), Err(Error::InvalidLength)); - - assert_eq!(hex2bytes("我爱你 "), Err(Error::InvalidCharacter { character: 'æ', index: 0 })); - assert_eq!(hex2bytes(" 我爱你"), Err(Error::InvalidCharacter { character: ' ', index: 0 })); -} - -#[test] -fn hex2bytes_unchecked_should_work() { - assert_eq!( - hex2bytes_unchecked("0x4c6f7665204a616e6520466f7265766572").into_vec(), - *b"Love Jane Forever" - ); - assert_eq!( - hex2bytes_unchecked("0x4c6f7665204a616e6520466f7265766572".as_bytes()).into_vec(), - *b"Love Jane Forever" - ); - assert_eq!( - hex2bytes_unchecked("4c6f7665204a616e6520466f7265766572").into_vec(), - *b"Love Jane Forever" - ); - assert_eq!( - hex2bytes_unchecked("4c6f7665204a616e6520466f7265766572".as_bytes()).into_vec(), - *b"Love Jane Forever" - ); -} - -#[test] -fn hex2slice_should_work() { - { - let mut bytes = [0; 17]; - - assert_eq!( - hex2slice("0x4c6f7665204a616e6520466f7265766572", &mut bytes), - Ok(b"Love Jane Forever".as_slice()) - ); - assert_eq!(bytes, *b"Love Jane Forever"); - } - { - let mut bytes = [0; 17]; - - assert_eq!( - hex2slice("0x4c6f7665204a616e6520466f7265766572".as_bytes(), &mut bytes), - Ok(b"Love Jane Forever".as_slice()) - ); - assert_eq!(bytes, *b"Love Jane Forever"); - } - - { - let mut bytes = [0; 17]; - - assert_eq!( - hex2slice("4c6f7665204a616e6520466f7265766572", &mut bytes), - Ok(b"Love Jane Forever".as_slice()) - ); - assert_eq!(bytes, *b"Love Jane Forever"); - } - { - let mut bytes = [0; 17]; - - assert_eq!( - hex2slice("4c6f7665204a616e6520466f7265766572".as_bytes(), &mut bytes), - Ok(b"Love Jane Forever".as_slice()) - ); - assert_eq!(bytes, *b"Love Jane Forever"); - } - - assert_eq!(hex2slice("0", &mut []), Err(Error::InvalidLength)); - assert_eq!(hex2slice("0x0", &mut []), Err(Error::InvalidLength)); - - assert_eq!(hex2slice("00", &mut []), Err(Error::MismatchedLength { expect: 1 })); - assert_eq!(hex2slice("0x0001", &mut []), Err(Error::MismatchedLength { expect: 2 })); - - assert_eq!( - hex2slice("fg", &mut [0]), - Err(Error::InvalidCharacter { character: 'g', index: 1 }) - ); - assert_eq!( - hex2slice("0xyz", &mut [0]), - Err(Error::InvalidCharacter { character: 'y', index: 0 }) - ); -} - -#[test] -fn hex2slice_unchecked_should_work() { - { - let mut bytes = [0; 17]; - - assert_eq!( - hex2slice_unchecked("0x4c6f7665204a616e6520466f7265766572", &mut bytes), - b"Love Jane Forever" - ); - assert_eq!(bytes, *b"Love Jane Forever"); - } - { - let mut bytes = [0; 17]; - - assert_eq!( - hex2slice_unchecked("0x4c6f7665204a616e6520466f7265766572".as_bytes(), &mut bytes), - b"Love Jane Forever" - ); - assert_eq!(bytes, *b"Love Jane Forever"); - } - - { - let mut bytes = [0; 17]; - - assert_eq!( - hex2slice_unchecked("4c6f7665204a616e6520466f7265766572", &mut bytes), - b"Love Jane Forever" - ); - assert_eq!(bytes, *b"Love Jane Forever"); - } - { - let mut bytes = [0; 17]; - - assert_eq!( - hex2slice_unchecked("4c6f7665204a616e6520466f7265766572".as_bytes(), &mut bytes), - b"Love Jane Forever" - ); - assert_eq!(bytes, *b"Love Jane Forever"); - } -} - -#[test] -fn hex_into_should_work() { - assert_eq!( - hex_into::<_, Ljf>("0x4c6f7665204a616e6520466f7265766572"), - Ok(Ljf(b"Love Jane Forever".to_vec())) - ); - assert_eq!( - hex_into::<_, Ljf>("0x4c6f7665204a616e6520466f7265766572".as_bytes()), - Ok(Ljf(b"Love Jane Forever".to_vec())) - ); - assert_eq!( - hex_into::<_, Ljf>("4c6f7665204a616e6520466f7265766572"), - Ok(Ljf(b"Love Jane Forever".to_vec())) - ); - assert_eq!( - hex_into::<_, Ljf>("4c6f7665204a616e6520466f7265766572".as_bytes()), - Ok(Ljf(b"Love Jane Forever".to_vec())) - ); -} - -#[test] -fn hex_n_into_should_work() { - assert_eq!( - hex_n_into::<_, Ljfn, 17>("0x4c6f7665204a616e6520466f7265766572"), - Ok(Ljfn(*b"Love Jane Forever")) - ); - assert_eq!( - hex_n_into::<_, Ljfn, 17>("0x4c6f7665204a616e6520466f7265766572".as_bytes()), - Ok(Ljfn(*b"Love Jane Forever")) - ); - assert_eq!( - hex_n_into::<_, Ljfn, 17>("4c6f7665204a616e6520466f7265766572"), - Ok(Ljfn(*b"Love Jane Forever")) - ); - assert_eq!( - hex_n_into::<_, Ljfn, 17>("4c6f7665204a616e6520466f7265766572".as_bytes()), - Ok(Ljfn(*b"Love Jane Forever")) - ); -} - -#[test] -fn hex_into_unchecked_should_work() { - assert_eq!( - hex_into_unchecked::<_, Ljf>("0x4c6f7665204a616e6520466f7265766572"), - Ljf(b"Love Jane Forever".to_vec()) - ); - assert_eq!( - hex_into_unchecked::<_, Ljf>("0x4c6f7665204a616e6520466f7265766572".as_bytes()), - Ljf(b"Love Jane Forever".to_vec()) - ); - assert_eq!( - hex_into_unchecked::<_, Ljf>("4c6f7665204a616e6520466f7265766572"), - Ljf(b"Love Jane Forever".to_vec()) - ); - assert_eq!( - hex_into_unchecked::<_, Ljf>("4c6f7665204a616e6520466f7265766572".as_bytes()), - Ljf(b"Love Jane Forever".to_vec()) - ); -} - -#[test] -fn hex_n_into_unchecked_should_work() { - assert_eq!( - hex_n_into_unchecked::<_, Ljfn, 17>("0x4c6f7665204a616e6520466f7265766572"), - Ljfn(*b"Love Jane Forever") - ); - assert_eq!( - hex_n_into_unchecked::<_, Ljfn, 17>("0x4c6f7665204a616e6520466f7265766572".as_bytes()), - Ljfn(*b"Love Jane Forever") - ); - assert_eq!( - hex_n_into_unchecked::<_, Ljfn, 17>("4c6f7665204a616e6520466f7265766572"), - Ljfn(*b"Love Jane Forever") - ); - assert_eq!( - hex_n_into_unchecked::<_, Ljfn, 17>("4c6f7665204a616e6520466f7265766572".as_bytes()), - Ljfn(*b"Love Jane Forever") - ); -} - -#[cfg(feature = "serde")] -#[test] -fn hex_deserialize_into_should_work() { - #[derive(Debug, PartialEq, Deserialize)] - struct WrappedLjf { - #[serde(deserialize_with = "hex_deserialize_into")] - ljf: Ljf, - } - - assert_eq!( - serde_json::from_str::( - r#"{ - "ljf": "0x4c6f7665204a616e6520466f7265766572" - }"# - ) - .unwrap(), - WrappedLjf { ljf: Ljf(b"Love Jane Forever".to_vec()) } - ); - assert_eq!( - serde_json::from_str::( - r#"{ - "ljf": "4c6f7665204a616e6520466f7265766572" - }"# - ) - .unwrap(), - WrappedLjf { ljf: Ljf(b"Love Jane Forever".to_vec()) } - ); -} - -#[cfg(feature = "serde")] -#[test] -fn hex_deserialize_n_into_should_work() { - #[derive(Debug, PartialEq, Deserialize)] - struct WrappedLjf { - #[serde(deserialize_with = "hex_deserialize_n_into")] - ljf: Ljfn, - } - - assert_eq!( - serde_json::from_str::( - r#"{ - "ljf": "0x4c6f7665204a616e6520466f7265766572" - }"# - ) - .unwrap(), - WrappedLjf { ljf: Ljfn(*b"Love Jane Forever") } - ); - assert_eq!( - serde_json::from_str::( - r#"{ - "ljf": "4c6f7665204a616e6520466f7265766572" - }"# - ) - .unwrap(), - WrappedLjf { ljf: Ljfn(*b"Love Jane Forever") } - ); -} - -#[cfg(feature = "serde")] -#[test] -fn de_try_from_hex_should_work() { - let ljf = LjfPredefined::default(); - let mut json = String::from( - r#"{ - "_0": "0x5", - "_1": "2", - "_2": "0x0", - "_3": "0x4f5da2", - "_4": "4f5da2", - "_5": "0x4f5da2", - "_6": "5", - "_7": "0x2", - "_8": "0", - "_9": "0x4f5da2", - "_10": "4f5da2", - "_11": "0x4f5da2", - "_12": "0x4c6f7665204a616e6520466f7265766572", - "_13": "0x05", - "_14": "0x0502", - "_15": "0x050200", - "_16": "0x05020001", - "_17": "0x0502000103", - "_18": "0x050200010301", - "_19": "0x05020001030104","#, - ); - - (20..=76).for_each(|i| { - json.push_str(&format!(r#""_{i}":"0x{}","#, "0".repeat((i - 12) * 2))); - }); - json.push_str(&format!(r#""_77":"{}","#, "0".repeat(128 * 2))); - json.push_str(&format!(r#""_78":"{}","#, "0".repeat(256 * 2))); - json.push_str(&format!(r#""_79":"{}","#, "0".repeat(512 * 2))); - json.push_str(&format!(r#""_80":"{}","#, "0".repeat(1024 * 2))); - json.push_str(&format!(r#""_81":"{}","#, "0".repeat(2048 * 2))); - json.push_str(&format!(r#""_82":"{}","#, "0".repeat(4096 * 2))); - json.push_str(&format!(r#""_83":"{}","#, "0".repeat(8192 * 2))); - json.push_str(&format!(r#""_84":"{}","#, "0".repeat(16384 * 2))); - json.push_str(&format!(r#""_85":"{}","#, "0".repeat(32768 * 2))); - json.push_str(&format!(r#""_86":"{}""#, "0".repeat(65536 * 2))); - json.push('}'); - - assert_eq!(ljf, serde_json::from_str::(&json).unwrap()); - - let json = json.split(",").map(|l| l.replacen("0x", "", 1)).collect::>().join(","); - - assert_eq!(ljf, serde_json::from_str::(&json).unwrap()); -} - -#[cfg(feature = "serde")] -#[test] -fn ser_hex_should_work() { - let ljf = LjfPredefined::default(); - let json = serde_json::to_string(&ljf).unwrap(); - - assert_eq!(ljf, serde_json::from_str::(&json).unwrap()); -} - -#[cfg(feature = "serde")] -#[test] -fn ser_hex_without_prefix_should_work() { - #[derive(Debug, PartialEq, Serialize)] - struct Ljf { - #[serde(serialize_with = "ser_hex_without_prefix")] - _0: Vec, - } - - let ljf = Ljf { _0: b"Love Jane Forever".to_vec() }; - - assert_eq!( - serde_json::to_string(&ljf).unwrap(), - r#"{"_0":"4c6f7665204a616e6520466f7265766572"}"# - ); -} - -#[test] -fn random_input_should_work() { - const DATA_1: &[u8] = include_bytes!("lib.rs"); - const DATA_2: &[u8] = include_bytes!("test.rs"); - - let data = [DATA_1, DATA_2].concat(); - - [8, 16, 32, 64, 128, 256, 512, 1024].into_iter().for_each(|chunks_size| { - let mut data_pieces = Vec::new(); - - data.chunks(chunks_size).enumerate().for_each(|(i, chunk)| { - data_pieces.push(bytes2hex(if i % 2 == 0 { "0x" } else { "" }, chunk)) - }); - - let data_pieces = data_pieces - .into_iter() - .map(|piece| match strip_0x(piece.as_bytes()).len() { - 8 => hex2array_unchecked::<_, 4>(&piece).to_vec(), - 32 => hex2array_unchecked::<_, 16>(&piece).to_vec(), - 64 => hex2array_unchecked::<_, 32>(&piece).to_vec(), - 128 => hex2array_unchecked::<_, 64>(&piece).to_vec(), - 256 => hex2array_unchecked::<_, 128>(&piece).to_vec(), - 512 => hex2array_unchecked::<_, 256>(&piece).to_vec(), - 1024 => hex2array_unchecked::<_, 512>(&piece).to_vec(), - 2048 => hex2array_unchecked::<_, 1024>(&piece).to_vec(), - _ => hex2bytes_unchecked(&piece).into_vec(), - }) - .collect::>(); - - assert_eq!(data_pieces.concat(), data) - }); -}