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