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