fashex 0.0.4

Conversion from bytes to hexadecimal string.
Documentation
//! Generic implementations.

use core::mem::MaybeUninit;
use core::slice;

use crate::error::InvalidInput;
use crate::util::digits16;

#[allow(unsafe_code, reason = "XXX")]
/// ## Safety
///
/// We assume that:
///
/// 1. `src.len() == dst.len()`.
pub(crate) const unsafe fn encode_generic_unchecked<const UPPER: bool>(
    mut src: &[u8],
    mut dst: &mut [[MaybeUninit<u8>; 2]],
) {
    debug_assert!(
        src.len() == dst.len(),
        "implementation bug: `src` and `dst` should have the same length"
    );

    let digits = digits16::<UPPER>();

    while !src.is_empty() {
        {
            let src = src[0];

            // TODO: use `get_unchecked` instead when it is stable.
            // SAFETY:
            // 1. `!src.is_empty()` and the caller guarantees that `dst` has the same length
            //    as `src`.
            let dst = unsafe { &mut *dst.as_mut_ptr() };

            dst[0].write(digits[(src >> 4) as usize]);
            dst[1].write(digits[(src & 0x0F) as usize]);
        }

        // TODO: use const indexing syntax instead when it is stable.
        // SAFETY:
        // 1. `!src.is_empty()` and the caller guarantees that `dst` has the same length
        //    as `src`.
        unsafe {
            src = slice::from_raw_parts(src.as_ptr().add(1), src.len().unchecked_sub(1));
            dst = slice::from_raw_parts_mut(dst.as_mut_ptr().add(1), dst.len().unchecked_sub(1));
        };
    }
}

#[allow(unused, reason = "XXX")]
#[inline]
pub(crate) fn validate_generic(src: &[[u8; 2]]) -> Result<(), InvalidInput> {
    src.as_flattened()
        .iter()
        .all(u8::is_ascii_hexdigit)
        .then_some(())
        .ok_or(InvalidInput)
}

#[allow(unsafe_code, reason = "XXX")]
/// ## Safety
///
/// We assume that:
///
/// 1. `src.len() == dst.len()`.
/// 2. When `VALIDATED`, the input contains only valid hexadecimal characters.
///
/// ## Errors
///
/// When `!VALIDATED` and the input contains invalid hexadecimal characters.
pub(crate) const unsafe fn decode_generic_unchecked<const VALIDATED: bool>(
    mut src: &[[u8; 2]],
    mut dst: &mut [MaybeUninit<u8>],
) -> Result<(), InvalidInput> {
    const NIL: u8 = u8::MAX;

    const LUT: [u8; u8::MAX as usize + 1] = {
        let mut lut = [NIL; _];

        let mut i = 0;

        while i <= u8::MAX as usize {
            #[allow(clippy::cast_possible_truncation, reason = "XXX")]
            match i as u8 {
                b'0'..=b'9' => {
                    lut[i] = i as u8 - b'0';
                }
                b'a'..=b'f' => {
                    lut[i] = i as u8 - b'a' + 10;
                }
                b'A'..=b'F' => {
                    lut[i] = i as u8 - b'A' + 10;
                }
                _ => {}
            };

            i += 1;
        }

        lut
    };

    const LUT_FOR_VALIDATED: [u8; u8::MAX as usize + 1] = {
        let mut lut = [0; _];

        let mut i = 0;

        while i <= u8::MAX as usize {
            #[allow(clippy::cast_possible_truncation, reason = "XXX")]
            match i as u8 {
                b'0'..=b'9' => {
                    lut[i] = i as u8 - b'0';
                }
                b'a'..=b'f' => {
                    lut[i] = i as u8 - b'a' + 10;
                }
                b'A'..=b'F' => {
                    lut[i] = i as u8 - b'A' + 10;
                }
                _ => {}
            };

            i += 1;
        }

        lut
    };

    debug_assert!(
        src.len() == dst.len(),
        "implementation bug: `src` and `dst` should have the same length"
    );

    while !src.is_empty() {
        {
            let [hi, lo] = src[0];

            // TODO: use `get_unchecked` instead when it is stable.
            // SAFETY:
            // 1. `!src.is_empty()` and the caller guarantees that `dst` has the same length
            //    as `src`.
            let dst = unsafe { &mut *dst.as_mut_ptr() };

            if VALIDATED {
                let hi = LUT_FOR_VALIDATED[hi as usize];
                let lo = LUT_FOR_VALIDATED[lo as usize];

                dst.write(hi << 4 | lo);
            } else {
                let hi = LUT[hi as usize];
                let lo = LUT[lo as usize];

                if hi == NIL || lo == NIL {
                    return Err(InvalidInput);
                }

                dst.write(hi << 4 | lo);
            }
        }

        // TODO: use const indexing instead when it is stable.
        // SAFETY:
        // 1. `!src.is_empty()` and the caller guarantees that `dst` has the same length
        //    as `src`.
        unsafe {
            src = slice::from_raw_parts(src.as_ptr().add(1), src.len().unchecked_sub(1));
            dst = slice::from_raw_parts_mut(dst.as_mut_ptr().add(1), dst.len().unchecked_sub(1));
        };
    }

    Ok(())
}