Skip to main content

fashex/
lib.rs

1#![doc = include_str!("../README.md")]
2#![no_std]
3#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
4#![cfg_attr(docsrs, feature(doc_cfg))]
5#![cfg_attr(
6    all(feature = "nightly", feature = "portable-simd"),
7    feature(portable_simd)
8)]
9#![cfg_attr(
10    all(
11        feature = "nightly",
12        feature = "experimental-loongarch-simd",
13        any(target_arch = "loongarch32", target_arch = "loongarch64")
14    ),
15    feature(stdarch_loongarch)
16)]
17
18#[cfg(any(test, feature = "alloc"))]
19extern crate alloc;
20
21#[cfg(any(test, feature = "std"))]
22extern crate std;
23
24mod backend;
25#[cfg(not(docsrs))]
26mod buffer;
27#[cfg(docsrs)]
28pub mod buffer;
29#[cfg(feature = "alloc")]
30mod display;
31mod error;
32#[cfg(feature = "__internal_fuzz")]
33pub mod fuzz;
34mod traits;
35mod util;
36
37use core::mem::MaybeUninit;
38use core::{slice, str};
39
40use crate::buffer::BufMut;
41#[cfg(feature = "alloc")]
42pub use crate::display::Display;
43pub use crate::error::InvalidInput;
44pub use crate::traits::Hex;
45pub use crate::util::{HEX_CHARS_LOWER, HEX_CHARS_UPPER};
46
47/// Encodes `src` as a hexadecimal string into `dst` and returns a reference
48/// to the written string.
49///
50/// ```rust
51/// let decoded = b"Hello, world!";
52///
53/// let mut encoded = Vec::with_capacity(decoded.len() * 2);
54///
55/// assert_eq!(
56///     fashex::encode::<false>(decoded, &mut encoded)
57///         .expect("pre-allocated capacity is sufficient"),
58///     "48656c6c6f2c20776f726c6421"
59/// );
60/// # assert_eq!(&*encoded, b"48656c6c6f2c20776f726c6421");
61///
62/// let mut encoded = Vec::with_capacity(decoded.len() * 2);
63/// assert_eq!(
64///     fashex::encode::<true>(decoded, &mut encoded)
65///         .expect("pre-allocated capacity is sufficient"),
66///     "48656C6C6F2C20776F726C6421"
67/// );
68/// # assert_eq!(&*encoded, b"48656C6C6F2C20776F726C6421");
69/// ```
70///
71/// If you like, `&mut [MaybeUninit<u8>]` can also be used as the output buffer:
72///
73/// ```rust
74/// use core::mem::MaybeUninit;
75///
76/// const CASE: &[u8; 13] = b"Hello, world!";
77///
78/// let mut encoded = [MaybeUninit::uninit(); CASE.len() * 2 + 1];
79///
80/// assert_eq!(
81///     fashex::encode::<false>(CASE, &mut encoded)
82///         .expect("the len of the uninitialized buffer is sufficient"),
83///     "48656c6c6f2c20776f726c6421"
84/// );
85///
86/// assert_eq!(
87///     unsafe { &*(&raw const encoded[..CASE.len() * 2] as *const [u8] as *const str) },
88///     "48656c6c6f2c20776f726c6421"
89/// );
90/// ```
91///
92/// ## Errors
93///
94/// 1. The len (spare capacity) of the output buffer is insufficient.
95pub fn encode<const UPPER: bool>(
96    src: impl AsRef<[u8]>,
97    dst: &mut (impl BufMut + ?Sized),
98) -> Result<&str, InvalidInput> {
99    let src = src.as_ref();
100
101    {
102        let Some(dst) = dst.as_uninit_mut().get_mut(..2 * src.len()) else {
103            return Err(InvalidInput);
104        };
105
106        #[allow(unsafe_code, reason = "The length of `dst` must be even.")]
107        let dst = unsafe { dst.as_chunks_unchecked_mut() };
108
109        #[allow(
110            unsafe_code,
111            reason = "The length of the input and the output buffer has been validated."
112        )]
113        unsafe {
114            crate::backend::encode_unchecked::<UPPER>(src, dst);
115        }
116    }
117
118    #[allow(
119        unsafe_code,
120        reason = "`src.len() * 2` bytes in `dst` have been initialized."
121    )]
122    let bytes = unsafe { dst.advance(2 * src.len()) };
123
124    #[allow(
125        unsafe_code,
126        reason = "Hexadecimal characters must be valid UTF-8 bytes."
127    )]
128    unsafe {
129        Ok(str::from_utf8_unchecked(bytes))
130    }
131}
132
133#[cfg(feature = "__internal_cargo_asm")]
134#[cfg_attr(coverage_nightly, coverage(off))]
135#[doc(hidden)]
136pub fn __cargo_asm_encode<'dst>(
137    src: &[u8],
138    dst: &'dst mut [MaybeUninit<u8>],
139) -> Result<&'dst str, InvalidInput> {
140    encode::<false>(src, dst)
141}
142
143/// Decodes `src` (a hexadecimal string, case insensitive) into raw bytes,
144/// writes them into `dst`, and returns a reference to the written bytes.
145///
146/// ```rust
147/// let encoded = b"48656c6c6f2c20776f726c6421";
148///
149/// let mut decoded = Vec::with_capacity(encoded.len() / 2);
150///
151/// assert_eq!(
152///     fashex::decode(encoded, &mut decoded)
153///         .expect("the input is valid hex and the spare capacity of the buffer is sufficient"),
154///     b"Hello, world!"
155/// );
156/// # assert_eq!(&*decoded, b"Hello, world!");
157/// ```
158///
159/// If you like, `&mut [MaybeUninit<u8>]` can also be used as the output buffer:
160///
161/// ```rust
162/// use core::mem::MaybeUninit;
163///
164/// const CASE: &[u8; 26] = b"48656c6c6f2c20776f726c6421";
165///
166/// let mut decoded = [MaybeUninit::uninit(); CASE.len() / 2 + 1];
167///
168/// assert_eq!(
169///     fashex::decode(CASE, &mut decoded)
170///         .expect("the input is valid hex and the len of the uninitialized buffer is sufficient"),
171///     b"Hello, world!"
172/// );
173///
174/// assert_eq!(
175///     unsafe { &*(&raw const decoded[..CASE.len() / 2] as *const [u8]) },
176///     b"Hello, world!"
177/// );
178/// ```
179///
180/// ## Errors
181///
182/// 1. The input contains invalid characters.
183/// 1. The input has an odd length.
184/// 1. The len (spare capacity) of the output buffer is insufficient.
185pub fn decode(
186    src: impl AsRef<[u8]>,
187    dst: &mut (impl BufMut + ?Sized),
188) -> Result<&[u8], InvalidInput> {
189    const EVEN: usize = 2;
190
191    let src = src.as_ref();
192
193    if !src.len().is_multiple_of(EVEN) {
194        // Cannot have odd nibbles.
195        return Err(InvalidInput);
196    }
197
198    #[allow(unsafe_code, reason = "XXX")]
199    // SAFETY: the length of the input has been validated to be even.
200    let src = unsafe { src.as_chunks_unchecked::<EVEN>() };
201
202    if src.len() > dst.uninit_len() {
203        // The nibble pairs cannot fit in the output buffer.
204        return Err(InvalidInput);
205    }
206
207    #[allow(unsafe_code, reason = "XXX")]
208    unsafe {
209        // SAFETY: the length of the input and the output buffer has been validated.
210        match crate::backend::decode_unchecked::<false>(src, dst.as_uninit_mut()) {
211            Ok(()) => Ok(dst.advance(src.len())),
212            Err(e) => Err(e),
213        }
214    }
215}
216
217#[cfg(feature = "__internal_cargo_asm")]
218#[cfg_attr(coverage_nightly, coverage(off))]
219#[doc(hidden)]
220pub fn __cargo_asm_decode<'dst>(
221    src: &[u8],
222    dst: &'dst mut [MaybeUninit<u8>],
223) -> Result<&'dst [u8], InvalidInput> {
224    decode(src, dst)
225}
226
227/// [`encode()`], but const-evaluable at the cost of performance.
228///
229/// The [`encode!`] macro wraps this function and is the preferred way to call
230/// it in const contexts, such as when initializing a `const` or `static`
231/// variable.
232///
233/// ## Errors
234///
235/// 1. The len of the output buffer is insufficient.
236pub const fn encode_generic<'dst, const UPPER: bool>(
237    src: &[u8],
238    dst: &'dst mut [MaybeUninit<u8>],
239) -> Result<&'dst str, InvalidInput> {
240    let (dst, _) = dst.as_chunks_mut::<2>();
241
242    if src.len() > dst.len() {
243        return Err(InvalidInput);
244    }
245
246    #[allow(unsafe_code, reason = "The length is validated")]
247    unsafe {
248        crate::backend::generic::encode_generic_unchecked::<UPPER>(src, dst);
249    };
250
251    #[allow(
252        unsafe_code,
253        reason = "`src.len() * 2` bytes in `dst` have been initialized."
254    )]
255    let bytes = unsafe { slice::from_raw_parts(dst.as_ptr().cast(), src.len().unchecked_mul(2)) };
256
257    #[allow(
258        unsafe_code,
259        reason = "Hexadecimal characters must be valid UTF-8 bytes."
260    )]
261    unsafe {
262        Ok(str::from_utf8_unchecked(bytes))
263    }
264}
265
266/// [`decode()`], but const-evaluable at the cost of performance.
267///
268/// The [`decode!`] macro wraps this function and is the preferred way to call
269/// it in const contexts, such as when initializing a `const` or `static`
270/// variable.
271///
272/// ## Errors
273///
274/// 1. The input contains invalid characters.
275/// 1. The input contains an odd number of nibbles.
276/// 1. The len of the output buffer is insufficient.
277pub const fn decode_generic<'dst>(
278    src: &[u8],
279    dst: &'dst mut [MaybeUninit<u8>],
280) -> Result<&'dst [u8], InvalidInput> {
281    const EVEN: usize = 2;
282
283    if !src.len().is_multiple_of(EVEN) {
284        // Cannot have odd nibbles.
285        return Err(InvalidInput);
286    }
287
288    #[allow(unsafe_code, reason = "XXX")]
289    // SAFETY: the length of the input has been validated to be even.
290    let src = unsafe { src.as_chunks_unchecked::<EVEN>() };
291
292    if src.len() > dst.len() {
293        // The nibble pairs cannot fit in the output buffer.
294        return Err(InvalidInput);
295    }
296
297    #[allow(unsafe_code, reason = "The length is validated")]
298    unsafe {
299        match crate::backend::generic::decode_generic_unchecked::<false>(src, dst) {
300            Ok(()) => Ok(slice::from_raw_parts(dst.as_ptr().cast(), src.len())),
301            Err(e) => Err(e),
302        }
303    }
304}
305
306#[macro_export]
307/// Helper macro for encoding byte arrays as hexadecimal strings in const
308/// contexts.
309///
310/// ## Examples
311///
312/// ```rust
313/// const HELLO_WORLD_LOWERCASE: &str = fashex::encode!(b"Hello, world!");
314/// assert_eq!(HELLO_WORLD_LOWERCASE, "48656c6c6f2c20776f726c6421");
315/// const HELLO_WORLD_UPPERCASE: &str = fashex::encode!(b"Hello, world!", true);
316/// assert_eq!(HELLO_WORLD_UPPERCASE, "48656C6C6F2C20776F726C6421");
317/// # const HELLO_WORLD_STR: &str = fashex::encode!("Hello, world!");
318/// # assert_eq!(HELLO_WORLD_STR, "48656c6c6f2c20776f726c6421");
319/// # const FROM_BYTES_LOWERCASE: &str = fashex::encode!([0x12, 0x34, 0xab, 0xcd]);
320/// # assert_eq!(FROM_BYTES_LOWERCASE, "1234abcd");
321/// ```
322macro_rules! encode {
323    ($bytes:expr) => {
324        $crate::encode!($bytes, false)
325    };
326    ($bytes:expr, $uppercase:expr) => {{
327        const ENCODED: [u8; $bytes.len() * 2] = {
328            let buf: &mut [::core::mem::MaybeUninit<u8>; const { $bytes.len() * 2 }] =
329                &mut [::core::mem::MaybeUninit::uninit(); _];
330
331            #[allow(unsafe_code, reason = "XXX")]
332            let bytes = unsafe { ::core::slice::from_raw_parts($bytes.as_ptr(), $bytes.len()) };
333
334            match $crate::encode_generic::<{ $uppercase }>(bytes, buf) {
335                Ok(_) => {}
336                Err(_) => unreachable!(),
337            };
338
339            #[allow(unsafe_code, reason = "XXX")]
340            unsafe {
341                ::core::mem::transmute::<_, _>(*buf)
342            }
343        };
344
345        #[allow(unsafe_code, reason = "XXX")]
346        unsafe {
347            ::core::str::from_utf8_unchecked(&ENCODED)
348        }
349    }};
350}
351
352#[macro_export]
353/// Helper macro for decoding hexadecimal strings to byte arrays in const
354/// contexts.
355///
356/// ## Examples
357///
358/// ```rust
359/// const FOOBAR: &[u8] = fashex::decode!("48656c6c6f2c20776f726c6421");
360/// assert_eq!(FOOBAR, b"Hello, world!");
361/// # const FOOBAR_ARRAY: &[u8; 13] = fashex::decode!("48656c6c6f2c20776f726c6421");
362/// # assert_eq!(FOOBAR_ARRAY, b"Hello, world!");
363/// # const FOOBAR_RIG: &[u8; 13] = fashex::decode!("48656c6c6f2C20776F726c6421");
364/// # assert_eq!(FOOBAR_RIG, b"Hello, world!");
365/// ```
366macro_rules! decode {
367    ($bytes:expr) => {{
368        const DECODED: [u8; $bytes.len() / 2] = {
369            assert!(
370                $bytes.len() % 2 == 0,
371                "the length of the input must be even"
372            );
373
374            let buf: &mut [::core::mem::MaybeUninit<u8>; const { $bytes.len() / 2 }] =
375                &mut [::core::mem::MaybeUninit::uninit(); _];
376
377            #[allow(unsafe_code, reason = "XXX")]
378            let bytes = unsafe { ::core::slice::from_raw_parts($bytes.as_ptr(), $bytes.len()) };
379
380            match $crate::decode_generic(bytes, buf) {
381                Ok(_) => {}
382                Err(_) => panic!("invalid hexadecimal string"),
383            };
384
385            #[allow(unsafe_code, reason = "XXX")]
386            unsafe {
387                ::core::mem::transmute::<_, _>(*buf)
388            }
389        };
390
391        &DECODED
392    }};
393}