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}