Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve readability of crate and add encode_to_slice #29

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
41 changes: 41 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use core::fmt;

/// The error type for decoding a hex string into `Vec<u8>` or `[u8; N]`.
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub enum FromHexError {
/// An invalid character was found. Valid ones are: `0...9`, `a...f`
/// or `A...F`.
InvalidHexCharacter { c: char, index: usize },

/// A hex string's length needs to be even, as two digits correspond to
/// one byte.
OddLength,

/// If the hex string is decoded into a fixed sized container, such as an
/// array, the hex string's length * 2 has to match the container's
/// length.
InvalidStringLength,
}

#[cfg(feature = "std")]
impl std::error::Error for FromHexError {
fn description(&self) -> &str {
match *self {
Self::InvalidHexCharacter { .. } => "invalid character",
Self::OddLength => "odd number of digits",
Self::InvalidStringLength => "invalid string length",
}
}
}

impl fmt::Display for FromHexError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Self::InvalidHexCharacter { c, index } => {
write!(f, "Invalid character '{}' at position {}", c, index)
}
Self::OddLength => write!(f, "Odd number of digits"),
Self::InvalidStringLength => write!(f, "Invalid string length"),
}
}
}
251 changes: 82 additions & 169 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,13 @@
// except according to those terms.
//! Encoding and decoding hex strings.
//!
//! For most cases, you can simply use the `decode()`, `encode()` and
//! `encode_upper()` functions. If you need a bit more control, use the traits
//! `ToHex` and `FromHex` instead.
//! For most cases, you can simply use the [`decode`], [`encode`] and
//! [`encode_upper`] functions. If you need a bit more control, use the traits
//! [`ToHex`] and [`FromHex`] instead.
//!
//! # Example
//!
//! ```
//! extern crate hex;
//!
//! fn main() {
//! let hex_string = hex::encode("Hello world!");
//! println!("{}", hex_string); // Prints '48656c6c6f20776f726c6421'
Expand All @@ -32,32 +30,12 @@ extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};

use core::fmt;
use core::iter;

/// Encoding values as hex string.
///
/// This trait is implemented for all `T` which implement `AsRef<[u8]>`. This
/// includes `String`, `str`, `Vec<u8>` and `[u8]`.
///
/// # Example
///
/// ```
/// use hex::ToHex;
///
/// println!("{}", "Hello world!".encode_hex::<String>());
/// ```
///
/// *Note*: instead of using this trait, you might want to use `encode()`.
pub trait ToHex {
/// Encode the hex strict representing `self` into the result.. Lower case
/// letters are used (e.g. `f9b4ca`)
fn encode_hex<T: iter::FromIterator<char>>(&self) -> T;

/// Encode the hex strict representing `self` into the result.. Lower case
/// letters are used (e.g. `F9B4CA`)
fn encode_hex_upper<T: iter::FromIterator<char>>(&self) -> T;
}
mod error;
pub use error::FromHexError;
mod traits;
pub use traits::{FromHex, ToHex};

const HEX_CHARS_LOWER: &[u8; 16] = b"0123456789abcdef";
const HEX_CHARS_UPPER: &[u8; 16] = b"0123456789ABCDEF";
Expand All @@ -70,7 +48,7 @@ struct BytesToHexChars<'a> {

