Skip to content

Commit ec2e6ac

Browse files
committed
Have encode_to_slice() return a &mut str and add encode_fmt
1 parent c333cf5 commit ec2e6ac

File tree

2 files changed

+176
-85
lines changed

2 files changed

+176
-85
lines changed

src/lib.rs

Lines changed: 176 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ extern crate alloc;
4242
#[cfg(feature = "alloc")]
4343
use alloc::{string::String, vec::Vec};
4444

45-
use core::iter;
45+
use core::{fmt, iter};
4646

4747
mod error;
4848
pub use crate::error::FromHexError;
@@ -83,55 +83,82 @@ pub trait ToHex {
8383
const HEX_CHARS_LOWER: &[u8; 16] = b"0123456789abcdef";
8484
const HEX_CHARS_UPPER: &[u8; 16] = b"0123456789ABCDEF";
8585

86-
struct BytesToHexChars<'a> {
86+
#[derive(Clone)]
87+
pub struct EncodeHex<'a> {
8788
inner: ::core::slice::Iter<'a, u8>,
8889
table: &'static [u8; 16],
8990
next: Option<char>,
9091
}
9192

92-
impl<'a> BytesToHexChars<'a> {
93-
fn new(inner: &'a [u8], table: &'static [u8; 16]) -> BytesToHexChars<'a> {
94-
BytesToHexChars {
93+
impl<'a> EncodeHex<'a> {
94+
#[inline]
95+
fn new(inner: &'a [u8], table: &'static [u8; 16]) -> EncodeHex<'a> {
96+
EncodeHex {
9597
inner: inner.iter(),
9698
table,
9799
next: None,
98100
}
99101
}
100102
}
101103

102-
impl<'a> Iterator for BytesToHexChars<'a> {
104+
impl<'a> Iterator for EncodeHex<'a> {
103105
type Item = char;
104106

107+
#[inline]
105108
fn next(&mut self) -> Option<Self::Item> {
106-
match self.next.take() {
107-
Some(current) => Some(current),
108-
None => self.inner.next().map(|byte| {
109-
let current = self.table[(byte >> 4) as usize] as char;
110-
self.next = Some(self.table[(byte & 0x0F) as usize] as char);
109+
self.next.take().or_else(|| {
110+
self.inner.next().map(|&byte| {
111+
let (high, low) = byte2hex(byte, self.table);
112+
let current = high as char;
113+
self.next = Some(low as char);
111114
current
112-
}),
113-
}
115+
})
116+
})
114117
}
115118

119+
#[inline]
116120
fn size_hint(&self) -> (usize, Option<usize>) {
117121
let length = self.len();
118122
(length, Some(length))
119123
}
120124
}
121125

122-
impl<'a> iter::ExactSizeIterator for BytesToHexChars<'a> {
126+
impl<'a> iter::ExactSizeIterator for EncodeHex<'a> {
127+
#[inline]
123128
fn len(&self) -> usize {
124-
let mut length = self.inner.len() * 2;
125-
if self.next.is_some() {
126-
length += 1;
129+
self.inner.len() * 2 + self.next.is_some() as usize
130+
}
131+
}
132+
133+
impl fmt::Display for EncodeHex<'_> {
134+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
135+
if f.alternate() {
136+
f.write_str("0x")?;
137+
}
138+
if let Some(c) = self.next {
139+
fmt::Write::write_char(f, c)?;
140+
}
141+
// write to f in chunks of 64 input bytes at a time
142+
const CHUNK: usize = 64;
143+
let mut buf = [0u8; CHUNK * 2];
144+
for chunk in self.inner.as_slice().chunks(CHUNK) {
145+
let buf_chunk = &mut buf[..chunk.len() * 2];
146+
let chunk = encode_to_slice_inner(chunk, buf_chunk, self.table).unwrap();
147+
f.write_str(chunk)?;
127148
}
128-
length
149+
Ok(())
150+
}
151+
}
152+
impl fmt::Debug for EncodeHex<'_> {
153+
#[inline]
154+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
155+
fmt::Display::fmt(self, f)
129156
}
130157
}
131158

