1use core::fmt::{Display, Formatter};
2
3const LOWER_HEX_CHARS: &[u8; 16] = b"0123456789abcdef";
4const UPPER_HEX_CHARS: &[u8; 16] = b"0123456789ABCDEF";
5
6#[derive(Clone, Copy, Debug, PartialEq)]
8pub enum HexEncMode {
9 #[cfg(feature = "sha3")]
11 Eip55,
12 WithPrefixLower,
14 WithPrefixUpper,
16 WithoutPrefixLower,
18 WithoutPrefixUpper,
20}
21
22#[derive(Debug)]
24pub enum HexError {
25 InsufficientBuffer,
27 #[cfg(feature = "sha3")]
29 InvalidEip55Input,
30 InvalidHexCharacter,
32 OddLen,
34}
35
36#[derive(Debug)]
38pub struct HexDisplay<'bytes>(
39 pub &'bytes [u8],
41 pub Option<HexEncMode>,
45);
46
47impl Display for HexDisplay<'_> {
48 #[inline]
49 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
50 let actual_mode = actual_mode(self.1);
51 let table = match actual_mode {
52 #[cfg(feature = "sha3")]
53 HexEncMode::Eip55 => return Err(core::fmt::Error),
54 HexEncMode::WithPrefixLower | HexEncMode::WithoutPrefixLower => LOWER_HEX_CHARS,
55 HexEncMode::WithPrefixUpper | HexEncMode::WithoutPrefixUpper => UPPER_HEX_CHARS,
56 };
57 if matches!(actual_mode, HexEncMode::WithPrefixLower | HexEncMode::WithPrefixUpper) {
58 write!(f, "0x")?;
59 }
60 for byte in self.0 {
61 let (lhs, rhs) = byte_to_hex(*byte, table);
62 write!(f, "{}{}", char::from(lhs), char::from(rhs))?;
63 }
64 Ok(())
65 }
66}
67
68#[inline]
70pub fn decode_hex<'out>(mut data: &[u8], out: &'out mut [u8]) -> crate::Result<&'out mut [u8]> {
71 data = if let [b'0', b'x' | b'X', rest @ ..] = data { rest } else { data };
72 let bytes_len = data.len() / 2;
73 let Some(actual_out) = out.get_mut(..bytes_len) else {
74 return Err(HexError::InsufficientBuffer.into());
75 };
76 let (arrays, rem) = data.as_chunks::<2>();
77 if !rem.is_empty() {
78 return Err(HexError::OddLen.into());
79 }
80 for ([a, b], byte) in arrays.iter().zip(&mut *actual_out) {
81 *byte = hex_to_bytes(*a, *b)?;
82 }
83 Ok(actual_out)
84}
85
86#[inline]
90pub fn encode_hex<'out>(
91 data: &[u8],
92 mode: Option<HexEncMode>,
93 out: &'out mut [u8],
94) -> crate::Result<&'out str> {
95 let actual_mode = actual_mode(mode);
96 let mut hex_len = data.len().wrapping_mul(2);
97 let actual_out = match actual_mode {
98 #[cfg(feature = "sha3")]
99 HexEncMode::Eip55 => return encode_eip55(data, out),
100 HexEncMode::WithPrefixLower | HexEncMode::WithPrefixUpper => {
101 hex_len = hex_len.wrapping_add(2);
102 let Some([a, b, actual_out @ ..]) = out.get_mut(..hex_len) else {
103 return Err(HexError::InsufficientBuffer.into());
104 };
105 *a = b'0';
106 *b = b'x';
107 actual_out
108 }
109 HexEncMode::WithoutPrefixLower | HexEncMode::WithoutPrefixUpper => {
110 let Some((actual_out, _)) = out.split_at_mut_checked(hex_len) else {
111 return Err(HexError::InsufficientBuffer.into());
112 };
113 actual_out
114 }
115 };
116 let (arrays, _) = actual_out.as_chunks_mut::<2>();
117 let table = match actual_mode {
118 #[cfg(feature = "sha3")]
119 HexEncMode::Eip55 => return Ok(""),
120 HexEncMode::WithPrefixLower | HexEncMode::WithoutPrefixLower => LOWER_HEX_CHARS,
121 HexEncMode::WithPrefixUpper | HexEncMode::WithoutPrefixUpper => UPPER_HEX_CHARS,
122 };
123 for (byte, [a, b]) in data.iter().zip(arrays) {
124 let (lhs, rhs) = byte_to_hex(*byte, table);
125 *a = lhs;
126 *b = rhs;
127 }
128 unsafe { Ok(str::from_utf8_unchecked(out.get_mut(..hex_len).unwrap_or_default())) }
130}
131
132const fn actual_mode(hem: Option<HexEncMode>) -> HexEncMode {
133 if let Some(elem) = hem { elem } else { HexEncMode::WithoutPrefixLower }
134}
135
136#[expect(clippy::indexing_slicing, reason = "all bytes are limited to the array's length")]
137fn byte_to_hex(byte: u8, table: &[u8; 16]) -> (u8, u8) {
138 let lhs_idx: usize = (byte >> 4).into();
139 let rhs_idx: usize = (byte & 0b0000_1111).into();
140 (table[lhs_idx], table[rhs_idx])
141}
142
143#[cfg(feature = "sha3")]
144fn encode_eip55<'out>(data: &[u8], out: &'out mut [u8]) -> crate::Result<&'out str> {
145 use sha3::Digest;
146 if data.len() > 32 {
147 return Err(HexError::InvalidEip55Input.into());
148 }
149 let rslt_len = encode_hex(data, Some(HexEncMode::WithPrefixLower), out)?.len();
150 let Some([_, _, hex @ ..]) = out.get_mut(..rslt_len) else {
151 return Ok("");
152 };
153 let hash: [u8; 32] = {
154 let mut hasher = sha3::Keccak256::default();
155 hasher.update(&*hex);
156 hasher.finalize().into()
157 };
158 for (idx, byte) in hex.iter_mut().enumerate() {
159 let is_letter = byte.is_ascii_lowercase();
160 if !is_letter {
161 continue;
162 }
163 let half_idx = hash.get(idx / 2).copied().unwrap_or_default();
164 let nibble = if idx % 2 == 0 { half_idx >> 4 } else { half_idx & 0b0000_1111 };
165 if nibble >= 8 {
166 *byte = byte.to_ascii_uppercase();
167 }
168 }
169 unsafe { Ok(str::from_utf8_unchecked(out.get_mut(..rslt_len).unwrap_or_default())) }
171}
172
173fn hex_to_bytes(lhs: u8, rhs: u8) -> crate::Result<u8> {
174 fn half(byte: u8) -> crate::Result<u8> {
175 match byte {
176 b'A'..=b'F' => Ok(byte.wrapping_sub(b'A').wrapping_add(10)),
177 b'a'..=b'f' => Ok(byte.wrapping_sub(b'a').wrapping_add(10)),
178 b'0'..=b'9' => Ok(byte.wrapping_sub(b'0')),
179 _ => Err(HexError::InvalidHexCharacter.into()),
180 }
181 }
182 Ok((half(lhs)? << 4) | half(rhs)?)
183}
184
185#[cfg(test)]
186mod test {
187 use crate::{
188 collection::ArrayVectorU8,
189 de::{HexDisplay, HexEncMode, decode_hex, encode_hex},
190 };
191
192 #[test]
193 fn decode_has_correct_output() {
194 assert_eq!(decode_hex(b"61626364", &mut [0; 4]).unwrap(), b"abcd");
195 assert_eq!(decode_hex(b"0x6162636465", &mut [0; 5]).unwrap(), b"abcde");
196 assert!(decode_hex(b"6", &mut [0, 0, 0, 0]).is_err());
197 }
198
199 #[cfg(feature = "sha3")]
200 #[test]
201 fn eip55() {
202 let mut buf = [0u8; 44];
203 assert_eq!(
204 encode_hex(
205 &[
206 90, 174, 182, 5, 63, 62, 148, 201, 185, 160, 159, 51, 102, 148, 53, 231, 239, 27, 234,
207 237,
208 ],
209 Some(HexEncMode::Eip55),
210 &mut buf
211 )
212 .unwrap(),
213 "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
214 );
215 }
216
217 #[test]
218 fn encode_has_correct_output() {
219 assert_eq!(encode_hex(&[], None, &mut [0u8; 8]).unwrap(), "");
220 assert_eq!(encode_hex(b"AZ", None, &mut [0u8; 8]).unwrap(), "415a");
221 assert_eq!(
222 encode_hex(b"AZ", Some(HexEncMode::WithoutPrefixUpper), &mut [0u8; 8]).unwrap(),
223 "415A"
224 );
225 }
226
227 #[test]
228 fn hex_display() {
229 assert_eq!(
230 &ArrayVectorU8::<u8, 16>::try_from(format_args!(
231 "{}",
232 HexDisplay(b"abcdZ", Some(HexEncMode::WithoutPrefixLower))
233 ))
234 .unwrap(),
235 "616263645a".as_bytes()
236 );
237 assert_eq!(
238 &ArrayVectorU8::<u8, 16>::try_from(format_args!(
239 "{}",
240 HexDisplay(b"abcdZ", Some(HexEncMode::WithPrefixLower))
241 ))
242 .unwrap(),
243 "0x616263645a".as_bytes()
244 );
245 }
246}