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
26pub 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]
75pub fn decode(src: &[u8], dst: &mut [MaybeUninit<u8>]) -> Result<(), InvalidInput> {
101 backend::decode(src, dst)
102}
103
104pub 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
132pub 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]
157macro_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]
202macro_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}