132159
#[inline]
133160
fn encode_to_iter<T: iter::FromIterator<char>>(table: &'static [u8; 16], source: &[u8]) -> T {
134-
BytesToHexChars::new(source, table).collect()
161+
EncodeHex::new(source, table).collect()
135162
}
136163

137164
impl<T: AsRef<[u8]>> ToHex for T {
@@ -172,33 +199,35 @@ pub trait FromHex: Sized {
172199
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error>;
173200
}
174201

175-
const fn val(c: u8, idx: usize) -> Result<u8, FromHexError> {
176-
match c {
177-
b'A'..=b'F' => Ok(c - b'A' + 10),
178-
b'a'..=b'f' => Ok(c - b'a' + 10),
179-
b'0'..=b'9' => Ok(c - b'0'),
180-
_ => Err(FromHexError::InvalidHexCharacter {
181-
c: c as char,
182-
index: idx,
183-
}),
184-
}
202+
#[inline]
203+
fn val(c: u8, index: usize) -> Result<u8, FromHexError> {
204+
let c = c as char;
205+
c.to_digit(16)
206+
.map(|x| x as u8)
207+
.ok_or(FromHexError::InvalidHexCharacter { c, index })
185208
}
186209

187210
#[cfg(feature = "alloc")]
188211
impl FromHex for Vec<u8> {
189212
type Error = FromHexError;
190213

191214
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
192-
let hex = hex.as_ref();
193-
if hex.len() % 2 != 0 {
194-
return Err(FromHexError::OddLength);
195-
}
215+
decode_iter(hex.as_ref())?.collect()
216+
}
217+
}
196218

197-
hex.chunks(2)
198-
.enumerate()
199-
.map(|(i, pair)| Ok(val(pair[0], 2 * i)? << 4 | val(pair[1], 2 * i + 1)?))
200-
.collect()
219+
#[inline]
220+
fn decode_iter(
221+
hex: &[u8],
222+
) -> Result<impl Iterator<Item = Result<u8, FromHexError>> + '_, FromHexError> {
223+
let it = hex.chunks_exact(2);
224+
if !it.remainder().is_empty() {
225+
return Err(FromHexError::OddLength);
201226
}
227+
228+
Ok(it
229+
.enumerate()
230+
.map(|(i, pair)| Ok(val(pair[0], i * 2)? << 4 | val(pair[1], i * 2 + 1)?)))
202231
}
203232

