Skip to content

Commit d747df4

Browse files
committed
Add support for heapless feature to avoid alloc in no_std case
1 parent 98b1ee3 commit d747df4

7 files changed

Lines changed: 359 additions & 2 deletions

File tree

.github/workflows/build.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ jobs:
5555
command: test
5656
args: --verbose --no-default-features --features alloc
5757

58+
- name: Test [heapless]
59+
uses: actions-rs/cargo@v1
60+
with:
61+
command: test
62+
args: --verbose --no-default-features --features heapless
63+
5864
- name: Test [serde]
5965
uses: actions-rs/cargo@v1
6066
with:
@@ -89,6 +95,28 @@ jobs:
8995
command: build
9096
args: --no-default-features --features alloc --target thumbv6m-none-eabi
9197

98+
build-no-alloc:
99+
name: Build no_std
100+
runs-on: ubuntu-latest
101+
102+
steps:
103+
- name: Checkout Sources
104+
uses: actions/checkout@v2
105+
106+
- name: Install Rust Toolchain
107+
uses: actions-rs/toolchain@v1
108+
with:
109+
profile: minimal
110+
toolchain: stable
111+
override: true
112+
target: thumbv6m-none-eabi
113+
114+
- name: Build
115+
uses: actions-rs/cargo@v1
116+
with:
117+
command: build
118+
args: --no-default-features --features heapless --target thumbv6m-none-eabi
119+
92120
# coverage:
93121
# name: Code Coverage
94122
# runs-on: ubuntu-latest

.gitlab-ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ test:
3030
- cargo test --no-default-features --features alloc
3131
- cargo test --no-default-features --features std
3232
- cargo test --no-default-features --features serde
33+
- cargo test --no-default-features --features heapless
3334
cache:
3435
key: "$CI_COMMIT_REF_SLUG:$RUST_VERSION"
3536
paths:

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,15 @@ maintenance = { status = "actively-developed" }
1919
default = ["std"]
2020
alloc = []
2121
std = ["alloc"]
22+
serde = ["dep:serde", "heapless?/serde"]
23+
heapless = ["dep:heapless"]
2224

2325
[[bench]]
2426
name = "hex"
2527
harness = false
2628

2729
[dependencies]
30+
heapless = { version = "0.7", optional = true }
2831
serde = { version = "1.0", default-features = false, optional = true }
2932

3033
[dev-dependencies]
@@ -36,6 +39,7 @@ version-sync = "0.9.5"
3639
pretty_assertions = "1.4.1"
3740
serde = { version = "1.0.215", features = ["derive"] }
3841
serde_json = "1.0.133"
42+
serde-json-core = "0.6"
3943

4044
[package.metadata.docs.rs]
4145
all-features = true

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ hex = { version = "0.4", default-features = false }
6565
Enabled by default. Add support for Rust's libstd types.
6666
- `alloc`:
6767
Enabled by default. Add support for alloc types (e.g. `String`) in `no_std` environment.
68+
- `heapless`:
69+
Disabled by default. Add support for heapless types (e.g. `String`) in `no_std` environment with allocation on the stack.
6870
- `serde`:
6971
Disabled by default. Add support for `serde` de/serializing library.
7072
See the `serde` module documentation for usage.

src/lib.rs

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ pub mod serde;
4949
pub use crate::serde::deserialize;
5050
#[cfg(all(feature = "alloc", feature = "serde"))]
5151
pub use crate::serde::{serialize, serialize_upper};
52+
#[cfg(all(feature = "heapless", feature = "serde"))]
53+
pub use crate::serde::{serialize_heapless, serialize_upper_heapless};
5254

5355
/// Encoding values as hex string.
5456
///
@@ -196,6 +198,23 @@ impl FromHex for Vec<u8> {
196198
}
197199
}
198200

