Skip to main content

hex/
hex.rs

1#![cfg_attr(not(any(feature = "std", test)), no_std)]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4//! Fast hex encoding and decoding with SIMD acceleration, constant-time
5//! operations, and `const fn` support.
6//!
7//! Encoding supports lowercase (`0-9a-f`) and uppercase (`0-9A-F`)
8//! alphabets via [`Alphabet`]. Decoding is case-insensitive.
9//!
10//! # Feature flags
11//!
12//! | Flag    | Description                                             |
13//! |---------|---------------------------------------------------------|
14//! | `std`   | [`std::error::Error`] trait impls (enabled by default)  |
15//! | `alloc` | `String`/`Vec`-returning convenience APIs               |
16//! | `serde` | Serde [`serialize`](crate::serde::serialize)/[`deserialize`](crate::serde::deserialize) helpers  |
17//!
18//! # Performance
19//!
20//! The [`encode_into`] and [`decode_into`] functions
21//! automatically dispatch to SIMD-accelerated paths (AVX2 on x86/x86_64,
22//! NEON on aarch64). When a constant-time guarantee is required, use
23//! [`encode_into_constant_time`] or [`decode_into_constant_time`].
24//!
25//! # `const fn` support
26//!
27//! [`encode_array`] and [`decode_array`] are `const fn`, enabling hex
28//! encoding and decoding at compile time.
29//!
30//! # Examples
31//!
32//! ```rust
33//! let encoded = hex::encode(b"hello");
34//! assert_eq!(encoded, "68656c6c6f");
35//!
36//! let decoded = hex::decode(b"68656c6c6f").unwrap();
37//! assert_eq!(decoded, b"hello");
38//!
39//! let upper = hex::encode_with_alphabet(b"hello", hex::Alphabet::Upper);
40//! assert_eq!(upper, "68656C6C6F");
41//! ```
42
43#[cfg(any(feature = "alloc", test))]
44extern crate alloc;
45
46#[cfg(all(feature = "serde", any(feature = "alloc", test)))]
47mod serde;
48
49#[cfg(target_arch = "aarch64")]
50mod hex_neon;
51
52#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
53mod hex_avx2;
54
55#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
56mod hex_wasm_simd128;
57
58const ALPHABET_LOWER: [u8; 16] = *b"0123456789abcdef";
59const ALPHABET_UPPER: [u8; 16] = *b"0123456789ABCDEF";
60
61/// The hex character set used for encoding.
62///
63/// | Variant  | Alphabet                    |
64/// |----------|-----------------------------|
65/// | `Lower`  | `0123456789abcdef`          |
66/// | `Upper`  | `0123456789ABCDEF`          |
67///
68/// Decoding accepts any case regardless of the encoding alphabet.
69///
70/// # Example
71///
72/// ```rust
73/// let encoded = hex::encode_with_alphabet(b"\xAB", hex::Alphabet::Lower);
74/// assert_eq!(encoded, "ab");
75///
76/// let encoded = hex::encode_with_alphabet(b"\xAB", hex::Alphabet::Upper);
77/// assert_eq!(encoded, "AB");
78/// ```
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub enum Alphabet {
81    Lower,
82    Upper,
83}
84
85/// Errors that can occur during hex decoding.
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub enum DecodeError {
88    /// The input contains a character that is not a valid hex digit
89    /// (`0-9`, `a-f`, `A-F`).
90    InvalidInput,
91    /// The input length is odd. Hex encoding requires pairs of characters.
92    InvalidInputLength,
93    /// The output buffer length does not match `input.len() / 2`.
94    InvalidOutputLength,
95}
96
97/// Errors that can occur during hex encoding.
98#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99pub enum EncodeError {
100    /// The output buffer length does not match `input.len() * 2`.
101    InvalidOutputLength,
102}
103
104impl core::fmt::Display for DecodeError {
105    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
106        match self {
107            Self::InvalidInput => f.write_str("invalid hex character"),
108            Self::InvalidInputLength => f.write_str("odd number of hex characters"),
109            Self::InvalidOutputLength => f.write_str("output buffer size must be equal to input.len() / 2"),
110        }
111    }
112}
113
114impl core::fmt::Display for EncodeError {
115    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
116        match self {
117            Self::InvalidOutputLength => f.write_str("output buffer size must be equal to input.len() * 2"),
118        }
119    }
120}
121
122#[cfg(feature = "std")]
123impl std::error::Error for DecodeError {}
124
125#[cfg(feature = "std")]
126impl std::error::Error for EncodeError {}
127
128/// Encodes bytes to a lowercase hex string.
129///
130/// This is a convenience wrapper around [`encode_with_alphabet`] using
131/// [`Alphabet::Lower`].
132///
133/// # Example
134///
135/// ```rust
136/// assert_eq!(hex::encode(b"hello"), "68656c6c6f");
137/// ```
138#[cfg(any(feature = "alloc", test))]
139#[inline]
140pub fn encode(data: impl AsRef<[u8]>) -> alloc::string::String {
141    encode_with_alphabet(data.as_ref(), Alphabet::Lower)
142}
143
144/// Encodes bytes to a hex string using the given [`Alphabet`].
145///
146/// # Example
147///
148/// ```rust
149/// assert_eq!(hex::encode_with_alphabet(b"hello", hex::Alphabet::Upper), "68656C6C6F");
150/// ```
151#[cfg(any(feature = "alloc", test))]
152#[inline]
153pub fn encode_with_alphabet(data: impl AsRef<[u8]>, alphabet: Alphabet) -> alloc::string::String {
154    let data = data.as_ref();
155    let mut output = alloc::vec![0u8; data.len() * 2];
156    encode_into(&mut output, data, alphabet).unwrap();
157    unsafe { alloc::string::String::from_utf8_unchecked(output) }
158}
159
160/// Encodes `data` into a fixed-size array at compile time.
161///
162/// The generic parameter `OUT` is the output array length. It must be exactly
163/// `data.len() * 2` long or a panic is raised.
164///
165/// # Panics
166///
167/// This function panics if `OUT != data.len() * 2`.
168///
169/// # Example
170///
171/// ```rust
172/// const HASH: [u8; 4] = [0xDE, 0xAD, 0xBE, 0xEF];
173/// const HEX: [u8; 8] = hex::encode_array::<8>(&HASH, hex::Alphabet::Lower);
174/// assert_eq!(&HEX, b"deadbeef");
175/// ```
176pub const fn encode_array<const OUT: usize>(data: &[u8], alphabet: Alphabet) -> [u8; OUT] {
177    let mut result = [0u8; OUT];
178    match encode_into_constant_time(&mut result, data, alphabet) {
179        Ok(_) => {}
180        Err(_) => panic!("output buffer size is not valid"),
181    };
182    result
183}
184
185/// Encodes bytes into an existing buffer.
186///
187/// Dispatches to a SIMD-accelerated implementation (AVX2 or NEON) when
188/// the target feature is available.
189///
190/// See [`encode_into_constant_time`] for security-sensitive and cryptographic operations.
191///
192/// # Errors
193///
194/// Returns [`EncodeError::InvalidOutputLength`] if `output.len() != data.len() * 2`.
195///
196/// # Example
197///
198/// ```rust
199/// let mut buf = [0u8; 10];
200/// hex::encode_into(&mut buf, b"hello", hex::Alphabet::Lower).unwrap();
201/// assert_eq!(&buf, b"68656c6c6f");
202/// ```
203#[inline]
204pub fn encode_into(output: &mut [u8], data: &[u8], alphabet: Alphabet) -> Result<(), EncodeError> {
205    #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
206    if data.len() >= 16 {
207        check_encode_output_length(data.len(), output.len())?;
208        return unsafe { hex_neon::encode_into(output, data, alphabet) };
209    }
210
211    #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "avx2"))]
212    if data.len() >= 32 {
213        check_encode_output_length(data.len(), output.len())?;
214        return unsafe { hex_avx2::encode_into(output, data, alphabet) };
215    }
216
217    #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
218    if data.len() >= 16 {
219        check_encode_output_length(data.len(), output.len())?;
220        return hex_wasm_simd128::encode_into(output, data, alphabet);
221    }
222
223    return encode_into_constant_time(output, data, alphabet);
224}
225
226/// Constant-time hex encoding. Processes all input data without
227/// secret-dependent branches or memory accesses, making it suitable
228/// for cryptographic applications.
229///
230/// Consumers may prefer the faster [`encode_into`] which dispatches to
231/// a SIMD-accelerated path when available (non constant-time).
232///
233/// # Example
234///
235/// ```rust
236/// let mut buf = [0u8; 10];
237/// hex::encode_into_constant_time(&mut buf, b"hello", hex::Alphabet::Lower).unwrap();
238/// assert_eq!(&buf, b"68656c6c6f");
239/// ```
240pub const fn encode_into_constant_time(output: &mut [u8], data: &[u8], alphabet: Alphabet) -> Result<(), EncodeError> {
241    match check_encode_output_length(data.len(), output.len()) {
242        Ok(_) => {}
243        Err(err) => return Err(err),
244    };
245
246    let mut i = 0;
247    let len = data.len();
248
249    while i + 16 <= len {
250        let b0 = data[i];
251        let b1 = data[i + 1];
252        let b2 = data[i + 2];
253        let b3 = data[i + 3];
254        let b4 = data[i + 4];
255        let b5 = data[i + 5];
256        let b6 = data[i + 6];
257        let b7 = data[i + 7];
258        let b8 = data[i + 8];
259        let b9 = data[i + 9];
260        let b10 = data[i + 10];
261        let b11 = data[i + 11];
262        let b12 = data[i + 12];
263        let b13 = data[i + 13];
264        let b14 = data[i + 14];
265        let b15 = data[i + 15];
266
267        let o = i * 2;
268        output[o] = nibble_to_hex(b0 >> 4, alphabet);
269        output[o + 1] = nibble_to_hex(b0 & 0x0F, alphabet);
270        output[o + 2] = nibble_to_hex(b1 >> 4, alphabet);
271        output[o + 3] = nibble_to_hex(b1 & 0x0F, alphabet);
272        output[o + 4] = nibble_to_hex(b2 >> 4, alphabet);
273        output[o + 5] = nibble_to_hex(b2 & 0x0F, alphabet);
274        output[o + 6] = nibble_to_hex(b3 >> 4, alphabet);
275        output[o + 7] = nibble_to_hex(b3 & 0x0F, alphabet);
276        output[o + 8] = nibble_to_hex(b4 >> 4, alphabet);
277        output[o + 9] = nibble_to_hex(b4 & 0x0F, alphabet);
278        output[o + 10] = nibble_to_hex(b5 >> 4, alphabet);
279        output[o + 11] = nibble_to_hex(b5 & 0x0F, alphabet);
280        output[o + 12] = nibble_to_hex(b6 >> 4, alphabet);
281        output[o + 13] = nibble_to_hex(b6 & 0x0F, alphabet);
282        output[o + 14] = nibble_to_hex(b7 >> 4, alphabet);
283        output[o + 15] = nibble_to_hex(b7 & 0x0F, alphabet);
284        output[o + 16] = nibble_to_hex(b8 >> 4, alphabet);
285        output[o + 17] = nibble_to_hex(b8 & 0x0F, alphabet);
286        output[o + 18] = nibble_to_hex(b9 >> 4, alphabet);
287        output[o + 19] = nibble_to_hex(b9 & 0x0F, alphabet);
288        output[o + 20] = nibble_to_hex(b10 >> 4, alphabet);
289        output[o + 21] = nibble_to_hex(b10 & 0x0F, alphabet);
290        output[o + 22] = nibble_to_hex(b11 >> 4, alphabet);
291        output[o + 23] = nibble_to_hex(b11 & 0x0F, alphabet);
292        output[o + 24] = nibble_to_hex(b12 >> 4, alphabet);
293        output[o + 25] = nibble_to_hex(b12 & 0x0F, alphabet);
294        output[o + 26] = nibble_to_hex(b13 >> 4, alphabet);
295        output[o + 27] = nibble_to_hex(b13 & 0x0F, alphabet);
296        output[o + 28] = nibble_to_hex(b14 >> 4, alphabet);
297        output[o + 29] = nibble_to_hex(b14 & 0x0F, alphabet);
298        output[o + 30] = nibble_to_hex(b15 >> 4, alphabet);
299        output[o + 31] = nibble_to_hex(b15 & 0x0F, alphabet);
300
301        i += 16;
302    }
303
304    while i < len {
305        let b = data[i];
306        let o = i * 2;
307        output[o] = nibble_to_hex(b >> 4, alphabet);
308        output[o + 1] = nibble_to_hex(b & 0x0F, alphabet);
309        i += 1;
310    }
311
312    Ok(())
313}
314
315#[inline]
316const fn nibble_to_hex(nibble: u8, alphabet: Alphabet) -> u8 {
317    let nibble = nibble & 0x0F;
318    let digit_mask = (((nibble as i16) - 10) >> 8) as u8;
319
320    let digit_val = b'0' + nibble;
321    let letter_val = b'a' + nibble - 10;
322    let upper_val = b'A' + nibble - 10;
323
324    let lower_result = (digit_val & digit_mask) | (letter_val & !digit_mask);
325    let upper_result = (digit_val & digit_mask) | (upper_val & !digit_mask);
326
327    match alphabet {
328        Alphabet::Lower => lower_result,
329        Alphabet::Upper => upper_result,
330    }
331}
332
333#[inline]
334const fn check_encode_output_length(data_length: usize, output_length: usize) -> Result<(), EncodeError> {
335    if data_length * 2 != output_length {
336        return Err(EncodeError::InvalidOutputLength);
337    }
338    Ok(())
339}
340
341/// Appends the hex-encoded representation of `data` to a [`String`].
342///
343/// # Example
344///
345/// ```rust
346/// let mut s = String::from("tag: ");
347/// hex::encode_into_string(&mut s, b"hello", hex::Alphabet::Lower);
348/// assert_eq!(s, "tag: 68656c6c6f");
349/// ```
350#[cfg(feature = "alloc")]
351pub fn encode_into_string(output: &mut alloc::string::String, data: &[u8], alphabet: Alphabet) {
352    let encoded_length = data.len() * 2;
353    if encoded_length <= 256 {
354        // zero-alloc version for small data
355        let mut buf = [0u8; 256];
356        let mut buf = &mut buf[..encoded_length];
357        encode_into(&mut buf, data, alphabet).unwrap();
358        // SAFETY: base64 only produces ASCII characters, which are valid UTF-8.
359        output.push_str(unsafe { core::str::from_utf8_unchecked(&buf) });
360    } else {
361        let mut buf = alloc::vec![0u8; encoded_length];
362        encode_into(&mut buf, data, alphabet).unwrap();
363        // SAFETY: base64 only produces ASCII characters, which are valid UTF-8.
364        output.push_str(unsafe { core::str::from_utf8_unchecked(&buf) });
365    }
366}
367
368////////////////////////////////////////////////////////////////////////////////////////////////////
369/// Decode
370////////////////////////////////////////////////////////////////////////////////////////////////////
371
372/// Decodes a hex string into bytes.
373///
374/// Accepts any combination of uppercase and lowercase hex characters.
375///
376/// # Errors
377///
378/// Returns [`DecodeError`] if any character is not a valid or if the input has an
379/// odd number of characters.
380///
381/// # Example
382///
383/// ```rust
384/// let decoded = hex::decode(b"68656c6c6f").unwrap();
385/// assert_eq!(decoded, b"hello");
386/// ```
387#[cfg(any(feature = "alloc", test))]
388pub fn decode(data: impl AsRef<[u8]>) -> Result<alloc::vec::Vec<u8>, DecodeError> {
389    let data = data.as_ref();
390    let mut output = alloc::vec![0u8; data.len() / 2];
391    decode_into(&mut output, data)?;
392    Ok(output)
393}
394
395/// Decodes a hex string into a fixed-size array at compile time.
396///
397/// The generic parameter `OUT` is the output array length. It must be exactly
398/// `data.len() / 2` bytes long or an error panic is returned.
399///
400/// # Example
401///
402/// ```rust
403/// const RESULT: Result<[u8; 4], hex::DecodeError> =
404///     hex::decode_array::<4>(b"deadbeef");
405/// assert_eq!(RESULT.unwrap(), [0xDE, 0xAD, 0xBE, 0xEF]);
406/// ```
407pub const fn decode_array<const OUT: usize>(encoded_data: &[u8]) -> Result<[u8; OUT], DecodeError> {
408    let mut result = [0u8; OUT];
409    match decode_into_constant_time(&mut result, encoded_data) {
410        Ok(_) => {}
411        Err(err) => return Err(err),
412    }
413    Ok(result)
414}
415
416/// Decodes a hex string into an existing buffer.
417///
418/// Dispatches to a SIMD-accelerated implementation (AVX2 or NEON) when
419/// the target feature is available.
420///
421/// Accepts any combination of uppercase and lowercase hex characters.
422///
423/// See [`decode_into_constant_time`] for security-sensitive and cryptographic operations.
424///
425/// # Errors
426///
427/// Returns [`DecodeError`] if any character is not a valid hex digit or if the input length is odd
428/// or if `output.len() != encoded_data.len() / 2`.
429///
430/// # Example
431///
432/// ```rust
433/// let mut buf = [0u8; 5];
434/// hex::decode_into(&mut buf, b"68656c6c6f").unwrap();
435/// assert_eq!(&buf, b"hello");
436/// ```
437pub fn decode_into(output: &mut [u8], encoded_data: &[u8]) -> Result<(), DecodeError> {
438    #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
439    if encoded_data.len() >= 32 {
440        check_decode_input_and_output_length(encoded_data.len(), output.len())?;
441        return unsafe { hex_neon::decode_into(output, encoded_data) };
442    }
443
444    #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "avx2"))]
445    if encoded_data.len() >= 32 {
446        check_decode_input_and_output_length(encoded_data.len(), output.len())?;
447        return unsafe { hex_avx2::decode_into(output, encoded_data) };
448    }
449
450    #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
451    if encoded_data.len() >= 32 {
452        check_decode_input_and_output_length(encoded_data.len(), output.len())?;
453        return hex_wasm_simd128::decode_into(output, encoded_data);
454    }
455
456    decode_into_constant_time(output, encoded_data)
457}
458
459/// Constant-time hex decoding. Processes all input data without
460/// secret-dependent branches or memory accesses, making it suitable
461/// for cryptographic applications.
462///
463/// Note that `output` is written even when `Err` is returned, which is
464/// required for constant-time operation. Callers must not rely on the
465/// contents of `output` when an error is returned.
466///
467/// Consumers may prefer the faster [`decode_into`] which dispatches to
468/// a SIMD-accelerated path when available (non constant-time).
469///
470/// # Example
471///
472/// ```rust
473/// let mut buf = [0u8; 5];
474/// hex::decode_into_constant_time(&mut buf, b"68656c6c6f").unwrap();
475/// assert_eq!(&buf, b"hello");
476/// ```
477pub const fn decode_into_constant_time(output: &mut [u8], encoded_data: &[u8]) -> Result<(), DecodeError> {
478    match check_decode_input_and_output_length(encoded_data.len(), output.len()) {
479        Ok(_) => {}
480        Err(err) => return Err(err),
481    };
482
483    let in_len = encoded_data.len();
484    let mut i = 0;
485    let mut err: u8 = 0;
486
487    while i + 32 <= in_len {
488        let h0 = nibble_from_hex(encoded_data[i]);
489        let l0 = nibble_from_hex(encoded_data[i + 1]);
490        err |= h0 | l0;
491        output[i / 2] = (h0 << 4) | l0;
492
493        let h1 = nibble_from_hex(encoded_data[i + 2]);
494        let l1 = nibble_from_hex(encoded_data[i + 3]);
495        err |= h1 | l1;
496        output[i / 2 + 1] = (h1 << 4) | l1;
497
498        let h2 = nibble_from_hex(encoded_data[i + 4]);
499        let l2 = nibble_from_hex(encoded_data[i + 5]);
500        err |= h2 | l2;
501        output[i / 2 + 2] = (h2 << 4) | l2;
502
503        let h3 = nibble_from_hex(encoded_data[i + 6]);
504        let l3 = nibble_from_hex(encoded_data[i + 7]);
505        err |= h3 | l3;
506        output[i / 2 + 3] = (h3 << 4) | l3;
507
508        let h4 = nibble_from_hex(encoded_data[i + 8]);
509        let l4 = nibble_from_hex(encoded_data[i + 9]);
510        err |= h4 | l4;
511        output[i / 2 + 4] = (h4 << 4) | l4;
512
513        let h5 = nibble_from_hex(encoded_data[i + 10]);
514        let l5 = nibble_from_hex(encoded_data[i + 11]);
515        err |= h5 | l5;
516        output[i / 2 + 5] = (h5 << 4) | l5;
517
518        let h6 = nibble_from_hex(encoded_data[i + 12]);
519        let l6 = nibble_from_hex(encoded_data[i + 13]);
520        err |= h6 | l6;
521        output[i / 2 + 6] = (h6 << 4) | l6;
522
523        let h7 = nibble_from_hex(encoded_data[i + 14]);
524        let l7 = nibble_from_hex(encoded_data[i + 15]);
525        err |= h7 | l7;
526        output[i / 2 + 7] = (h7 << 4) | l7;
527
528        let h8 = nibble_from_hex(encoded_data[i + 16]);
529        let l8 = nibble_from_hex(encoded_data[i + 17]);
530        err |= h8 | l8;
531        output[i / 2 + 8] = (h8 << 4) | l8;
532
533        let h9 = nibble_from_hex(encoded_data[i + 18]);
534        let l9 = nibble_from_hex(encoded_data[i + 19]);
535        err |= h9 | l9;
536        output[i / 2 + 9] = (h9 << 4) | l9;
537
538        let h10 = nibble_from_hex(encoded_data[i + 20]);
539        let l10 = nibble_from_hex(encoded_data[i + 21]);
540        err |= h10 | l10;
541        output[i / 2 + 10] = (h10 << 4) | l10;
542
543        let h11 = nibble_from_hex(encoded_data[i + 22]);
544        let l11 = nibble_from_hex(encoded_data[i + 23]);
545        err |= h11 | l11;
546        output[i / 2 + 11] = (h11 << 4) | l11;
547
548        let h12 = nibble_from_hex(encoded_data[i + 24]);
549        let l12 = nibble_from_hex(encoded_data[i + 25]);
550        err |= h12 | l12;
551        output[i / 2 + 12] = (h12 << 4) | l12;
552
553        let h13 = nibble_from_hex(encoded_data[i + 26]);
554        let l13 = nibble_from_hex(encoded_data[i + 27]);
555        err |= h13 | l13;
556        output[i / 2 + 13] = (h13 << 4) | l13;
557
558        let h14 = nibble_from_hex(encoded_data[i + 28]);
559        let l14 = nibble_from_hex(encoded_data[i + 29]);
560        err |= h14 | l14;
561        output[i / 2 + 14] = (h14 << 4) | l14;
562
563        let h15 = nibble_from_hex(encoded_data[i + 30]);
564        let l15 = nibble_from_hex(encoded_data[i + 31]);
565        err |= h15 | l15;
566        output[i / 2 + 15] = (h15 << 4) | l15;
567
568        i += 32;
569    }
570
571    while i < in_len {
572        let h = nibble_from_hex(encoded_data[i]);
573        let l = nibble_from_hex(encoded_data[i + 1]);
574        err |= h | l;
575        output[i / 2] = (h << 4) | l;
576        i += 2;
577    }
578
579    if err & 0xF0 != 0 {
580        return Err(DecodeError::InvalidInput);
581    }
582
583    Ok(())
584}
585
586#[inline]
587const fn nibble_from_hex(c: u8) -> u8 {
588    let is_digit = ((((c as i16) - (b'0' as i16)) | ((b'9' as i16) - (c as i16))) >> 8) as u8;
589    let is_lower = ((((c as i16) - (b'a' as i16)) | ((b'f' as i16) - (c as i16))) >> 8) as u8;
590    let is_upper = ((((c as i16) - (b'A' as i16)) | ((b'F' as i16) - (c as i16))) >> 8) as u8;
591
592    let digit_val = c.wrapping_sub(b'0');
593    let lower_val = c.wrapping_sub(b'a').wrapping_add(10);
594    let upper_val = c.wrapping_sub(b'A').wrapping_add(10);
595
596    let value = (digit_val & !is_digit) | (lower_val & !is_lower) | (upper_val & !is_upper);
597    let invalid = is_digit & is_lower & is_upper;
598
599    value | (invalid & 0xF0)
600}
601
602#[inline]
603const fn check_decode_input_and_output_length(encoded_length: usize, output_length: usize) -> Result<(), DecodeError> {
604    if encoded_length % 2 != 0 {
605        return Err(DecodeError::InvalidInputLength);
606    }
607    if output_length != encoded_length / 2 {
608        return Err(DecodeError::InvalidOutputLength);
609    }
610
611    Ok(())
612}
613
614#[cfg(test)]
615mod tests {
616    use super::*;
617
618    #[test]
619    fn encode_empty() {
620        assert_eq!(encode(b""), "");
621        let mut out = [0u8; 0];
622        encode_into(&mut out, b"", Alphabet::Lower).unwrap();
623    }
624
625    #[test]
626    fn encode_single_byte() {
627        assert_eq!(encode(b"\x00"), "00");
628        assert_eq!(encode(b"\xFF"), "ff");
629        assert_eq!(encode(b"\xAB"), "ab");
630        assert_eq!(encode_with_alphabet(b"\xAB", Alphabet::Upper), "AB");
631    }
632
633    #[test]
634    fn encode_multiple_bytes() {
635        assert_eq!(encode(b"hello"), "68656c6c6f");
636        assert_eq!(encode_with_alphabet(b"hello", Alphabet::Upper), "68656C6C6F");
637        assert_eq!(
638            encode(b"\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF"),
639            "00112233445566778899aabbccddeeff"
640        );
641    }
642
643    #[test]
644    fn encode_all_bytes() {
645        let data: Vec<u8> = (0..=255).collect();
646        let hex = encode(&data);
647        assert_eq!(hex.len(), 512);
648        for (i, &b) in data.iter().enumerate() {
649            let hi = ALPHABET_LOWER[(b >> 4) as usize];
650            let lo = ALPHABET_LOWER[(b & 0x0F) as usize];
651            assert_eq!(hex.as_bytes()[i * 2], hi);
652            assert_eq!(hex.as_bytes()[i * 2 + 1], lo);
653        }
654    }
655
656    #[test]
657    fn encode_into_exact_buffer() {
658        let mut out = [0u8; 4];
659        encode_into(&mut out, b"\xDE\xAD", Alphabet::Upper).unwrap();
660        assert_eq!(&out, b"DEAD");
661    }
662
663    #[test]
664    fn decode_empty() {
665        assert_eq!(decode(b"").unwrap(), b"");
666    }
667
668    #[test]
669    fn decode_single_byte() {
670        assert_eq!(decode(b"00").unwrap(), b"\x00");
671        assert_eq!(decode(b"ff").unwrap(), b"\xFF");
672        assert_eq!(decode(b"FF").unwrap(), b"\xFF");
673        assert_eq!(decode(b"ab").unwrap(), b"\xAB");
674        assert_eq!(decode(b"AB").unwrap(), b"\xAB");
675    }
676
677    #[test]
678    fn decode_multiple_bytes() {
679        assert_eq!(decode(b"68656c6c6f").unwrap(), b"hello");
680        assert_eq!(
681            decode(b"00112233445566778899AABBCCDDEEFF").unwrap(),
682            b"\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF"
683        );
684    }
685
686    #[test]
687    fn decode_into_exact_buffer() {
688        let mut out = [0u8; 2];
689        decode_into(&mut out, b"DEAD").unwrap();
690        assert_eq!(&out, b"\xDE\xAD");
691    }
692
693    #[test]
694    fn decode_invalid_character() {
695        assert_eq!(decode(b"0g"), Err(DecodeError::InvalidInput));
696        assert_eq!(decode(b"GG"), Err(DecodeError::InvalidInput));
697        assert_eq!(decode(b"  "), Err(DecodeError::InvalidInput));
698    }
699
700    #[test]
701    fn decode_odd_length() {
702        assert_eq!(decode(b"0"), Err(DecodeError::InvalidInputLength));
703        assert_eq!(decode(b"abc"), Err(DecodeError::InvalidInputLength));
704    }
705
706    #[test]
707    fn decode_trailing_invalid_in_large_buffer() {
708        let mut input = alloc::vec![b'0'; 64];
709        input[63] = b'g';
710        assert_eq!(decode(&input), Err(DecodeError::InvalidInput));
711    }
712
713    #[test]
714    fn roundtrip() {
715        let data: Vec<u8> = (0..=255).cycle().take(1024).collect();
716        let hex = encode(&data);
717        let decoded = decode(hex.as_bytes()).unwrap();
718        assert_eq!(decoded, data);
719    }
720
721    #[test]
722    fn roundtrip_upper() {
723        let data: Vec<u8> = (0..=255).cycle().take(1024).collect();
724        let hex = encode_with_alphabet(&data, Alphabet::Upper);
725        let decoded = decode(&hex).unwrap();
726        assert_eq!(decoded, data);
727    }
728
729    #[test]
730    fn roundtrip_various_sizes() {
731        for len in [0, 1, 2, 3, 4, 5, 15, 16, 17, 31, 32, 33, 63, 64, 65, 95, 96] {
732            let data: Vec<u8> = (0..len as u8).collect();
733            let hex = encode(&data);
734            let decoded = decode(hex.as_bytes()).unwrap();
735            assert_eq!(decoded, data, "roundtrip failed for len={}", len);
736        }
737    }
738
739    #[test]
740    fn decode_case_insensitivity() {
741        assert_eq!(decode(b"abcdef"), decode(b"ABCDEF"));
742        assert_eq!(decode(b"AbCdEf"), decode(b"aBcDeF"));
743    }
744
745    #[test]
746    fn rfc4648_test_vectors_encode() {
747        let vectors = [
748            (b"" as &[u8], ""),
749            (b"f", "66"),
750            (b"fo", "666F"),
751            (b"foo", "666F6F"),
752            (b"foob", "666F6F62"),
753            (b"fooba", "666F6F6261"),
754            (b"foobar", "666F6F626172"),
755        ];
756        for (input, expected) in &vectors {
757            assert_eq!(encode_with_alphabet(input, Alphabet::Upper), *expected);
758        }
759    }
760
761    #[test]
762    fn rfc4648_test_vectors_decode() {
763        let vectors = [
764            ("", b"" as &[u8]),
765            ("66", b"f"),
766            ("666F", b"fo"),
767            ("666F6F", b"foo"),
768            ("666F6F62", b"foob"),
769            ("666F6F6261", b"fooba"),
770            ("666F6F626172", b"foobar"),
771        ];
772        for (hex_str, expected) in &vectors {
773            assert_eq!(decode(hex_str.as_bytes()).unwrap(), *expected);
774        }
775    }
776
777    #[test]
778    fn rfc4648_test_vectors_lowercase() {
779        let vectors = [
780            ("66", b"f" as &[u8]),
781            ("666f", b"fo" as &[u8]),
782            ("666f6f", b"foo" as &[u8]),
783            ("666f6f62", b"foob" as &[u8]),
784            ("666f6f6261", b"fooba" as &[u8]),
785            ("666f6f626172", b"foobar" as &[u8]),
786        ];
787        for (hex_str, expected) in &vectors {
788            assert_eq!(decode(hex_str.as_bytes()).unwrap(), *expected);
789        }
790    }
791
792    #[test]
793    fn simd_boundary_nonuniform() {
794        let sizes = [
795            0, 1, 2, 3, 15, 16, 17, 31, 32, 33, 63, 64, 65, 95, 96, 127, 128, 129, 255, 256, 257,
796        ];
797        for &len in &sizes {
798            let data: Vec<u8> = (0..len)
799                .map(|i: usize| (i.wrapping_mul(17).wrapping_add(0xAB)) as u8)
800                .collect();
801            let hex = encode(&data);
802            let decoded = decode(hex.as_bytes()).unwrap();
803            assert_eq!(decoded, data, "non-uniform roundtrip failed for len={}", len);
804        }
805    }
806
807    #[test]
808    fn decode_into_too_small() {
809        let mut out = [0u8; 1];
810        assert_eq!(decode_into(&mut out, b"0000"), Err(DecodeError::InvalidOutputLength));
811    }
812
813    #[cfg(not(target_arch = "wasm32"))]
814    #[test]
815    fn encode_into_panics_on_too_small() {
816        use std::panic::{AssertUnwindSafe, catch_unwind};
817        let mut out = [0u8; 1];
818        let result = catch_unwind(AssertUnwindSafe(|| {
819            encode_into(&mut out, b"hello", Alphabet::Lower).unwrap();
820        }));
821        assert!(result.is_err());
822    }
823
824    #[cfg(feature = "serde")]
825    #[test]
826    fn serde_roundtrip() {
827        #[derive(::serde::Serialize, ::serde::Deserialize)]
828        struct Data(#[serde(with = "crate::serde")] Vec<u8>);
829
830        let data = Data(b"hello world".to_vec());
831        let json = ::serde_json::to_string(&data).unwrap();
832        assert_eq!(json, "\"68656c6c6f20776f726c64\"");
833        let deserialized: Data = ::serde_json::from_str(&json).unwrap();
834        assert_eq!(deserialized.0, b"hello world");
835    }
836
837    #[test]
838    fn const_encode() {
839        const DATA: [u8; 4] = [0xDE, 0xAD, 0xBE, 0xEF];
840        const HEX: [u8; 8] = encode_array::<8>(&DATA, Alphabet::Lower);
841        const HEX_UPPER: [u8; 8] = encode_array::<8>(&DATA, Alphabet::Upper);
842        assert_eq!(&HEX, b"deadbeef");
843        assert_eq!(&HEX_UPPER, b"DEADBEEF");
844    }
845
846    #[test]
847    fn const_encode_empty() {
848        const HEX: [u8; 0] = encode_array::<0>(b"", Alphabet::Lower);
849        assert_eq!(HEX.len(), 0);
850    }
851
852    #[test]
853    fn const_decode() {
854        const RESULT: Result<[u8; 4], DecodeError> = decode_array::<4>(b"deadbeef");
855        assert_eq!(RESULT.unwrap(), [0xDE, 0xAD, 0xBE, 0xEF]);
856    }
857
858    #[test]
859    fn const_decode_empty() {
860        const RESULT: Result<[u8; 0], DecodeError> = decode_array::<0>(b"");
861        assert_eq!(RESULT.unwrap().len(), 0);
862    }
863
864    #[test]
865    fn const_decode_upper() {
866        const RESULT: Result<[u8; 4], DecodeError> = decode_array::<4>(b"DEADBEEF");
867        assert_eq!(RESULT.unwrap(), [0xDE, 0xAD, 0xBE, 0xEF]);
868    }
869
870    #[test]
871    fn const_decode_invalid_character() {
872        const ERR: Result<[u8; 1], DecodeError> = decode_array::<1>(b"0g");
873        assert_eq!(ERR, Err(DecodeError::InvalidInput));
874    }
875
876    #[test]
877    fn const_decode_odd_length() {
878        const ERR: Result<[u8; 0], DecodeError> = decode_array::<0>(b"0");
879        assert_eq!(ERR, Err(DecodeError::InvalidInputLength));
880    }
881
882    #[test]
883    fn const_decode_wrong_output_size() {
884        const ERR: Result<[u8; 2], DecodeError> = decode_array::<2>(b"00");
885        assert_eq!(ERR, Err(DecodeError::InvalidOutputLength));
886    }
887
888    #[test]
889    fn encode_into_string_empty() {
890        let mut s = alloc::string::String::new();
891        encode_into_string(&mut s, b"", Alphabet::Lower);
892        assert_eq!(s, "");
893    }
894
895    #[test]
896    fn encode_into_string_empty_data_nonempty_output() {
897        let mut s = alloc::string::String::from("prefix");
898        encode_into_string(&mut s, b"", Alphabet::Lower);
899        assert_eq!(s, "prefix");
900    }
901
902    #[test]
903    fn encode_into_string_single_byte() {
904        let mut s = alloc::string::String::new();
905        encode_into_string(&mut s, b"\x00", Alphabet::Lower);
906        assert_eq!(s, "00");
907        let mut s = alloc::string::String::new();
908        encode_into_string(&mut s, b"\xFF", Alphabet::Upper);
909        assert_eq!(s, "FF");
910    }
911
912    #[test]
913    fn encode_into_string_multiple_bytes() {
914        let mut s = alloc::string::String::new();
915        encode_into_string(&mut s, b"hello", Alphabet::Lower);
916        assert_eq!(s, "68656c6c6f");
917        let mut s = alloc::string::String::new();
918        encode_into_string(&mut s, b"hello", Alphabet::Upper);
919        assert_eq!(s, "68656C6C6F");
920    }
921
922    #[test]
923    fn encode_into_string_append() {
924        let mut s = alloc::string::String::from("~~");
925        encode_into_string(&mut s, b"\xDE\xAD", Alphabet::Lower);
926        assert_eq!(s, "~~dead");
927        encode_into_string(&mut s, b"\xBE\xEF", Alphabet::Lower);
928        assert_eq!(s, "~~deadbeef");
929    }
930
931    #[test]
932    fn encode_into_string_large() {
933        let data: Vec<u8> = (0..255).cycle().take(4096).collect();
934        let expected = encode_with_alphabet(&data, Alphabet::Lower);
935        let mut s = alloc::string::String::new();
936        encode_into_string(&mut s, &data, Alphabet::Lower);
937        assert_eq!(s, expected);
938    }
939
940    #[test]
941    fn encode_into_string_roundtrip() {
942        let data: Vec<u8> = (0..=255).collect();
943        let mut s = alloc::string::String::new();
944        encode_into_string(&mut s, &data, Alphabet::Lower);
945        let decoded = decode(s.as_bytes()).unwrap();
946        assert_eq!(decoded, data);
947    }
948
949    #[test]
950    fn encode_into_string_small_boundary() {
951        let mut s = alloc::string::String::new();
952        encode_into_string(
953            &mut s,
954            b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F",
955            Alphabet::Lower,
956        );
957        assert_eq!(s, "000102030405060708090a0b0c0d0e0f");
958    }
959
960    #[test]
961    fn encode_into_string_all_alphabets() {
962        let data = b"hello world";
963        for alphabet in &[Alphabet::Lower, Alphabet::Upper] {
964            let expected = encode_with_alphabet(data, *alphabet);
965            let mut s = alloc::string::String::new();
966            encode_into_string(&mut s, data, *alphabet);
967            assert_eq!(s, expected, "mismatch for alphabet {alphabet:?}");
968        }
969    }
970
971    #[test]
972    fn encode_into_string_rfc4648_vectors() {
973        let vectors = [
974            (b"" as &[u8], "", ""),
975            (b"f", "66", "66"),
976            (b"fo", "666f", "666F"),
977            (b"foo", "666f6f", "666F6F"),
978            (b"foob", "666f6f62", "666F6F62"),
979            (b"fooba", "666f6f6261", "666F6F6261"),
980            (b"foobar", "666f6f626172", "666F6F626172"),
981        ];
982        for (input, expected_lower, expected_upper) in &vectors {
983            let mut s = alloc::string::String::new();
984            encode_into_string(&mut s, input, Alphabet::Lower);
985            assert_eq!(s, *expected_lower);
986            let mut s = alloc::string::String::new();
987            encode_into_string(&mut s, input, Alphabet::Upper);
988            assert_eq!(s, *expected_upper);
989        }
990    }
991
992    #[test]
993    fn encode_into_string_exact_stack_capacity() {
994        let data: Vec<u8> = (0..128).collect();
995        let expected = encode(&data);
996        let mut s = alloc::string::String::new();
997        encode_into_string(&mut s, &data, Alphabet::Lower);
998        assert_eq!(s, expected);
999    }
1000
1001    #[test]
1002    fn encode_into_string_exceeds_stack_capacity() {
1003        let data: Vec<u8> = (0..129).collect();
1004        let expected = encode(&data);
1005        let mut s = alloc::string::String::new();
1006        encode_into_string(&mut s, &data, Alphabet::Lower);
1007        assert_eq!(s, expected);
1008    }
1009}