Skip to content

Commit

Permalink
Merge pull request #48 from Georges760/no-alloc
Browse files Browse the repository at this point in the history
Add support for `[no_alloc]`using heapless
  • Loading branch information
quake authored Sep 13, 2024
2 parents a75dda5 + 1dbdbb6 commit 4d654aa
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 18 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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),
Expand Down
9 changes: 5 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
15 changes: 15 additions & 0 deletions src/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,25 +313,40 @@ 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::<CAPACITY>(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 ".+") {
_test_decode_fallback(s);
}
}

#[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 (
Expand Down
39 changes: 36 additions & 3 deletions src/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<const N: usize>(src: &[u8], upper_case: bool) -> String<N> {
let mut buffer = Vec::<_, N>::new();
buffer
.resize(src.len() * 2, 0)
.expect("String<N> 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<const N: usize>(src: &[u8]) -> String<N> {
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<const N: usize>(src: &[u8]) -> String<N> {
hex_string_custom_case(src, true)
}

pub fn hex_encode_custom<'a>(
src: &[u8],
dst: &'a mut [u8],
Expand Down
43 changes: 37 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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();
Expand All @@ -158,36 +160,53 @@ 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::<CAPACITY>(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::<CAPACITY>(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 ".*") {
_test_hex_encode(s);
}
}

#[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::<CAPACITY>(s.as_bytes());

hex_decode(hex_string.as_bytes(), &mut dst).unwrap();

Expand All @@ -198,21 +217,33 @@ 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::<CAPACITY>(s.as_bytes());

hex_decode_with_case(hex_string_upper.as_bytes(), &mut dst, CheckCase::Upper).unwrap();

assert_eq!(&dst[..], s.as_bytes());
}
}

#[cfg(feature = "alloc")]
proptest! {
#[test]
fn test_hex_decode(ref s in ".+") {
_test_hex_decode(s);
}
}

#[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);
Expand Down
11 changes: 6 additions & 5 deletions src/serde.rs
Original file line number Diff line number Diff line change
@@ -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<S, T>(
data: T,
Expand Down Expand Up @@ -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>(
Expand Down Expand Up @@ -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<S, T>(data: T, serializer: S) -> Result<S::Ok, S::Error>
Expand Down

0 comments on commit 4d654aa

Please sign in to comment.