From a45bc5304b0d37e7194c7c9f96b95939e890e85a Mon Sep 17 00:00:00 2001 From: Georges Palauqui Date: Fri, 13 Sep 2024 14:08:04 +0200 Subject: [PATCH 1/4] remove String dep from alloc to allow `[no-alloc]` --- Cargo.toml | 4 ++-- src/decode.rs | 13 +++++++------ src/encode.rs | 6 +++--- src/lib.rs | 13 +++++++++++-- 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 625174b..54b8586 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,11 +17,11 @@ exclude = [ ] [dependencies] -serde = { version = "1.0", optional = true } +serde = { version = "1.0", default-features = false, optional = true } [features] default = ["std", "serde"] -std = ["alloc"] +std = ["alloc", "serde?/std"] alloc = [] serde = ["dep:serde"] diff --git a/src/decode.rs b/src/decode.rs index 188d1f6..0d32dca 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -304,15 +304,15 @@ pub fn hex_decode_fallback(src: &[u8], dst: &mut [u8]) { #[cfg(test)] mod tests { + #[cfg(feature = "alloc")] + use crate::decode::hex_decode_fallback; use crate::decode::NIL; - use crate::{ - decode::{ - hex_check_fallback, hex_check_fallback_with_case, hex_decode_fallback, CheckCase, - }, - encode::hex_string, - }; + use crate::decode::{hex_check_fallback, hex_check_fallback_with_case, CheckCase}; + #[cfg(feature = "alloc")] + use crate::encode::hex_string; use proptest::proptest; + #[cfg(feature = "alloc")] fn _test_decode_fallback(s: &String) { let len = s.as_bytes().len(); let mut dst = Vec::with_capacity(len); @@ -325,6 +325,7 @@ mod tests { assert_eq!(&dst[..], s.as_bytes()); } + #[cfg(feature = "alloc")] proptest! { #[test] fn test_decode_fallback(ref s in ".+") { diff --git a/src/encode.rs b/src/encode.rs index ff56836..6657856 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -11,7 +11,7 @@ use crate::error::Error; static TABLE_LOWER: &[u8] = b"0123456789abcdef"; static TABLE_UPPER: &[u8] = b"0123456789ABCDEF"; -#[cfg(any(feature = "alloc", test))] +#[cfg(feature = "alloc")] fn hex_string_custom_case(src: &[u8], upper_case: bool) -> String { let mut buffer = vec![0; src.len() * 2]; if upper_case { @@ -28,12 +28,12 @@ fn hex_string_custom_case(src: &[u8], upper_case: bool) -> String { } } -#[cfg(any(feature = "alloc", test))] +#[cfg(feature = "alloc")] pub fn hex_string(src: &[u8]) -> String { hex_string_custom_case(src, false) } -#[cfg(any(feature = "alloc", test))] +#[cfg(feature = "alloc")] pub fn hex_string_upper(src: &[u8]) -> String { hex_string_custom_case(src, true) } diff --git a/src/lib.rs b/src/lib.rs index b34ece3..f63bb0b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -130,9 +130,14 @@ unsafe fn avx2_support_no_cache_x86() -> bool { #[cfg(test)] mod tests { - use crate::decode::{hex_decode, hex_decode_with_case, CheckCase}; + use crate::decode::hex_decode; + #[cfg(feature = "alloc")] + use crate::decode::{hex_decode_with_case, CheckCase}; + #[cfg(feature = "alloc")] use crate::encode::{hex_encode, hex_string}; - use crate::{hex_encode_upper, hex_string_upper, vectorization_support, Vectorization}; + #[cfg(feature = "alloc")] + use crate::{hex_encode_upper, hex_string_upper}; + use crate::{vectorization_support, Vectorization}; use proptest::proptest; #[test] @@ -153,6 +158,7 @@ mod tests { assert_eq!(vector_support, Vectorization::None); } + #[cfg(feature = "alloc")] fn _test_hex_encode(s: &String) { let mut buffer = vec![0; s.as_bytes().len() * 2]; { @@ -174,6 +180,7 @@ mod tests { } } + #[cfg(feature = "alloc")] proptest! { #[test] fn test_hex_encode(ref s in ".*") { @@ -181,6 +188,7 @@ mod tests { } } + #[cfg(feature = "alloc")] fn _test_hex_decode(s: &String) { let len = s.as_bytes().len(); @@ -206,6 +214,7 @@ mod tests { } } + #[cfg(feature = "alloc")] proptest! { #[test] fn test_hex_decode(ref s in ".+") { From d987e1c59c9a57131c3312268b63e53b682be235 Mon Sep 17 00:00:00 2001 From: Georges Palauqui Date: Fri, 13 Sep 2024 14:08:04 +0200 Subject: [PATCH 2/4] impl hex_string() and hex_string_upper() using heapless::String --- Cargo.toml | 3 ++- src/decode.rs | 26 ++++++++++++++++++++------ src/encode.rs | 33 ++++++++++++++++++++++++++++++++ src/lib.rs | 52 ++++++++++++++++++++++++++++++++++++--------------- 4 files changed, 92 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 54b8586..6ad17d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,8 @@ std = ["alloc", "serde?/std"] alloc = [] serde = ["dep:serde"] - +[target.'cfg(not(feature = "alloc"))'.dependencies] +heapless = { version = "0.8" } [dev-dependencies] criterion = "0.3" diff --git a/src/decode.rs b/src/decode.rs index 0d32dca..be8ea59 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -304,21 +304,27 @@ pub fn hex_decode_fallback(src: &[u8], dst: &mut [u8]) { #[cfg(test)] mod tests { - #[cfg(feature = "alloc")] - use crate::decode::hex_decode_fallback; use crate::decode::NIL; - use crate::decode::{hex_check_fallback, hex_check_fallback_with_case, CheckCase}; - #[cfg(feature = "alloc")] - use crate::encode::hex_string; + use crate::{ + decode::{ + hex_check_fallback, hex_check_fallback_with_case, hex_decode_fallback, CheckCase, + }, + encode::hex_string, + }; use proptest::proptest; - #[cfg(feature = "alloc")] + #[cfg(not(feature = "alloc"))] + const CAPACITY: usize = 128; + fn _test_decode_fallback(s: &String) { let len = s.as_bytes().len(); let mut dst = Vec::with_capacity(len); dst.resize(len, 0); + #[cfg(feature = "alloc")] let hex_string = hex_string(s.as_bytes()); + #[cfg(not(feature = "alloc"))] + let hex_string = hex_string::(s.as_bytes()); hex_decode_fallback(hex_string.as_bytes(), &mut dst); @@ -333,6 +339,14 @@ mod tests { } } + #[cfg(not(feature = "alloc"))] + proptest! { + #[test] + fn test_decode_fallback(ref s in ".{1,16}") { + _test_decode_fallback(s); + } + } + fn _test_check_fallback_true(s: &String) { assert!(hex_check_fallback(s.as_bytes())); match ( diff --git a/src/encode.rs b/src/encode.rs index 6657856..5095778 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -6,6 +6,9 @@ use core::arch::x86_64::*; #[cfg(feature = "alloc")] use alloc::{string::String, vec}; +#[cfg(not(feature = "alloc"))] +use heapless::{String, Vec}; + use crate::error::Error; static TABLE_LOWER: &[u8] = b"0123456789abcdef"; @@ -28,16 +31,46 @@ fn hex_string_custom_case(src: &[u8], upper_case: bool) -> String { } } +#[cfg(not(feature = "alloc"))] +fn hex_string_custom_case(src: &[u8], upper_case: bool) -> String { + let mut buffer = Vec::<_, N>::new(); + buffer + .resize(src.len() * 2, 0) + .expect("String capacity too short"); + if upper_case { + hex_encode_upper(src, &mut buffer).expect("hex_string"); + } else { + hex_encode(src, &mut buffer).expect("hex_string"); + } + + if cfg!(debug_assertions) { + String::from_utf8(buffer).unwrap() + } else { + // Saftey: We just wrote valid utf8 hex string into the dst + unsafe { String::from_utf8_unchecked(buffer) } + } +} + #[cfg(feature = "alloc")] pub fn hex_string(src: &[u8]) -> String { hex_string_custom_case(src, false) } +#[cfg(not(feature = "alloc"))] +pub fn hex_string(src: &[u8]) -> String { + hex_string_custom_case(src, false) +} + #[cfg(feature = "alloc")] pub fn hex_string_upper(src: &[u8]) -> String { hex_string_custom_case(src, true) } +#[cfg(not(feature = "alloc"))] +pub fn hex_string_upper(src: &[u8]) -> String { + hex_string_custom_case(src, true) +} + pub fn hex_encode_custom<'a>( src: &[u8], dst: &'a mut [u8], diff --git a/src/lib.rs b/src/lib.rs index f63bb0b..d4acf1e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,10 +15,9 @@ pub use crate::decode::{ hex_decode_unchecked, }; pub use crate::encode::{ - hex_encode, hex_encode_fallback, hex_encode_upper, hex_encode_upper_fallback, + hex_encode, hex_encode_fallback, hex_encode_upper, hex_encode_upper_fallback, hex_string, + hex_string_upper, }; -#[cfg(feature = "alloc")] -pub use crate::encode::{hex_string, hex_string_upper}; pub use crate::error::Error; @@ -130,16 +129,14 @@ unsafe fn avx2_support_no_cache_x86() -> bool { #[cfg(test)] mod tests { - use crate::decode::hex_decode; - #[cfg(feature = "alloc")] - use crate::decode::{hex_decode_with_case, CheckCase}; - #[cfg(feature = "alloc")] + use crate::decode::{hex_decode, hex_decode_with_case, CheckCase}; use crate::encode::{hex_encode, hex_string}; - #[cfg(feature = "alloc")] - use crate::{hex_encode_upper, hex_string_upper}; - use crate::{vectorization_support, Vectorization}; + use crate::{hex_encode_upper, hex_string_upper, vectorization_support, Vectorization}; use proptest::proptest; + #[cfg(not(feature = "alloc"))] + const CAPACITY: usize = 128; + #[test] fn test_feature_detection() { let vector_support = vectorization_support(); @@ -158,25 +155,30 @@ mod tests { assert_eq!(vector_support, Vectorization::None); } - #[cfg(feature = "alloc")] fn _test_hex_encode(s: &String) { let mut buffer = vec![0; s.as_bytes().len() * 2]; { let encode = &*hex_encode(s.as_bytes(), &mut buffer).unwrap(); + #[cfg(feature = "alloc")] let hex_string = hex_string(s.as_bytes()); + #[cfg(not(feature = "alloc"))] + let hex_string = hex_string::(s.as_bytes()); assert_eq!(encode, hex::encode(s)); - assert_eq!(hex_string, hex::encode(s)); + assert_eq!(hex_string.as_str(), hex::encode(s)); } { let encode_upper = &*hex_encode_upper(s.as_bytes(), &mut buffer).unwrap(); + #[cfg(feature = "alloc")] let hex_string_upper = hex_string_upper(s.as_bytes()); + #[cfg(not(feature = "alloc"))] + let hex_string_upper = hex_string_upper::(s.as_bytes()); assert_eq!(encode_upper, hex::encode_upper(s)); - assert_eq!(hex_string_upper, hex::encode_upper(s)); + assert_eq!(hex_string_upper.as_str(), hex::encode_upper(s)); } } @@ -188,14 +190,23 @@ mod tests { } } - #[cfg(feature = "alloc")] + #[cfg(not(feature = "alloc"))] + proptest! { + #[test] + fn test_hex_encode(ref s in ".{0,16}") { + _test_hex_encode(s); + } + } + fn _test_hex_decode(s: &String) { let len = s.as_bytes().len(); - { let mut dst = Vec::with_capacity(len); dst.resize(len, 0); + #[cfg(feature = "alloc")] let hex_string = hex_string(s.as_bytes()); + #[cfg(not(feature = "alloc"))] + let hex_string = hex_string::(s.as_bytes()); hex_decode(hex_string.as_bytes(), &mut dst).unwrap(); @@ -206,7 +217,10 @@ mod tests { { let mut dst = Vec::with_capacity(len); dst.resize(len, 0); + #[cfg(feature = "alloc")] let hex_string_upper = hex_string_upper(s.as_bytes()); + #[cfg(not(feature = "alloc"))] + let hex_string_upper = hex_string_upper::(s.as_bytes()); hex_decode_with_case(hex_string_upper.as_bytes(), &mut dst, CheckCase::Upper).unwrap(); @@ -222,6 +236,14 @@ mod tests { } } + #[cfg(not(feature = "alloc"))] + proptest! { + #[test] + fn test_hex_decode(ref s in ".{1,16}") { + _test_hex_decode(s); + } + } + fn _test_hex_decode_check(s: &String, ok: bool) { let len = s.as_bytes().len(); let mut dst = Vec::with_capacity(len / 2); From c73a61d75eceb34dd33da8a36e8e375d4131ced5 Mon Sep 17 00:00:00 2001 From: Georges Palauqui Date: Fri, 13 Sep 2024 14:08:04 +0200 Subject: [PATCH 3/4] fix `serde` with `alloc` but without `std` --- Cargo.toml | 2 +- src/serde.rs | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6ad17d2..39758f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ serde = { version = "1.0", default-features = false, optional = true } default = ["std", "serde"] std = ["alloc", "serde?/std"] alloc = [] -serde = ["dep:serde"] +serde = ["dep:serde", "alloc"] [target.'cfg(not(feature = "alloc"))'.dependencies] heapless = { version = "0.8" } diff --git a/src/serde.rs b/src/serde.rs index eeea577..89e328c 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -1,15 +1,16 @@ #![warn(missing_docs)] -use std::iter::FromIterator; +use core::iter::FromIterator; mod internal { use crate::{ decode::{hex_decode_with_case, CheckCase}, encode::hex_encode_custom, }; - use alloc::borrow::Cow; + #[cfg(feature = "alloc")] + use alloc::{borrow::Cow, format, string::ToString, vec}; + use core::iter::FromIterator; use serde::{de::Error, Deserializer, Serializer}; - use std::iter::FromIterator; pub(crate) fn serialize( data: T, @@ -39,7 +40,7 @@ mod internal { hex_encode_custom(src, &mut dst[dst_start..], matches!(case, CheckCase::Upper)) .map_err(serde::ser::Error::custom)?; - serializer.serialize_str(unsafe { ::std::str::from_utf8_unchecked(&dst) }) + serializer.serialize_str(unsafe { ::core::str::from_utf8_unchecked(&dst) }) } pub(crate) fn deserialize<'de, D, T>( @@ -102,7 +103,7 @@ macro_rules! faster_hex_serde_macros { pub mod $mod_name { use crate::decode::CheckCase; use crate::serde::internal; - use std::iter::FromIterator; + use core::iter::FromIterator; /// Serializes `data` as hex string pub fn serialize(data: T, serializer: S) -> Result From 1dbdbb67e30dca7ea715dc232f37da50955dfe88 Mon Sep 17 00:00:00 2001 From: Georges Palauqui Date: Fri, 13 Sep 2024 14:08:21 +0200 Subject: [PATCH 4/4] update ChangeLog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d48e5fd..2c9ce32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ### Features * Add PartialEq derived macro to Error in order to be able to test error cases +* Allow `[no_alloc]` for faster-hex (without any feature), hex_string(_upper)() rely on heapless:String # [0.9.0](https://github.com/nervosnetwork/faster-hex/compare/v0.9.0..v0.8.2) (2023-11-22) Re create `v0.9.0`, since `v0.8.2` introduced a [break change](https://github.com/nervosnetwork/faster-hex/issues/43#issuecomment-1822551961),