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