use super::{
Encoding,
Error::{self, *},
};
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[cfg(feature = "alloc")]
pub fn encode<B: AsRef<[u8]>>(bytes: B) -> Vec<u8> {
Hex::lower_case().encode(bytes)
}
#[cfg(feature = "alloc")]
pub fn decode<B: AsRef<[u8]>>(encoded_bytes: B) -> Result<Vec<u8>, Error> {
Hex::lower_case().decode(encoded_bytes)
}
#[cfg(feature = "alloc")]
pub fn encode_upper<B: AsRef<[u8]>>(bytes: B) -> Vec<u8> {
Hex::upper_case().encode(bytes)
}
#[cfg(feature = "alloc")]
pub fn decode_upper<B: AsRef<[u8]>>(encoded_bytes: B) -> Result<Vec<u8>, Error> {
Hex::upper_case().decode(encoded_bytes)
}
#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct Hex {
case: Case,
}
impl Hex {
pub fn lower_case() -> Hex {
Hex { case: Case::Lower }
}
pub fn upper_case() -> Hex {
Hex { case: Case::Upper }
}
}
impl Encoding for Hex {
fn encode_to_slice(&self, src: &[u8], dst: &mut [u8]) -> Result<usize, Error> {
if self.encoded_len(src) > dst.len() {
return Err(LengthInvalid);
}
for (i, src_byte) in src.iter().enumerate() {
let offset = i * 2;
dst[offset] = self.case.encode_nibble(src_byte >> 4);
dst[offset + 1] = self.case.encode_nibble(src_byte & 0x0f);
}
Ok(src.len() * 2)
}
fn encoded_len(&self, bytes: &[u8]) -> usize {
bytes.len() * 2
}
fn decode_to_slice(&self, src: &[u8], dst: &mut [u8]) -> Result<usize, Error> {
if !src.is_empty() && char::from(src[src.len() - 1]).is_whitespace() {
return Err(TrailingWhitespace);
}
let dst_length = self.decoded_len(src)?;
ensure!(dst_length <= dst.len(), LengthInvalid);
let mut err: usize = 0;
for (i, dst_byte) in dst.iter_mut().enumerate().take(dst_length) {
let src_offset = i * 2;
let byte = (self.case.decode_nibble(src[src_offset]) << 4)
| self.case.decode_nibble(src[src_offset + 1]);
err |= byte >> 8;
*dst_byte = byte as u8;
}
if err == 0 {
Ok(dst_length)
} else {
Err(EncodingInvalid)
}
}
fn decoded_len(&self, bytes: &[u8]) -> Result<usize, Error> {
if bytes.len() & 1 == 0 {
Ok(bytes.len() >> 1)
} else {
Err(LengthInvalid)
}
}
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
enum Case {
Lower,
Upper,
}
impl Case {
#[inline]
fn decode_nibble(self, src: u8) -> usize {
let byte = src as isize;
let mut ret: isize = -1;
ret += (((0x2fisize - byte) & (byte - 0x3a)) >> 8) & (byte - 47);
ret += match self {
Case::Lower => {
(((0x60isize - byte) & (byte - 0x67)) >> 8) & (byte - 86)
}
Case::Upper => {
(((0x40isize - byte) & (byte - 0x47)) >> 8) & (byte - 54)
}
};
ret as usize
}
#[inline]
fn encode_nibble(self, src: u8) -> u8 {
let mut ret = src as isize + 0x30;
ret += match self {
Case::Lower => {
((0x39isize - ret) >> 8) & (0x61isize - 0x3a)
}
Case::Upper => {
((0x39isize - ret) >> 8) & (0x41isize - 0x3a)
}
};
ret as u8
}
}
impl Default for Case {
fn default() -> Case {
Case::Lower
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::Error::*;
struct HexVector {
raw: &'static [u8],
hex: &'static [u8],
}
const HEX_TEST_VECTORS: &[HexVector] = &[
HexVector { raw: b"", hex: b"" },
HexVector {
raw: b"\0",
hex: b"00",
},
HexVector {
raw: b"***",
hex: b"2a2a2a",
},
HexVector {
raw: b"\x01\x02\x03\x04",
hex: b"01020304",
},
HexVector {
raw: b"\xAD\xAD\xAD\xAD\xAD",
hex: b"adadadadad",
},
HexVector {
raw: b"\xFF\xFF\xFF\xFF\xFF",
hex: b"ffffffffff",
},
];
#[test]
fn encode_test_vectors() {
for vector in HEX_TEST_VECTORS {
let mut out = [0u8; 10];
let out_len = Hex::lower_case()
.encode_to_slice(vector.raw, &mut out)
.unwrap();
assert_eq!(vector.hex, &out[..out_len]);
}
}
#[test]
fn decode_test_vectors() {
for vector in HEX_TEST_VECTORS {
let mut out = [0u8; 5];
let out_len = Hex::lower_case()
.decode_to_slice(vector.hex, &mut out)
.unwrap();
assert_eq!(vector.raw, &out[..out_len]);
}
}
#[test]
fn reject_odd_size_input() {
let mut out = [0u8; 3];
assert_eq!(
LengthInvalid,
Hex::lower_case()
.decode_to_slice(b"12345", &mut out)
.err()
.unwrap(),
)
}
#[test]
fn encode_and_decode_various_lengths() {
let data = [b'X'; 64];
for i in 0..data.len() {
let encoded = Hex::lower_case().encode(&data[..i]);
let decoded = Hex::lower_case().decode(encoded).unwrap();
assert_eq!(decoded.as_slice(), &data[..i]);
}
}
}