204233
// Helper macro to implement the trait for a few fixed sized arrays. Once Rust
@@ -309,32 +338,19 @@ pub fn decode<T: AsRef<[u8]>>(data: T) -> Result<Vec<u8>, FromHexError> {
309338
/// assert_eq!(hex::decode_to_slice("6b697769", &mut bytes as &mut [u8]), Ok(()));
310339
/// assert_eq!(&bytes, b"kiwi");
311340
/// ```
341+
#[inline]
312342
pub fn decode_to_slice<T: AsRef<[u8]>>(data: T, out: &mut [u8]) -> Result<(), FromHexError> {
313343
let data = data.as_ref();
314344

315-
if data.len() % 2 != 0 {
316-
return Err(FromHexError::OddLength);
317-
}
345+
let it = decode_iter(data)?;
318346
if data.len() / 2 != out.len() {
319347
return Err(FromHexError::InvalidStringLength);
320348
}
321349

322-
for (i, byte) in out.iter_mut().enumerate() {
323-
*byte = val(data[2 * i], 2 * i)? << 4 | val(data[2 * i + 1], 2 * i + 1)?;
324-
}
325-
326-
Ok(())
327-
}
328-
329-
// generates an iterator like this
330-
// (0, 1)
331-
// (2, 3)
332-
// (4, 5)
333-
// (6, 7)
334-
// ...
335-
#[inline]
336-
fn generate_iter(len: usize) -> impl Iterator<Item = (usize, usize)> {
337-
(0..len).step_by(2).zip((0..len).skip(1).step_by(2))
350+
out.iter_mut().zip(it).try_for_each(|(out, byte)| {
351+
*out = byte?;
352+
Ok(())
353+
})
338354
}
339355

340356
// the inverse of `val`.
@@ -347,10 +363,10 @@ const fn byte2hex(byte: u8, table: &[u8; 16]) -> (u8, u8) {
347363
(high, low)
348364
}
349365

350-
/// Encodes some bytes into a mutable slice of bytes.
366+
/// Encodes some bytes into the provided byte buffer, and then returns the buffer as a string.
351367
///
352-
/// The output buffer, has to be able to hold exactly `input.len() * 2` bytes,
353-
/// otherwise this function will return an error.
368+
/// The output buffer has to be able to hold exactly `input.len() * 2` bytes,
369+
/// or this function will return an error.
354370
///
355371
/// # Example
356372
///
@@ -359,8 +375,8 @@ const fn byte2hex(byte: u8, table: &[u8; 16]) -> (u8, u8) {
359375
/// # fn main() -> Result<(), FromHexError> {
360376
/// let mut bytes = [0u8; 4 * 2];
361377
///
362-
/// hex::encode_to_slice(b"kiwi", &mut bytes)?;
363-
/// assert_eq!(&bytes, b"6b697769");
378+
/// let string = hex::encode_to_slice(b"kiwi", &mut bytes)?;
379+
/// assert_eq!(string, "6b697769");
364380
/// # Ok(())
365381
/// # }
366382
/// ```
@@ -375,46 +391,123 @@ const fn byte2hex(byte: u8, table: &[u8; 16]) -> (u8, u8) {
375391
/// assert_eq!(hex::encode_to_slice(b"kiwi", &mut bytes), Err(FromHexError::InvalidStringLength));
376392
///
377393
/// // you can do this instead:
378-
/// hex::encode_to_slice(b"kiwi", &mut bytes[..4 * 2])?;
394+
/// let string = hex::encode_to_slice(b"kiwi", &mut bytes[..4 * 2])?;
395+
/// assert_eq!(string, "6b697769");
379396
/// assert_eq!(&bytes, b"6b697769\0\0");
380397
/// # Ok(())
381398
/// # }
382399
/// ```
383-
pub fn encode_to_slice<T: AsRef<[u8]>>(input: T, output: &mut [u8]) -> Result<(), FromHexError> {
384-
if input.as_ref().len() * 2 != output.len() {
400+
#[inline]
401+
pub fn encode_to_slice<T: AsRef<[u8]>>(
402+
input: T,
403+
output: &mut [u8],
404+
) -> Result<&mut str, FromHexError> {
405+
encode_to_slice_inner(input.as_ref(), output, HEX_CHARS_LOWER)
406+
}
407+
408+
/// Encodes some bytes into the provided byte buffer, and then returns the buffer as a string.
409+
///
410+
/// Apart from the characters' casing, this works exactly like [`encode_to_slice()`].
411+
///
412+
/// The output buffer has to be able to hold exactly `input.len() * 2` bytes,
413+
/// or this function will return an error.
414+
///
415+
/// # Example
416+
///
417+
/// ```
418+
/// # use hex::FromHexError;
419+
/// # fn main() -> Result<(), FromHexError> {
420+
/// let mut bytes = [0u8; 4 * 2];
421+
///
422+
/// let string = hex::encode_to_slice_upper(b"kiwi", &mut bytes)?;
423+
/// assert_eq!(string, "6B697769");
424+
/// # Ok(())
425+
/// # }
426+
/// ```
427+
///
428+
/// If the buffer is too large, an error is returned:
429+
///
430+
/// ```
431+
/// use hex::FromHexError;
432+
/// # fn main() -> Result<(), FromHexError> {
433+
/// let mut bytes = [0_u8; 5 * 2];
434+
///
435+
/// assert_eq!(hex::encode_to_slice_upper(b"kiwi", &mut bytes), Err(FromHexError::InvalidStringLength));
436+
///
437+
/// // you can do this instead:
438+
/// let string = hex::encode_to_slice_upper(b"kiwi", &mut bytes[..4 * 2])?;
439+
/// assert_eq!(string, "6B697769");
440+
/// assert_eq!(&bytes, b"6B697769\0\0");
441+
/// # Ok(())
442+
/// # }
443+
/// ```
444+
#[inline]
445+
pub fn encode_to_slice_upper<T: AsRef<[u8]>>(
446+
input: T,
447+
output: &mut [u8],
448+
) -> Result<&mut str, FromHexError> {
449+
encode_to_slice_inner(input.as_ref(), output, HEX_CHARS_UPPER)
450+
}
451+
452+
#[inline]
453+
fn encode_to_slice_inner<'a>(
454+
input: &[u8],
455+
output: &'a mut [u8],
456+
table: &[u8; 16],
457+
) -> Result<&'a mut str, FromHexError> {
458+
if input.len() * 2 != output.len() {
385459
return Err(FromHexError::InvalidStringLength);
386460
}
387461

388-
for (byte, (i, j)) in input
389-
.as_ref()
390-
.iter()
391-
.zip(generate_iter(input.as_ref().len() * 2))
392-
{
393-
let (high, low) = byte2hex(*byte, HEX_CHARS_LOWER);
394-
output[i] = high;
395-
output[j] = low;
462+
for (out, byte) in output.chunks_exact_mut(2).zip(input) {
463+
let (high, low) = byte2hex(*byte, table);
464+
out[0] = high;
465+
out[1] = low;
396466
}
397467

398-
Ok(())
468+
// SAFETY: output was just fully filled with only ascii characters
469+
let output = unsafe { core::str::from_utf8_unchecked_mut(output) };
470+
471+
Ok(output)
472+
}
473+
474+
/// Returns a [`Display`][fmt::Display] type that formats to the hex representation of the input.
475+
///
476+
/// # Example
477+
///
478+
/// ```
479+
/// let hex = hex::encode_fmt(b"\r\n");
480+
/// let s = format!("the data is: {}", hex);
481+
/// assert_eq!(s, "the data is: 0d0a");
482+
/// ```
483+
#[inline]
484+
pub fn encode_fmt<T: AsRef<[u8]>>(input: &T) -> EncodeHex<'_> {
485+
EncodeHex::new(input.as_ref(), HEX_CHARS_LOWER)
486+
}
487+
488+
/// Returns a [`Display`][fmt::Display] type that formats to the hex representation of the input.
489+
///
490+
/// Apart from the characters' casing, this works exactly like [`encode_fmt()`].
491+
///
492+
/// # Example
493+
///
494+
/// ```
495+
/// let hex = hex::encode_fmt_upper(b"\r\n");
496+
/// let s = format!("the data is: {}", hex);
497+
/// assert_eq!(s, "the data is: 0D0A");
498+
/// ```
499+
#[inline]
500+
pub fn encode_fmt_upper<T: AsRef<[u8]>>(input: &T) -> EncodeHex<'_> {
501+
EncodeHex::new(input.as_ref(), HEX_CHARS_UPPER)
399502
}
400503

401504
#[cfg(test)]
402505
mod test {
403506
use super::*;
404507
#[cfg(feature = "alloc")]
405508
use alloc::string::ToString;
406-
#[cfg(feature = "alloc")]
407-
use alloc::vec;
408509
use pretty_assertions::assert_eq;
409510

410-
#[test]
411-
#[cfg(feature = "alloc")]
412-
fn test_gen_iter() {
413-
let result = vec![(0, 1), (2, 3)];
414-
415-
assert_eq!(generate_iter(5).collect::<Vec<_>>(), result);
416-
}
417-
418511
#[test]
419512
fn test_encode_to_slice() {
420513
let mut output_1 = [0; 4 * 2];

tests/version-number.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
#![allow(non_fmt_panic)]
2-
31
#[test]
42
fn test_readme_deps() {
53
version_sync::assert_markdown_deps_updated!("README.md");

0 commit comments

Comments
 (0)