201+
#[cfg(feature = "heapless")]
202+
impl<const N: usize> FromHex for heapless::Vec<u8, N> {
203+
type Error = FromHexError;
204+
205+
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
206+
let hex = hex.as_ref();
207+
if hex.len() % 2 != 0 {
208+
return Err(FromHexError::OddLength);
209+
}
210+
211+
hex.chunks(2)
212+
.enumerate()
213+
.map(|(i, pair)| Ok(val(pair[0], 2 * i)? << 4 | val(pair[1], 2 * i + 1)?))
214+
.collect()
215+
}
216+
}
217+
199218
impl<const N: usize> FromHex for [u8; N] {
200219
type Error = FromHexError;
201220

@@ -226,6 +245,42 @@ pub fn encode<T: AsRef<[u8]>>(data: T) -> String {
226245
data.encode_hex()
227246
}
228247

248+
/// Encodes `data` as hex string using lowercase characters.
249+
///
250+
/// Lowercase characters are used (e.g. `f9b4ca`). The resulting string's
251+
/// length is always even, each byte in `data` is always encoded using two hex
252+
/// digits. Thus, the resulting string contains exactly twice as many bytes as
253+
/// the input data.
254+
///
255+
/// Because it use heapless::String, this function need type annotation.
256+
/// The explicit type String, has to be able to hold at least `data.len() * 2` bytes,
257+
/// otherwise this function will return an error.
258+
///
259+
/// # Example
260+
///
261+
/// ```
262+
/// use heapless::{String, Vec};
263+
///
264+
/// let hex_str: String<24> = hex::encode_heapless("Hello world!").expect("encode error");
265+
/// assert_eq!(hex_str, "48656c6c6f20776f726c6421");
266+
/// let hex_str: String<10> = hex::encode_heapless(Vec::<u8, 5>::from_slice(&[1, 2, 3, 15, 16]).unwrap()).expect("encode error");
267+
/// assert_eq!(hex_str, "0102030f10");
268+
/// let hex_str: String<30> = hex::encode_heapless("can be longer").expect("encode error");
269+
/// assert_eq!(hex_str, "63616e206265206c6f6e676572");
270+
/// let res: Result<String<29>,_> = hex::encode_heapless("but not shorter");
271+
/// assert!(res.is_err());
272+
/// ```
273+
#[must_use]
274+
#[cfg(feature = "heapless")]
275+
pub fn encode_heapless<T: AsRef<[u8]>, const N: usize>(
276+
data: T,
277+
) -> Result<heapless::String<N>, FromHexError> {
278+
if data.as_ref().len() * 2 > N {
279+
return Err(FromHexError::InvalidStringLength);
280+
}
281+
Ok(data.encode_hex())
282+
}
283+
229284
/// Encodes `data` as hex string using uppercase characters.
230285
///
231286
/// Apart from the characters' casing, this works exactly like `encode()`.
@@ -242,6 +297,34 @@ pub fn encode_upper<T: AsRef<[u8]>>(data: T) -> String {
242297
data.encode_hex_upper()
243298
}
244299

300+
/// Encodes `data` as hex string using uppercase characters.
301+
///
302+
/// Apart from the characters' casing, this works exactly like `encode_heapless()`.
303+
///
304+
/// # Example
305+
///
306+
/// ```
307+
/// use heapless::{String, Vec};
308+
///
309+
/// let hex_str: String<24> = hex::encode_upper_heapless("Hello world!").expect("encode error");
310+
/// assert_eq!(hex_str, "48656C6C6F20776F726C6421");
311+
/// let hex_str: String<10> = hex::encode_upper_heapless(Vec::<u8, 5>::from_slice(&[1, 2, 3, 15, 16]).unwrap()).expect("encode error");
312+
/// assert_eq!(hex_str, "0102030F10");
313+
/// let hex_str: String<30> = hex::encode_upper_heapless("can be longer").expect("encode error");
314+
/// assert_eq!(hex_str, "63616E206265206C6F6E676572");
315+
/// let res: Result<String<29>,_> = hex::encode_upper_heapless("but not shorter");
316+
/// assert!(res.is_err());
317+
/// ```
318+
#[cfg(feature = "heapless")]
319+
pub fn encode_upper_heapless<T: AsRef<[u8]>, const N: usize>(
320+
data: T,
321+
) -> Result<heapless::String<N>, FromHexError> {
322+
if data.as_ref().len() * 2 > N {
323+
return Err(FromHexError::InvalidStringLength);
324+
}
325+
Ok(data.encode_hex_upper())
326+
}
327+
245328
/// Decodes a hex string into raw bytes.
246329
///
247330
/// Both, upper and lower case characters are valid in the input string and can
@@ -263,6 +346,32 @@ pub fn decode<T: AsRef<[u8]>>(data: T) -> Result<Vec<u8>, FromHexError> {
263346
FromHex::from_hex(data)
264347
}
265348

349+
/// Decodes a hex string into raw bytes.
350+
///
351+
/// Both, upper and lower case characters are valid in the input string and can
352+
/// even be mixed (e.g. `f9b4ca`, `F9B4CA` and `f9B4Ca` are all valid strings).
353+
///
354+
/// # Example
355+
///
356+
/// ```
357+
/// use heapless::Vec;
358+
///
359+
/// let res: Vec<u8,12> = hex::decode_heapless("48656c6c6f20776f726c6421").expect("decode error");
360+
/// let expected: Vec<u8, 12> = Vec::from_slice(b"Hello world!").unwrap();
361+
/// assert_eq!(res, expected);
362+
///
363+
/// let res: Result<Vec<u8,2>,_> = hex::decode_heapless("123");
364+
/// assert_eq!(res, Err(hex::FromHexError::OddLength));
365+
/// let res: Result<Vec<u8,2>,_> = hex::decode_heapless("foo");
366+
/// assert!(res.is_err());
367+
/// ```
368+
#[cfg(feature = "heapless")]
369+
pub fn decode_heapless<T: AsRef<[u8]>, const N: usize>(
370+
data: T,
371+
) -> Result<heapless::Vec<u8, N>, FromHexError> {
372+
FromHex::from_hex(data)
373+
}
374+
266375
/// Decode a hex string into a mutable bytes slice.
267376
///
268377
/// Both, upper and lower case characters are valid in the input string and can
@@ -423,6 +532,15 @@ mod test {
423532
assert_eq!(encode("foobar"), "666f6f626172");
424533
}
425534

535+
#[test]
536+
#[cfg(feature = "heapless")]
537+
fn test_encode_heapless() {
538+
assert_eq!(
539+
encode_heapless::<&str, 12>("foobar").expect("encode error"),
540+
"666f6f626172"
541+
);
542+
}
543+
426544
#[test]
427545
#[cfg(feature = "alloc")]
428546
fn test_decode() {
@@ -432,20 +550,55 @@ mod test {
432550
);
433551
}
434552

553+
#[test]
554+
#[cfg(feature = "heapless")]
555+
fn test_decode_heapless() {
556+
assert_eq!(
557+
decode_heapless::<&str, 12>("666f6f626172"),
558+
Ok(heapless::Vec::from_slice(b"foobar").unwrap())
559+
);
560+
}
561+
435562
#[test]
436563
#[cfg(feature = "alloc")]
437564
pub fn test_from_hex_okay_str() {
438565
assert_eq!(Vec::from_hex("666f6f626172").unwrap(), b"foobar");
439566
assert_eq!(Vec::from_hex("666F6F626172").unwrap(), b"foobar");
440567
}
441568

569+
#[test]
570+
#[cfg(feature = "heapless")]
571+
pub fn test_from_hex_okay_str_heapless() {
572+
assert_eq!(
573+
<heapless::Vec<u8, 12> as FromHex>::from_hex("666f6f626172").unwrap(),
574+
b"foobar"
575+
);
576+
assert_eq!(
577+
<heapless::Vec<u8, 12> as FromHex>::from_hex("666F6F626172").unwrap(),
578+
b"foobar"
579+
);
580+
}
581+
442582
#[test]
443583
#[cfg(feature = "alloc")]
444584
pub fn test_from_hex_okay_bytes() {
445585
assert_eq!(Vec::from_hex(b"666f6f626172").unwrap(), b"foobar");
446586
assert_eq!(Vec::from_hex(b"666F6F626172").unwrap(), b"foobar");
447587
}
448588

589+
#[test]
590+
#[cfg(feature = "heapless")]
591+
pub fn test_from_hex_okay_bytes_heapless() {
592+
assert_eq!(
593+
<heapless::Vec<u8, 12> as FromHex>::from_hex(b"666f6f626172").unwrap(),
594+
b"foobar"
595+
);
596+
assert_eq!(
597+
<heapless::Vec<u8, 12> as FromHex>::from_hex(b"666F6F626172").unwrap(),
598+
b"foobar"
599+
);
600+
}
601+
449602
#[test]
450603
#[cfg(feature = "alloc")]
451604
pub fn test_invalid_length() {
@@ -456,6 +609,19 @@ mod test {
456609
);
457610
}
458611

612+
#[test]
613+
#[cfg(feature = "heapless")]
614+
pub fn test_invalid_length_heapless() {
615+
assert_eq!(
616+
<heapless::Vec<u8, 1> as FromHex>::from_hex("1").unwrap_err(),
617+
FromHexError::OddLength
618+
);
619+
assert_eq!(
620+
<heapless::Vec<u8, 13> as FromHex>::from_hex("666f6f6261721").unwrap_err(),
621+
FromHexError::OddLength
622+
);
623+
}
624+
459625
#[test]
460626
#[cfg(feature = "alloc")]
461627
pub fn test_invalid_char() {
@@ -465,12 +631,30 @@ mod test {
465631
);
466632
}
467633

634+
#[test]
635+
#[cfg(feature = "heapless")]
636+
pub fn test_invalid_char_heapless() {
637+
assert_eq!(
638+
<heapless::Vec<u8, 4> as FromHex>::from_hex("66ag").unwrap_err(),
639+
FromHexError::InvalidHexCharacter { c: 'g', index: 3 }
640+
);
641+
}
642+
468643
#[test]
469644
#[cfg(feature = "alloc")]
470645
pub fn test_empty() {
471646
assert_eq!(Vec::from_hex("").unwrap(), b"");
472647
}
473648

649+
#[test]
650+
#[cfg(feature = "heapless")]
651+
pub fn test_empty_heapless() {
652+
assert_eq!(
653+
<heapless::Vec<u8, 0> as FromHex>::from_hex("").unwrap(),
654+
b""
655+
);
656+
}
657+
474658
#[test]
475659
#[cfg(feature = "alloc")]
476660
pub fn test_from_hex_whitespace() {
@@ -480,6 +664,15 @@ mod test {
480664
);
481665
}
482666

667+
#[test]
668+
#[cfg(feature = "heapless")]
669+
pub fn test_from_hex_whitespace_heapless() {
670+
assert_eq!(
671+
<heapless::Vec<u8, 12> as FromHex>::from_hex("666f 6f62617").unwrap_err(),
672+
FromHexError::InvalidHexCharacter { c: ' ', index: 4 }
673+
);
674+
}
675+
483676
#[test]
484677
pub fn test_from_hex_array() {
485678
assert_eq!(
@@ -506,4 +699,18 @@ mod test {
506699
"666F6F626172".to_string(),
507700
);
508701
}
702+
703+
#[test]
704+
#[cfg(feature = "heapless")]
705+
fn test_to_hex_heapless() {
706+
assert_eq!(
707+
[0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72].encode_hex::<heapless::String<12>>(),
708+
heapless::String::<12>::from("666f6f626172"),
709+
);
710+
711+
assert_eq!(
712+
[0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72].encode_hex_upper::<heapless::String<12>>(),
713+
heapless::String::<12>::from("666F6F626172"),
714+
);
715+
}
509716
}

0 commit comments

Comments
 (0)