#![no_std]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![warn(missing_docs)]
#[cfg(feature = "std")]
extern crate std;
#[cfg(feature = "alloc")]
#[allow(unused_imports)] #[macro_use]
extern crate alloc;
#[doc(hidden)]
pub mod _export {
pub mod _core {
pub use core::*;
}
}
pub mod buf_encoder;
pub mod display;
pub mod error;
mod iter;
pub mod prelude {
#[doc(inline)]
pub use crate::display::DisplayHex;
}
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use core::fmt;
pub(crate) use table::Table;
#[rustfmt::skip] #[doc(inline)]
pub use self::{
display::DisplayHex,
iter::{BytesToHexIter, HexToBytesIter, HexSliceToBytesIter},
};
#[doc(no_inline)]
pub use self::error::{
DecodeFixedLengthBytesError, DecodeVariableLengthBytesError, InvalidCharError,
InvalidLengthError, OddLengthStringError,
};
#[cfg(feature = "alloc")]
pub fn decode_to_vec(hex: &str) -> Result<Vec<u8>, DecodeVariableLengthBytesError> {
Ok(HexToBytesIter::new(hex)?.drain_to_vec()?)
}
pub fn decode_to_array<const N: usize>(hex: &str) -> Result<[u8; N], DecodeFixedLengthBytesError> {
if hex.len() == N * 2 {
let mut ret = [0u8; N];
HexToBytesIter::new_unchecked(hex).drain_to_slice(&mut ret)?;
Ok(ret)
} else {
Err(InvalidLengthError { invalid: hex.len(), expected: 2 * N }.into())
}
}
#[macro_export]
macro_rules! hex {
($hex:expr) => {{
const _: () = assert!($hex.len() % 2 == 0, "hex string must have even length");
const fn decode_digit(digit: u8) -> u8 {
match digit {
b'0'..=b'9' => digit - b'0',
b'a'..=b'f' => digit - b'a' + 10,
b'A'..=b'F' => digit - b'A' + 10,
_ => panic!("invalid hex digit"),
}
}
let mut output = [0u8; $hex.len() / 2];
let bytes = $hex.as_bytes();
let mut i = 0;
while i < output.len() {
let high = decode_digit(bytes[i * 2]);
let low = decode_digit(bytes[i * 2 + 1]);
output[i] = (high << 4) | low;
i += 1;
}
output
}};
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Case {
Lower,
Upper,
}
impl Default for Case {
#[inline]
fn default() -> Self { Case::Lower }
}
impl Case {
#[inline]
#[rustfmt::skip]
pub(crate) fn table(self) -> &'static Table {
match self {
Case::Lower => &Table::LOWER,
Case::Upper => &Table::UPPER,
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub enum Char {
Zero = b'0',
One = b'1',
Two = b'2',
Three = b'3',
Four = b'4',
Five = b'5',
Six = b'6',
Seven = b'7',
Eight = b'8',
Nine = b'9',
LowerA = b'a',
LowerB = b'b',
LowerC = b'c',
LowerD = b'd',
LowerE = b'e',
LowerF = b'f',
UpperA = b'A',
UpperB = b'B',
UpperC = b'C',
UpperD = b'D',
UpperE = b'E',
UpperF = b'F',
}
impl Char {
#[inline]
pub(crate) fn decode_nibble(b: u8) -> Option<u8> {
#[rustfmt::skip]
static TABLE: [u8; 256] = {
let mut t = [0xFF_u8; 256];
t[Char::Zero as usize] = 0; t[Char::One as usize] = 1;
t[Char::Two as usize] = 2; t[Char::Three as usize] = 3;
t[Char::Four as usize] = 4; t[Char::Five as usize] = 5;
t[Char::Six as usize] = 6; t[Char::Seven as usize] = 7;
t[Char::Eight as usize] = 8; t[Char::Nine as usize] = 9;
t[Char::LowerA as usize] = 10; t[Char::UpperA as usize] = 10;
t[Char::LowerB as usize] = 11; t[Char::UpperB as usize] = 11;
t[Char::LowerC as usize] = 12; t[Char::UpperC as usize] = 12;
t[Char::LowerD as usize] = 13; t[Char::UpperD as usize] = 13;
t[Char::LowerE as usize] = 14; t[Char::UpperE as usize] = 14;
t[Char::LowerF as usize] = 15; t[Char::UpperF as usize] = 15;
t
};
let n = TABLE[usize::from(b)];
if n == 0xFF {
None
} else {
Some(n)
}
}
#[inline]
pub fn slice_as_str(slice: &[Self]) -> &str {
let bytes = Self::slice_as_bytes(slice);
unsafe { core::str::from_utf8_unchecked(bytes) }
}
#[inline]
pub fn slice_as_bytes(slice: &[Self]) -> &[u8] {
let ptr = slice.as_ptr().cast();
let len = slice.len();
unsafe { core::slice::from_raw_parts(ptr, len) }
}
}
impl fmt::Display for Char {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let slice = core::slice::from_ref(self);
fmt::Display::fmt(Self::slice_as_str(slice), f)
}
}
impl fmt::Debug for Char {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let buf = [b'\'', u8::from(*self), b'\''];
let buf = unsafe { core::str::from_utf8_unchecked(&buf) };
fmt::Display::fmt(buf, f)
}
}
impl From<Char> for char {
#[inline]
fn from(c: Char) -> char { char::from(c as u8) }
}
impl From<Char> for u8 {
#[inline]
fn from(c: Char) -> u8 { c as u8 }
}
mod table {
use super::Char;
#[allow(clippy::derived_hash_with_manual_eq)] #[derive(Debug, Hash)]
pub(crate) struct Table([Char; 16]);
impl Table {
#[rustfmt::skip] pub(crate) const LOWER: Self = Table([
Char::Zero, Char::One, Char::Two, Char::Three,
Char::Four, Char::Five, Char::Six, Char::Seven,
Char::Eight, Char::Nine, Char::LowerA, Char::LowerB,
Char::LowerC, Char::LowerD, Char::LowerE, Char::LowerF,
]);
#[rustfmt::skip] pub(crate) const UPPER: Self = Table([
Char::Zero, Char::One, Char::Two, Char::Three,
Char::Four, Char::Five, Char::Six, Char::Seven,
Char::Eight, Char::Nine, Char::UpperA, Char::UpperB,
Char::UpperC, Char::UpperD, Char::UpperE, Char::UpperF,
]);
#[inline]
pub(crate) fn byte_to_chars(&self, byte: u8) -> [char; 2] {
self.byte_to_hex_chars(byte).map(char::from)
}
#[inline]
pub(crate) fn byte_to_str<'a>(&self, dest: &'a mut [u8; 2], byte: u8) -> &'a str {
dest[0] = self.0[usize::from(byte >> 4)].into();
dest[1] = self.0[usize::from(byte & 0x0F)].into();
let hex_str = unsafe { core::str::from_utf8_unchecked(dest) };
hex_str
}
#[inline]
pub(crate) fn byte_to_hex_chars(&self, byte: u8) -> [Char; 2] {
let left = self.0[usize::from(byte >> 4)];
let right = self.0[usize::from(byte & 0x0F)];
[left, right]
}
}
impl PartialEq for Table {
fn eq(&self, other: &Self) -> bool { self.0[10] == other.0[10] }
}
impl Eq for Table {}
}
#[cfg(test)]
#[cfg(feature = "alloc")]
mod tests {
#[test]
fn hex_macro() {
let data = hex!("deadbeef");
assert_eq!(data, [0xde, 0xad, 0xbe, 0xef]);
}
#[test]
fn hex_macro_case_insensitive() {
assert_eq!(hex!("DEADBEEF"), hex!("deadbeef"));
}
#[test]
fn hex_macro_const_context() {
const HASH: [u8; 32] =
hex!("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f");
assert_eq!(HASH[0], 0x00);
assert_eq!(HASH[31], 0x6f);
}
#[test]
fn char_slice_casts() {
use super::Char;
const BEEF: &[Char] = &[Char::LowerB, Char::LowerE, Char::LowerE, Char::LowerF];
assert_eq!(Char::slice_as_bytes(&[]), &[]);
assert_eq!(Char::slice_as_bytes(&BEEF[..1]), b"b");
assert_eq!(Char::slice_as_bytes(BEEF), b"beef");
assert_eq!(Char::slice_as_str(&[]), "");
assert_eq!(Char::slice_as_str(&BEEF[..1]), "b");
assert_eq!(Char::slice_as_str(BEEF), "beef");
}
#[test]
fn char_display() {
use alloc::string::ToString;
use super::Char;
assert_eq!(Char::Zero.to_string(), "0");
assert_eq!(Char::LowerB.to_string(), "b");
assert_eq!(Char::UpperB.to_string(), "B");
assert_eq!(format!("{: >3}", Char::UpperB), " B");
assert_eq!(format!("{: <3}", Char::UpperB), "B ");
assert_eq!(format!("{: ^3}", Char::UpperB), " B ");
}
#[test]
fn char_debug() {
use super::Char;
assert_eq!(format!("{:?}", Char::Zero), format!("{:?}", '0'));
assert_eq!(format!("{:?}", Char::LowerB), format!("{:?}", 'b'));
assert_eq!(format!("{:?}", Char::UpperB), format!("{:?}", 'B'));
assert_eq!(format!("{: >5?}", Char::UpperB), " 'B'");
assert_eq!(format!("{: <5?}", Char::UpperB), "'B' ");
assert_eq!(format!("{: ^5?}", Char::UpperB), " 'B' ");
}
}