1crate::cfg_feature_alloc! {
9 extern crate alloc;
10}
11use crate::buf::{FixedU8Buf, StrBuf};
12use crate::cfg_feature_alloc;
13use core::fmt::Write;
14use irox_bits::{BitsError, BitsErrorKind, Error, ErrorKind, FormatBits, MutBits};
15
16pub static HEX_UPPER_CHARS: [char; 16] = [
18 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
19];
20pub static HEX_LOWER_CHARS: [char; 16] = [
22 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
23];
24
25pub trait HexDump {
29 crate::cfg_feature_std! {
30 fn hexdump(&self);
32 }
33
34 fn hexdump_to<T: MutBits + ?Sized>(&self, out: &mut T) -> Result<(), Error>;
36}
37
38impl<S: AsRef<[u8]>> HexDump for S {
39 crate::cfg_feature_std! {
40 fn hexdump(&self) {
41 let _ = self.hexdump_to(&mut irox_bits::BitsWrapper::Borrowed(&mut std::io::stdout().lock()));
42 }
43 }
44
45 fn hexdump_to<T: MutBits + ?Sized>(&self, out: &mut T) -> Result<(), Error> {
46 let mut idx = 0;
47 let chunks = self.as_ref().chunks(16);
48 let mut out: FormatBits<T> = out.into();
49 for chunk in chunks {
50 write!(out, "{idx:08X} ")?;
51 for v in chunk {
52 write!(out, "{v:02X} ")?;
53 }
54 for _i in 0..(16 - chunk.len()) {
55 write!(out, " ")?;
56 }
57 write!(out, " |")?;
58 for v in chunk {
59 match *v {
60 0..=0x1F | 0x7F..=0xA0 | 0xFF => {
61 write!(out, ".")?;
63 }
64 p => {
65 write!(out, "{}", p as char)?;
67 }
68 }
69 }
70 for _i in 0..(16 - chunk.len()) {
71 write!(out, " ")?;
72 }
73 writeln!(out, "|")?;
74 idx += 16;
75 }
76 Ok(())
77 }
78}
79cfg_feature_alloc! {
80 pub fn to_hex_array(value: &[u8]) -> alloc::string::String {
82 let mut out = alloc::vec::Vec::new();
83 for v in value {
84 out.push(format!("0x{:02X}", v));
85 }
86 let joined = out.join(",");
87
88 format!("[{joined}]")
89 }
90}
91
92pub const fn hex_char_to_nibble(ch: char) -> Result<u8, Error> {
93 Ok(match ch {
94 '0' => 0,
95 '1' => 1,
96 '2' => 2,
97 '3' => 3,
98 '4' => 4,
99 '5' => 5,
100 '6' => 6,
101 '7' => 7,
102 '8' => 8,
103 '9' => 9,
104 'a' | 'A' => 0xA,
105 'b' | 'B' => 0xB,
106 'c' | 'C' => 0xC,
107 'd' | 'D' => 0xD,
108 'e' | 'E' => 0xE,
109 'f' | 'F' => 0xF,
110 _ => return ErrorKind::InvalidData.err("Invalid hex character"),
111 })
112}
113pub const fn nibble_to_hex_char(val: u8) -> Result<char, Error> {
115 Ok(match val {
116 0x0 => '0',
117 0x1 => '1',
118 0x2 => '2',
119 0x3 => '3',
120 0x4 => '4',
121 0x5 => '5',
122 0x6 => '6',
123 0x7 => '7',
124 0x8 => '8',
125 0x9 => '9',
126 0xA => 'A',
127 0xB => 'B',
128 0xC => 'C',
129 0xD => 'D',
130 0xE => 'E',
131 0xF => 'F',
132 _ => return ErrorKind::InvalidData.err("Invalid hex character"),
133 })
134}
135
136crate::cfg_feature_alloc! {
137 pub fn from_hex_str(hex: &str) -> Result<alloc::boxed::Box<[u8]>, Error> {
141 let len = hex.len();
142 let mut out = alloc::vec::Vec::with_capacity(len * 2);
143
144 let mut val = 0u8;
145 let mut idx = 0;
146 for ch in hex.chars() {
147 if ch == ' ' {
148 continue;
149 }
150 let ch = hex_char_to_nibble(ch)?;
151 if idx & 0x1 == 0 {
152 val |= (ch << 4) & 0xF0;
153 } else {
154 val |= ch & 0xF;
155 out.push(val);
156 val = 0;
157 }
158 idx += 1;
159 }
160
161 Ok(out.into_boxed_slice())
162 }
163}
164
165pub fn from_hex_into<T: MutBits>(hex: &str, out: &mut T) -> Result<usize, Error> {
169 let mut val = 0u8;
170 let mut idx = 0;
171 let mut wrote = 0;
172 for ch in hex.chars() {
173 if ch == ' ' {
174 continue;
175 }
176 let ch = hex_char_to_nibble(ch)?;
177 if idx & 0x1 == 0 {
178 val |= (ch << 4) & 0xF0;
179 } else {
180 val |= ch & 0xF;
181 out.write_u8(val)?;
182 wrote += 1;
183 val = 0;
184 }
185 idx += 1;
186 }
187
188 Ok(wrote)
189}
190
191pub fn try_from_hex_str<const N: usize>(str: &str) -> Result<[u8; N], BitsError> {
195 let mut buf = FixedU8Buf::<N>::new();
196 if from_hex_into(str, &mut buf)? != N {
197 return Err(BitsErrorKind::UnexpectedEof.into());
198 }
199 Ok(buf.take())
200}
201
202crate::cfg_feature_alloc! {
203 pub fn to_hex_str_upper(val: &[u8]) -> alloc::string::String {
206 let len = val.len() * 2;
207 let mut out = alloc::string::String::with_capacity(len);
208
209 for v in val {
210 let _ = write!(&mut out, "{v:02X}");
211 }
212
213 out
214 }
215}
216
217crate::cfg_feature_alloc! {
218 pub fn to_hex_str_lower(val: &[u8]) -> alloc::string::String {
221 let len = val.len() * 2;
222 let mut out = alloc::string::String::with_capacity(len);
223
224 for v in val {
225 let _ = write!(&mut out, "{v:02x}");
226 }
227
228 out
229 }
230}
231
232pub fn to_hex_strbuf_lower<const N: usize>(val: &[u8], buf: &mut StrBuf<N>) -> Result<(), Error> {
236 let len = val.len() * 2;
237 if N < len {
238 return Err(ErrorKind::UnexpectedEof.into());
239 }
240 for v in val {
241 write!(buf, "{v:02x}")?;
242 }
243
244 Ok(())
245}
246
247pub fn to_hex_strbuf_upper<const N: usize>(val: &[u8], buf: &mut StrBuf<N>) -> Result<(), Error> {
251 let len = val.len() * 2;
252 if N < len {
253 return Err(ErrorKind::UnexpectedEof.into());
254 }
255 for v in val {
256 write!(buf, "{v:02X}")?;
257 }
258
259 Ok(())
260}
261
262#[doc(hidden)]
263#[allow(clippy::indexing_slicing)]
264pub const fn hex_len(vals: &[&[u8]]) -> Option<usize> {
265 let mut out = 0;
266 let mut idx = 0;
267 while idx < vals.len() {
268 let val = vals[idx];
269 let len = val.len();
270
271 out += len;
272 idx += 1;
273 }
274 if out & 0x01 == 0x01 {
275 None
276 } else {
277 Some(out / 2)
278 }
279}
280#[doc(hidden)]
281#[allow(clippy::indexing_slicing)]
282pub const fn raw_hex<const L: usize>(vals: &[&[u8]]) -> Result<[u8; L], char> {
283 let mut out = [0u8; L];
284 let mut outidx = 0;
285 let mut idx = 0;
286 while idx < vals.len() {
287 let val = vals[idx];
288 let mut inneridx = 0;
289 while inneridx < val.len() {
290 let a = val[inneridx] as char;
291 let Ok(a) = hex_char_to_nibble(a) else {
292 return Err(a);
293 };
294 inneridx += 1;
295 let b = val[inneridx] as char;
296 let Ok(b) = hex_char_to_nibble(b) else {
297 return Err(b);
298 };
299 inneridx += 1;
300 out[outidx] = (a << 4) | b;
301 outidx += 1;
302 }
303 idx += 1;
304 }
305 Ok(out)
306}
307
308#[allow(unused_macros)]
309#[macro_export]
310macro_rules! hex {
317 ($($input:literal)+) => {{
318 const VALS: &[& 'static [u8]] = &[$($input.as_bytes(),)*];
319 const LEN: usize = match $crate::hex::hex_len(VALS) {
320 Some(v) => v,
321 None => panic!("Hex string is an odd length")
322 };
323 const RTN: [u8;LEN] = match $crate::hex::raw_hex::<LEN>(VALS) {
324 Ok(v) => v,
325 Err(_) => panic!("Hex string contains invalid character")
326 };
327 RTN
328 }};
329}
330
331#[cfg(test)]
332#[cfg(feature = "std")]
333mod tests {
334 extern crate alloc;
335 use crate::hex::HexDump;
336 use alloc::vec::Vec;
337
338 #[test]
339 pub fn test() -> Result<(), irox_bits::Error> {
340 let mut buf: Vec<u8> = Vec::new();
341 for v in u8::MIN..=u8::MAX {
342 buf.push(v);
343 }
344
345 buf.hexdump();
346
347 Ok(())
348 }
349
350 #[test]
351 pub fn const_hex_test() -> Result<(), irox_bits::Error> {
352 let raw_hex = hex!("");
353 assert_eq_hex_slice!(&[] as &[u8], &raw_hex);
354 let raw_hex = hex!("00");
355 assert_eq_hex_slice!(&[0x0u8], &raw_hex);
356 raw_hex.hexdump();
357 Ok(())
358 }
359}