impl<'a> BytesToHexChars<'a> {
fn new(inner: &'a [u8], table: &'static [u8; 16]) -> BytesToHexChars<'a> {
BytesToHexChars {
Self {
inner: inner.iter(),
table,
next: None,
Expand Down Expand Up @@ -112,87 +90,6 @@ fn encode_to_iter<T: iter::FromIterator<char>>(table: &'static [u8; 16], source:
BytesToHexChars::new(source, table).collect()
}

impl<T: AsRef<[u8]>> ToHex for T {
fn encode_hex<U: iter::FromIterator<char>>(&self) -> U {
encode_to_iter(HEX_CHARS_LOWER, self.as_ref())
}

fn encode_hex_upper<U: iter::FromIterator<char>>(&self) -> U {
encode_to_iter(HEX_CHARS_UPPER, self.as_ref())
}
}

/// The error type for decoding a hex string into `Vec<u8>` or `[u8; N]`.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FromHexError {
/// An invalid character was found. Valid ones are: `0...9`, `a...f`
/// or `A...F`.
InvalidHexCharacter { c: char, index: usize },

/// A hex string's length needs to be even, as two digits correspond to
/// one byte.
OddLength,

/// If the hex string is decoded into a fixed sized container, such as an
/// array, the hex string's length * 2 has to match the container's
/// length.
InvalidStringLength,
}

#[cfg(feature = "std")]
impl std::error::Error for FromHexError {
fn description(&self) -> &str {
match *self {
Self::InvalidHexCharacter { .. } => "invalid character",
Self::OddLength => "odd number of digits",
Self::InvalidStringLength => "invalid string length",
}
}
}

impl fmt::Display for FromHexError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Self::InvalidHexCharacter { c, index } => {
write!(f, "Invalid character '{}' at position {}", c, index)
}
Self::OddLength => write!(f, "Odd number of digits"),
Self::InvalidStringLength => write!(f, "Invalid string length"),
}
}
}

/// Types that can be decoded from a hex string.
///
/// This trait is implemented for `Vec<u8>` and small `u8`-arrays.
///
/// # Example
///
/// ```
/// use hex::FromHex;
///
/// match Vec::from_hex("48656c6c6f20776f726c6421") {
/// Ok(vec) => {
/// for b in vec {
/// println!("{}", b as char);
/// }
/// }
/// Err(e) => {
/// // Deal with the error ...
/// }
/// }
/// ```
pub trait FromHex: Sized {
type Error;

/// Creates an instance of type `Self` from the given hex string, or fails
/// with a custom error type.
///
/// Both, upper and lower case characters are valid and can even be
/// mixed (e.g. `f9b4ca`, `F9B4CA` and `f9B4Ca` are all valid strings).
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error>;
}

fn val(c: u8, idx: usize) -> Result<u8, FromHexError> {
match c {
b'A'..=b'F' => Ok(c - b'A' + 10),
Expand All @@ -205,62 +102,6 @@ fn val(c: u8, idx: usize) -> Result<u8, FromHexError> {
}
}

impl FromHex for Vec<u8> {
type Error = FromHexError;

fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
let hex = hex.as_ref();
if hex.len() % 2 != 0 {
return Err(FromHexError::OddLength);
}

hex.chunks(2)
.enumerate()
.map(|(i, pair)| Ok(val(pair[0], 2 * i)? << 4 | val(pair[1], 2 * i + 1)?))
.collect()
}
}

// Helper macro to implement the trait for a few fixed sized arrays. Once Rust
// has type level integers, this should be removed.
macro_rules! from_hex_array_impl {
($($len:expr)+) => {$(
impl FromHex for [u8; $len] {
type Error = FromHexError;

fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
let mut out = [0u8; $len];
decode_to_slice(hex, &mut out as &mut [u8])?;
Ok(out)
}
}
)+}
}

from_hex_array_impl! {
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
160 192 200 224 256 384 512 768 1024 2048 4096 8192 16384 32768
}

#[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))]
from_hex_array_impl! {
65536 131072 262144 524288 1048576 2097152 4194304 8388608
16777216 33554432 67108864 134217728 268435456 536870912
1073741824 2147483648
}

#[cfg(target_pointer_width = "64")]
from_hex_array_impl! {
4294967296
}

/// Encodes `data` as hex string using lowercase characters.
///
/// Lowercase characters are used (e.g. `f9b4ca`). The resulting string's
Expand All @@ -278,13 +119,61 @@ pub fn encode<T: AsRef<[u8]>>(data: T) -> String {
data.encode_hex()
}

