Skip to main content

hex/
hex.rs

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