crate::cfg_feature_alloc! {
extern crate alloc;
}
use crate::buf::{FixedU8Buf, StrBuf};
use crate::cfg_feature_alloc;
use core::fmt::Write;
use irox_bits::{BitsError, BitsErrorKind, Error, ErrorKind, FormatBits, MutBits};
pub static HEX_UPPER_CHARS: [char; 16] = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
];
pub static HEX_LOWER_CHARS: [char; 16] = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
];
pub trait HexDump {
crate::cfg_feature_std! {
fn hexdump(&self);
}
fn hexdump_to<T: MutBits + ?Sized>(&self, out: &mut T) -> Result<(), Error>;
}
impl<S: AsRef<[u8]>> HexDump for S {
crate::cfg_feature_std! {
fn hexdump(&self) {
let _ = self.hexdump_to(&mut irox_bits::BitsWrapper::Borrowed(&mut std::io::stdout().lock()));
}
}
fn hexdump_to<T: MutBits + ?Sized>(&self, out: &mut T) -> Result<(), Error> {
let mut idx = 0;
let chunks = self.as_ref().chunks(16);
let mut out: FormatBits<T> = out.into();
for chunk in chunks {
write!(out, "{idx:08X} ")?;
for v in chunk {
write!(out, "{v:02X} ")?;
}
for _i in 0..(16 - chunk.len()) {
write!(out, " ")?;
}
write!(out, " |")?;
for v in chunk {
match *v {
0..=0x1F | 0x7F..=0xA0 | 0xFF => {
write!(out, ".")?;
}
p => {
write!(out, "{}", p as char)?;
}
}
}
for _i in 0..(16 - chunk.len()) {
write!(out, " ")?;
}
writeln!(out, "|")?;
idx += 16;
}
Ok(())
}
}
cfg_feature_alloc! {
pub fn to_hex_array(value: &[u8]) -> alloc::string::String {
let mut out = alloc::vec::Vec::new();
for v in value {
out.push(format!("0x{:02X}", v));
}
let joined = out.join(",");
format!("[{joined}]")
}
}
pub const fn hex_char_to_nibble(ch: char) -> Result<u8, Error> {
Ok(match ch {
'0' => 0,
'1' => 1,
'2' => 2,
'3' => 3,
'4' => 4,
'5' => 5,
'6' => 6,
'7' => 7,
'8' => 8,
'9' => 9,
'a' | 'A' => 0xA,
'b' | 'B' => 0xB,
'c' | 'C' => 0xC,
'd' | 'D' => 0xD,
'e' | 'E' => 0xE,
'f' | 'F' => 0xF,
_ => return ErrorKind::InvalidData.err("Invalid hex character"),
})
}
pub const fn nibble_to_hex_char(val: u8) -> Result<char, Error> {
Ok(match val {
0x0 => '0',
0x1 => '1',
0x2 => '2',
0x3 => '3',
0x4 => '4',
0x5 => '5',
0x6 => '6',
0x7 => '7',
0x8 => '8',
0x9 => '9',
0xA => 'A',
0xB => 'B',
0xC => 'C',
0xD => 'D',
0xE => 'E',
0xF => 'F',
_ => return ErrorKind::InvalidData.err("Invalid hex character"),
})
}
crate::cfg_feature_alloc! {
pub fn from_hex_str(hex: &str) -> Result<alloc::boxed::Box<[u8]>, Error> {
let len = hex.len();
let mut out = alloc::vec::Vec::with_capacity(len * 2);
let mut val = 0u8;
let mut idx = 0;
for ch in hex.chars() {
if ch == ' ' {
continue;
}
let ch = hex_char_to_nibble(ch)?;
if idx & 0x1 == 0 {
val |= (ch << 4) & 0xF0;
} else {
val |= ch & 0xF;
out.push(val);
val = 0;
}
idx += 1;
}
Ok(out.into_boxed_slice())
}
}
pub fn from_hex_into<T: MutBits>(hex: &str, out: &mut T) -> Result<usize, Error> {
let mut val = 0u8;
let mut idx = 0;
let mut wrote = 0;
for ch in hex.chars() {
if ch == ' ' {
continue;
}
let ch = hex_char_to_nibble(ch)?;
if idx & 0x1 == 0 {
val |= (ch << 4) & 0xF0;
} else {
val |= ch & 0xF;
out.write_u8(val)?;
wrote += 1;
val = 0;
}
idx += 1;
}
Ok(wrote)
}
pub fn try_from_hex_str<const N: usize>(str: &str) -> Result<[u8; N], BitsError> {
let mut buf = FixedU8Buf::<N>::new();
if from_hex_into(str, &mut buf)? != N {
return Err(BitsErrorKind::UnexpectedEof.into());
}
Ok(buf.take())
}
crate::cfg_feature_alloc! {
pub fn to_hex_str_upper(val: &[u8]) -> alloc::string::String {
let len = val.len() * 2;
let mut out = alloc::string::String::with_capacity(len);
for v in val {
let _ = write!(&mut out, "{v:02X}");
}
out
}
}
crate::cfg_feature_alloc! {
pub fn to_hex_str_lower(val: &[u8]) -> alloc::string::String {
let len = val.len() * 2;
let mut out = alloc::string::String::with_capacity(len);
for v in val {
let _ = write!(&mut out, "{v:02x}");
}
out
}
}
pub fn to_hex_strbuf_lower<const N: usize>(val: &[u8], buf: &mut StrBuf<N>) -> Result<(), Error> {
let len = val.len() * 2;
if N < len {
return Err(ErrorKind::UnexpectedEof.into());
}
for v in val {
write!(buf, "{v:02x}")?;
}
Ok(())
}
pub fn to_hex_strbuf_upper<const N: usize>(val: &[u8], buf: &mut StrBuf<N>) -> Result<(), Error> {
let len = val.len() * 2;
if N < len {
return Err(ErrorKind::UnexpectedEof.into());
}
for v in val {
write!(buf, "{v:02X}")?;
}
Ok(())
}
#[doc(hidden)]
#[allow(clippy::indexing_slicing)]
pub const fn hex_len(vals: &[&[u8]]) -> Option<usize> {
let mut out = 0;
let mut idx = 0;
while idx < vals.len() {
let val = vals[idx];
let len = val.len();
out += len;
idx += 1;
}
if out & 0x01 == 0x01 {
None
} else {
Some(out / 2)
}
}
#[doc(hidden)]
#[allow(clippy::indexing_slicing)]
pub const fn raw_hex<const L: usize>(vals: &[&[u8]]) -> Result<[u8; L], char> {
let mut out = [0u8; L];
let mut outidx = 0;
let mut idx = 0;
while idx < vals.len() {
let val = vals[idx];
let mut inneridx = 0;
while inneridx < val.len() {
let a = val[inneridx] as char;
let Ok(a) = hex_char_to_nibble(a) else {
return Err(a);
};
inneridx += 1;
let b = val[inneridx] as char;
let Ok(b) = hex_char_to_nibble(b) else {
return Err(b);
};
inneridx += 1;
out[outidx] = (a << 4) | b;
outidx += 1;
}
idx += 1;
}
Ok(out)
}
#[allow(unused_macros)]
#[macro_export]
macro_rules! hex {
($($input:literal)+) => {{
const VALS: &[& 'static [u8]] = &[$($input.as_bytes(),)*];
const LEN: usize = match $crate::hex::hex_len(VALS) {
Some(v) => v,
None => panic!("Hex string is an odd length")
};
const RTN: [u8;LEN] = match $crate::hex::raw_hex::<LEN>(VALS) {
Ok(v) => v,
Err(_) => panic!("Hex string contains invalid character")
};
RTN
}};
}
#[cfg(test)]
#[cfg(feature = "std")]
mod tests {
extern crate alloc;
use crate::hex::HexDump;
use alloc::vec::Vec;
#[test]
pub fn test() -> Result<(), irox_bits::Error> {
let mut buf: Vec<u8> = Vec::new();
for v in u8::MIN..=u8::MAX {
buf.push(v);
}
buf.hexdump();
Ok(())
}
#[test]
pub fn const_hex_test() -> Result<(), irox_bits::Error> {
let raw_hex = hex!("");
assert_eq_hex_slice!(&[] as &[u8], &raw_hex);
let raw_hex = hex!("00");
assert_eq_hex_slice!(&[0x0u8], &raw_hex);
raw_hex.hexdump();
Ok(())
}
}