Skip to main content

fashex/
lib.rs

1#![doc = include_str!("../README.md")]
2#![no_std]
3#![allow(clippy::inline_always, reason = "XXX")]
4#![cfg_attr(feature = "portable-simd", feature(portable_simd))]
5#![cfg_attr(
6    all(feature = "experimental-loongarch64-simd", target_arch = "loongarch64"),
7    feature(stdarch_loongarch)
8)]
9
10#[cfg(any(test, feature = "alloc"))]
11extern crate alloc;
12
13#[cfg(any(test, feature = "std"))]
14extern crate std;
15
16mod backend;
17pub mod error;
18#[cfg(feature = "fuzz")]
19pub mod fuzz;
20pub mod util;
21
22use core::mem::MaybeUninit;
23
24use crate::error::InvalidInput;
25
26/// Encodes the input bytes to hexadecimal string and writes it to the output
27/// buffer.
28///
29/// ## Examples
30///
31/// ```rust
32/// use core::mem::MaybeUninit;
33///
34/// let input = b"Hello, world!";
35/// let mut output = vec![MaybeUninit::<u8>::uninit(); input.len() * 2];
36///
37/// fashex::encode::<false>(input, &mut output).expect("infallible: the length must be valid");
38///
39/// #[allow(
40///     unsafe_code,
41///     reason = "We have encoded the input to hexadecimal string"
42/// )]
43/// let output = unsafe { output.assume_init_ref() };
44///
45/// assert_eq!(output, b"48656c6c6f2c20776f726c6421");
46///
47/// let mut output = vec![MaybeUninit::<u8>::uninit(); input.len() * 2];
48///
49/// fashex::encode::<true>(input, &mut output).expect("infallible: the length must be valid");
50///
51/// #[allow(
52///     unsafe_code,
53///     reason = "We have encoded the input to hexadecimal string"
54/// )]
55/// let output = unsafe { output.assume_init_ref() };
56///
57/// assert_eq!(output, b"48656C6C6F2C20776F726C6421");
58/// ```
59///
60/// ## Errors
61///
62/// The length of the output buffer must be twice the length of the input
63/// buffer.
64///
65/// We may relax this requirement in the future accepting a longer output
66/// buffer, but for now, we require it for simplicity and performance.
67pub fn encode<const UPPER: bool>(
68    src: &[u8],
69    dst: &mut [MaybeUninit<u8>],
70) -> Result<(), InvalidInput> {
71    backend::encode::<UPPER>(src, dst)
72}
73
74#[inline]
75/// Decodes the input hexadecimal string to bytes and writes it to the output
76/// buffer.
77///
78/// ```rust
79/// use core::mem::MaybeUninit;
80///
81/// let input = b"48656c6c6f2c20776f726c6421";
82/// let mut output = vec![MaybeUninit::<u8>::uninit(); input.len() / 2];
83///
84/// fashex::decode(input, &mut output).expect("infallible: the input must be valid here");
85///
86/// #[allow(
87///     unsafe_code,
88///     reason = "We have decoded the input hexadecimal string to bytes"
89/// )]
90/// let output = unsafe { output.assume_init_ref() };
91///
92/// assert_eq!(output, b"Hello, world!");
93/// ```
94///
95/// ## Errors
96///
97/// 1. The input and output lengths do not match (`src.len() == 2 * dst.len()`).
98/// 2. The input contains invalid hexadecimal characters.
99/// 3. The input contains both uppercase and lowercase hexadecimal characters.
100pub fn decode(src: &[u8], dst: &mut [MaybeUninit<u8>]) -> Result<(), InvalidInput> {
101    backend::decode(src, dst)
102}
103
104/// [`encode()`] or [`decode()`], but const-evaluable at the cost of performance.
105///
106/// Macro [`encode!`] is useful when you want to encode a byte array to
107/// hexadecimal string in const contexts, such as creating a `const` variable or
108/// a `static` variable.
109///
110/// ## Errors
111///
112/// The input and output lengths do not match (`2 * src.len() == dst.len()`).
113pub const fn encode_generic<const UPPER: bool>(
114    src: &[u8],
115    dst: &mut [MaybeUninit<u8>],
116) -> Result<(), InvalidInput> {
117    if 2 * src.len() == dst.len() {
118        #[allow(unsafe_code, reason = "The length is validated")]
119        let dst: &mut [[MaybeUninit<u8>; 2]] = unsafe { dst.as_chunks_unchecked_mut() };
120
121        #[allow(unsafe_code, reason = "The length is validated")]
122        unsafe {
123            backend::generic::encode_generic_unchecked::<UPPER>(src, dst);
124        };
125
126        Ok(())
127    } else {
128        Err(InvalidInput)
129    }
130}
131
132/// [`decode()`], but const-evaluable at the cost of performance.
133///
134/// Macro [`decode!`] is useful when you want to decode hexadecimal string to a
135/// byte array in const contexts, such as creating a `const` variable or a
136/// `static` variable.
137///
138/// ## Errors
139///
140/// The input and output lengths do not match (`src.len() == 2 * dst.len()`), or
141/// the input contains invalid hexadecimal characters.
142pub const fn decode_generic(src: &[u8], dst: &mut [MaybeUninit<u8>]) -> Result<(), InvalidInput> {
143    if src.len() == 2 * dst.len() {
144        #[allow(unsafe_code, reason = "The length is validated")]
145        let src: &[[u8; 2]] = unsafe { src.as_chunks_unchecked() };
146
147        #[allow(unsafe_code, reason = "The length is validated")]
148        unsafe {
149            backend::generic::decode_generic_unchecked::<false>(src, dst)
150        }
151    } else {
152        Err(InvalidInput)
153    }
154}
155
156#[macro_export]
157/// Helper macro for encoding hexadecimal string in const contexts.
158///
159/// ## Examples
160///
161/// ```rust
162/// const HELLO_WORLD_LOWERCASE: &str = fashex::encode!(b"Hello, world!");
163/// assert_eq!(HELLO_WORLD_LOWERCASE, "48656c6c6f2c20776f726c6421");
164/// const HELLO_WORLD_UPPERCASE: &str = fashex::encode!(b"Hello, world!", true);
165/// assert_eq!(HELLO_WORLD_UPPERCASE, "48656C6C6F2C20776F726C6421");
166/// # const HELLO_WORLD_STR: &str = fashex::encode!("Hello, world!");
167/// # assert_eq!(HELLO_WORLD_STR, "48656c6c6f2c20776f726c6421");
168/// # const FROM_BYTES_LOWERCASE: &str = fashex::encode!([0x12, 0x34, 0xab, 0xcd]);
169/// # assert_eq!(FROM_BYTES_LOWERCASE, "1234abcd");
170/// ```
171macro_rules! encode {
172    ($bytes:expr) => {
173        $crate::encode!($bytes, false)
174    };
175    ($bytes:expr, $uppercase:expr) => {{
176        const ENCODED: [u8; $bytes.len() * 2] = {
177            let buf: &mut [::core::mem::MaybeUninit<u8>; const { $bytes.len() * 2 }] =
178                &mut [::core::mem::MaybeUninit::uninit(); _];
179
180            #[allow(unsafe_code, reason = "XXX")]
181            let bytes = unsafe { ::core::slice::from_raw_parts($bytes.as_ptr(), $bytes.len()) };
182
183            match $crate::encode_generic::<{ $uppercase }>(bytes, buf) {
184                Ok(()) => {}
185                Err(_) => unreachable!(),
186            };
187
188            #[allow(unsafe_code, reason = "XXX")]
189            unsafe {
190                ::core::mem::transmute::<_, _>(*buf)
191            }
192        };
193
194        #[allow(unsafe_code, reason = "XXX")]
195        unsafe {
196            ::core::str::from_utf8_unchecked(&ENCODED)
197        }
198    }};
199}
200
201#[macro_export]
202/// Helper macro for decoding hexadecimal string in const contexts.
203///
204/// ## Examples
205///
206/// ```rust
207/// const FOOBAR: &[u8] = fashex::decode!("48656c6c6f2c20776f726c6421");
208/// assert_eq!(FOOBAR, b"Hello, world!");
209/// # const FOOBAR_ARRAY: &[u8; 13] = fashex::decode!("48656c6c6f2c20776f726c6421");
210/// # assert_eq!(FOOBAR_ARRAY, b"Hello, world!");
211/// # const FOOBAR_RIG: &[u8; 13] = fashex::decode!("48656c6c6f2C20776F726c6421");
212/// # assert_eq!(FOOBAR_RIG, b"Hello, world!");
213/// ```
214macro_rules! decode {
215    ($bytes:expr) => {{
216        const DECODED: [u8; $bytes.len() / 2] = {
217            assert!(
218                $bytes.len() % 2 == 0,
219                "the length of the input must be even"
220            );
221
222            let buf: &mut [::core::mem::MaybeUninit<u8>; const { $bytes.len() / 2 }] =
223                &mut [::core::mem::MaybeUninit::uninit(); _];
224
225            #[allow(unsafe_code, reason = "XXX")]
226            let bytes = unsafe { ::core::slice::from_raw_parts($bytes.as_ptr(), $bytes.len()) };
227
228            match $crate::decode_generic(bytes, buf) {
229                Ok(()) => {}
230                Err(_) => panic!("invalid hexadecimal string"),
231            };
232
233            #[allow(unsafe_code, reason = "XXX")]
234            unsafe {
235                ::core::mem::transmute::<_, _>(*buf)
236            }
237        };
238
239        &DECODED
240    }};
241}
242
243#[cfg(test)]
244mod smoking {
245    #![allow(unsafe_code, reason = "XXX")]
246    #![allow(unsafe_op_in_unsafe_fn, reason = "XXX")]
247    #![allow(clippy::cognitive_complexity, reason = "XXX")]
248
249    use alloc::string::String;
250    use alloc::vec;
251    use core::mem::MaybeUninit;
252    use core::{slice, str};
253
254    use super::{decode, decode_generic, encode, encode_generic};
255    use crate::util::DIGITS_LOWER_16;
256
257    macro_rules! test {
258        (
259            Encode = $encode_f:ident;
260            Decode = $($decode_f:ident),*;
261            Case = $i:expr
262        ) => {{
263            let input = $i;
264
265            let expected = input
266                .iter()
267                .flat_map(|b| [
268                    DIGITS_LOWER_16[(*b >> 4) as usize] as char,
269                    DIGITS_LOWER_16[(*b & 0b1111) as usize] as char,
270                ])
271                .collect::<String>();
272
273            let mut output = vec![MaybeUninit::<u8>::uninit(); input.len() * 2];
274
275            $encode_f::<false>(input, &mut output).unwrap();
276
277            let output = unsafe {
278                slice::from_raw_parts(
279                    output.as_ptr().cast::<u8>(),
280                    output.len(),
281                )
282            };
283
284            assert_eq!(
285                output,
286                expected.as_bytes(),
287                "Encode error, expect \"{expected}\", got \"{}\" ({:?})",
288                str::from_utf8(output).unwrap_or("<invalid utf-8>"),
289                output
290            );
291
292            $({
293                let mut decoded = vec![MaybeUninit::<u8>::uninit(); input.len()];
294
295                $decode_f(output, &mut decoded).unwrap();
296
297                unsafe {
298                    assert_eq!(
299                        decoded.assume_init_ref(),
300                        input,
301                        "Decode error for {}, expect {:?}, got {:?}",
302                        stringify!($decode_f),
303                        input,
304                        decoded.assume_init_ref()
305                    );
306                }
307            })+
308        }};
309    }
310
311    #[test]
312    fn test_encode() {
313        const CASE: &[u8; 65] = &[
314            0xA1, 0xA4, 0xA2, 0x49, 0x4A, 0x43, 0x03, 0x31, 0x5F, 0x60, 0xE7, 0x8F, 0x17, 0x36,
315            0x31, 0xAD, 0xB3, 0xE4, 0xF2, 0x35, 0x33, 0x6F, 0x05, 0xF0, 0xAA, 0x52, 0xD2, 0x6F,
316            0x3A, 0xB7, 0x4A, 0xAB, 0x66, 0x32, 0xB0, 0xD6, 0x1C, 0x8C, 0xED, 0x85, 0x9E, 0x03,
317            0x90, 0x87, 0x16, 0x9C, 0xBA, 0x34, 0xAD, 0x59, 0x35, 0x66, 0xED, 0x80, 0x22, 0x85,
318            0xDB, 0x54, 0x5E, 0x79, 0xD3, 0x9A, 0x6F, 0x24, 0x43,
319        ];
320
321        test!(
322            Encode = encode_generic;
323            Decode = decode_generic, decode;
324            Case = &CASE[..15]
325        );
326        test!(
327            Encode = encode_generic;
328            Decode = decode_generic, decode;
329            Case = &CASE[..16]
330        );
331        test!(
332            Encode = encode_generic;
333            Decode = decode_generic, decode;
334            Case = &CASE[..17]
335        );
336        test!(
337            Encode = encode_generic;
338            Decode = decode_generic, decode;
339            Case = &CASE[..31]
340        );
341        test!(
342            Encode = encode_generic;
343            Decode = decode_generic, decode;
344            Case = &CASE[..32]
345        );
346        test!(
347            Encode = encode_generic;
348            Decode = decode_generic, decode;
349            Case = &CASE[..33]
350        );
351        test!(
352            Encode = encode_generic;
353            Decode = decode_generic, decode;
354            Case = &CASE[..63]
355        );
356        test!(
357            Encode = encode_generic;
358            Decode = decode_generic, decode;
359            Case = &CASE[..64]
360        );
361        test!(
362            Encode = encode_generic;
363            Decode = decode_generic, decode;
364            Case = &CASE[..65]
365        );
366
367        test!(
368            Encode = encode;
369            Decode = decode_generic, decode;
370            Case = &CASE[..15]
371        );
372        test!(
373            Encode = encode;
374            Decode = decode_generic, decode;
375            Case = &CASE[..16]
376        );
377        test!(
378            Encode = encode;
379            Decode = decode_generic, decode;
380            Case = &CASE[..17]
381        );
382        test!(
383            Encode = encode;
384            Decode = decode_generic, decode;
385            Case = &CASE[..31]
386        );
387        test!(
388            Encode = encode;
389            Decode = decode_generic, decode;
390            Case = &CASE[..32]
391        );
392        test!(
393            Encode = encode;
394            Decode = decode_generic, decode;
395            Case = &CASE[..33]
396        );
397        test!(
398            Encode = encode;
399            Decode = decode_generic, decode;
400            Case = &CASE[..63]
401        );
402        test!(
403            Encode = encode;
404            Decode = decode_generic, decode;
405            Case = &CASE[..64]
406        );
407        test!(
408            Encode = encode;
409            Decode = decode_generic, decode;
410            Case = &CASE[..65]
411        );
412    }
413}