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}