/// Encodes `data` as hex string using uppercase characters.
// generates an iterator like this
// (0, 1)
// (2, 3)
// (4, 5)
// (6, 7)
// ...
fn generate_iter(len: usize) -> impl Iterator<Item = (usize, usize)> {
(0..len).step_by(2).zip((0..len).skip(1).step_by(2))
}

// the inverse of `val`.
fn byte2hex(byte: u8, table: &[u8; 16]) -> (u8, u8) {
let high = table[((byte & 0xf0) >> 4) as usize];
let low = table[(byte & 0x0f) as usize];

(high, low)
}

/// Encodes some bytes into a mutable slice of bytes.
///
/// Apart from the characters' casing, this works exactly like `encode()`.
/// The output buffer, has to be able to hold at least `input.len() * 2` bytes,
/// otherwise this function will return an error.
///
/// # Example
///
/// ```
/// # use hex::FromHexError;
/// # fn main() -> Result<(), FromHexError> {
/// let mut bytes = [0u8; 4 * 2];
///
/// hex::encode_to_slice(b"kiwi", &mut bytes)?;
/// assert_eq!(&bytes, b"6b697769");
/// # Ok(())
/// # }
/// ```
pub fn encode_to_slice(input: &[u8], output: &mut [u8]) -> Result<(), FromHexError> {
if input.len() * 2 != output.len() {
return Err(FromHexError::InvalidStringLength);
}

for (byte, (i, j)) in input.iter().zip(generate_iter(input.len() * 2)) {
let (high, low) = byte2hex(*byte, HEX_CHARS_LOWER);
output[i] = high;
output[j] = low;
}

Ok(())
}

/// Encodes `data` as hex string using uppercase characters.
///
/// Apart from the characters' casing, this works exactly like [`encode`].
///
/// # Example
/// ```
/// assert_eq!(hex::encode_upper("Hello world!"), "48656C6C6F20776F726C6421");
/// assert_eq!(hex::encode_upper(vec![1, 2, 3, 15, 16]), "0102030F10");
/// ```
Expand All @@ -298,6 +187,7 @@ pub fn encode_upper<T: AsRef<[u8]>>(data: T) -> String {
/// even be mixed (e.g. `f9b4ca`, `F9B4CA` and `f9B4Ca` are all valid strings).
///
/// # Example
///
/// ```
/// assert_eq!(
/// hex::decode("48656c6c6f20776f726c6421"),
Expand All @@ -317,6 +207,7 @@ pub fn decode<T: AsRef<[u8]>>(data: T) -> Result<Vec<u8>, FromHexError> {
/// even be mixed (e.g. `f9b4ca`, `F9B4CA` and `f9B4Ca` are all valid strings).
///
/// # Example
///
/// ```
/// let mut bytes = [0u8; 4];
/// assert_eq!(hex::decode_to_slice("6b697769", &mut bytes as &mut [u8]), Ok(()));
Expand All @@ -343,6 +234,28 @@ pub fn decode_to_slice<T: AsRef<[u8]>>(data: T, out: &mut [u8]) -> Result<(), Fr
mod test {
use super::*;

use crate::traits::{FromHex, ToHex};

#[test]
fn test_gen_iter() {
let mut result = Vec::new();
result.push((0, 1));
result.push((2, 3));

assert_eq!(generate_iter(5).collect::<Vec<_>>(), result);
}

#[test]
fn test_encode_to_slice() {
let mut output_1 = [0; 4 * 2];
encode_to_slice(b"kiwi", &mut output_1).unwrap();
assert_eq!(&output_1, b"6b697769");

let mut output_2 = [0; 5 * 2];
encode_to_slice(b"kiwis", &mut output_2).unwrap();
assert_eq!(&output_2, b"6b69776973")
}

#[test]
fn test_encode() {
assert_eq!(encode("foobar"), "666f6f626172");
Expand Down