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), diff --git a/Cargo.toml b/Cargo.toml index 625174b..39758f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,15 +17,16 @@ 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"] - +serde = ["dep:serde", "alloc"] +[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 188d1f6..be8ea59 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -313,18 +313,25 @@ mod tests { }; use proptest::proptest; + #[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); assert_eq!(&dst[..], s.as_bytes()); } + #[cfg(feature = "alloc")] proptest! { #[test] fn test_decode_fallback(ref s in ".+") { @@ -332,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 ff56836..5095778 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -6,12 +6,15 @@ 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"; 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,16 +31,46 @@ fn hex_string_custom_case(src: &[u8], upper_case: bool) -> String { } } -#[cfg(any(feature = "alloc", test))] +#[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(any(feature = "alloc", test))] +#[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 b34ece3..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; @@ -135,6 +134,9 @@ mod tests { 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,22 +160,29 @@ mod tests { { 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)); } } + #[cfg(feature = "alloc")] proptest! { #[test] fn test_hex_encode(ref s in ".*") { @@ -181,13 +190,23 @@ mod tests { } } + #[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(); @@ -198,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(); @@ -206,6 +228,7 @@ mod tests { } } + #[cfg(feature = "alloc")] proptest! { #[test] fn test_hex_decode(ref s in ".+") { @@ -213,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); 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