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