fashex 0.1.0

Hexadecimal string encoding and decoding with best-effort SIMD acceleration.
Documentation
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use core::fmt;
#[cfg(not(feature = "alloc"))]
use core::mem::MaybeUninit;

use crate::encode;

wrapper_lite::wrapper!(
    #[wrapper(AsRef)]
    #[wrapper([const] AsMut)]
    #[wrapper(BorrowMut)]
    #[wrapper(DerefMut)]
    #[wrapper(From)]
    #[derive(Clone, Copy, PartialEq, Eq)]
    /// A wrapper for displaying a byte slice as a hexadecimal string.
    ///
    /// ## Examples
    ///
    /// ```rust
    /// let bytes = &[0xde, 0xad, 0xbe, 0xef];
    ///
    /// let displayed = fashex::Display::from(bytes);
    ///
    /// assert_eq!(format!("{}", &displayed), "deadbeef");
    /// assert_eq!(format!("{:X}", &displayed), "DEADBEEF");
    /// assert_eq!(format!("{:#x}", &displayed), "0xdeadbeef");
    /// assert_eq!(format!("{:#X}", &displayed), "0xDEADBEEF");
    /// ```
    ///
    /// ## Notes
    ///
    /// The input bytes should be less than or equal to 128 bytes in length when
    /// the `alloc` feature is disabled.
    pub struct Display<T> {
        value: T,
    }
);

impl<T: AsRef<[u8]>> fmt::Debug for Display<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::LowerHex::fmt(self, f)
    }
}

impl<T: AsRef<[u8]>> fmt::Display for Display<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::LowerHex::fmt(self, f)
    }
}

impl<T: AsRef<[u8]>> fmt::LowerHex for Display<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.write::<false>(f)
    }
}

impl<T: AsRef<[u8]>> fmt::UpperHex for Display<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.write::<true>(f)
    }
}

impl<T> Display<T>
where
    T: AsRef<[u8]>,
{
    fn write<const UPPER: bool>(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let src = self.as_inner().as_ref();

        if f.alternate() {
            f.write_str("0x")?;
        }

        #[cfg(feature = "alloc")]
        let mut buf = Vec::with_capacity(src.len() * 2);

        #[cfg(not(feature = "alloc"))]
        let mut buf = [MaybeUninit::<u8>::uninit(); 256];

        f.write_str(encode::<UPPER>(src, &mut buf).map_err(|_| fmt::Error)?)
    }
}

#[cfg(test)]
mod smoking {
    use core::fmt::Write;
    use core::mem::MaybeUninit;
    use core::slice;

    use super::*;

    struct Buffer {
        buf: [MaybeUninit<u8>; 1024],
        len: usize,
    }

    impl Write for Buffer {
        fn write_str(&mut self, s: &str) -> fmt::Result {
            let bytes = s.as_bytes();
            let len = bytes.len();

            if self.len + len > self.buf.len() {
                return Err(fmt::Error);
            }

            #[allow(unsafe_code, reason = "XXX")]
            unsafe {
                self.buf
                    .as_mut_ptr()
                    .add(self.len)
                    .copy_from_nonoverlapping(bytes.as_ptr().cast::<MaybeUninit<u8>>(), len);
            }

            self.len += len;

            Ok(())
        }
    }

    impl Buffer {
        const fn new() -> Self {
            Self {
                buf: [MaybeUninit::uninit(); 1024],
                len: 0,
            }
        }

        const fn as_str(&self) -> &str {
            #[allow(unsafe_code, reason = "XXX")]
            unsafe {
                str::from_utf8_unchecked(slice::from_raw_parts(
                    self.buf.as_ptr().cast::<u8>(),
                    self.len,
                ))
            }
        }
    }

    #[test]
    fn test_display() {
        let bytes = &[0xde, 0xad, 0xbe, 0xef];

        let displayed = Display::from(bytes);

        {
            let mut buf = Buffer::new();
            write!(&mut buf, "{}", &displayed).unwrap();
            assert_eq!(buf.as_str(), "deadbeef");
        }

        {
            let mut buf = Buffer::new();
            write!(&mut buf, "{:X}", &displayed).unwrap();
            assert_eq!(buf.as_str(), "DEADBEEF");
        }

        {
            let mut buf = Buffer::new();
            write!(&mut buf, "{:#x}", &displayed).unwrap();
            assert_eq!(buf.as_str(), "0xdeadbeef");
        }

        {
            let mut buf = Buffer::new();
            write!(&mut buf, "{:#X}", &displayed).unwrap();
            assert_eq!(buf.as_str(), "0xDEADBEEF");
        }
    }

    #[test]
    fn test_display_oversized() {
        let bytes = [0xAAu8; 129];

        {
            let case = 127;

            let displayed = Display::from(&bytes[..case]);

            {
                let mut buf = Buffer::new();
                write!(&mut buf, "{}", &displayed).unwrap();
                assert_eq!(buf.as_str().len(), case * 2);
                assert!(buf.as_str().chars().all(|c| c == 'a'));
            }

            {
                let mut buf = Buffer::new();
                write!(&mut buf, "{:X}", &displayed).unwrap();
                assert_eq!(buf.as_str().len(), case * 2);
                assert!(buf.as_str().chars().all(|c| c == 'A'));
            }

            {
                let mut buf = Buffer::new();
                write!(&mut buf, "{:#x}", &displayed).unwrap();
                assert_eq!(buf.as_str().len(), case * 2 + 2);
                assert!(buf.as_str()[2..].chars().all(|c| c == 'a'));
            }

            {
                let mut buf = Buffer::new();
                write!(&mut buf, "{:#X}", &displayed).unwrap();
                assert_eq!(buf.as_str().len(), case * 2 + 2);
                assert!(buf.as_str()[2..].chars().all(|c| c == 'A'));
            }
        }

        {
            let case = 128;

            let displayed = Display::from(&bytes[..case]);

            {
                let mut buf = Buffer::new();
                write!(&mut buf, "{}", &displayed).unwrap();
                assert_eq!(buf.as_str().len(), case * 2);
                assert!(buf.as_str().chars().all(|c| c == 'a'));
            }

            {
                let mut buf = Buffer::new();
                write!(&mut buf, "{:X}", &displayed).unwrap();
                assert_eq!(buf.as_str().len(), case * 2);
                assert!(buf.as_str().chars().all(|c| c == 'A'));
            }

            {
                let mut buf = Buffer::new();
                write!(&mut buf, "{:#x}", &displayed).unwrap();
                assert_eq!(buf.as_str().len(), case * 2 + 2);
                assert!(buf.as_str()[2..].chars().all(|c| c == 'a'));
            }

            {
                let mut buf = Buffer::new();
                write!(&mut buf, "{:#X}", &displayed).unwrap();
                assert_eq!(buf.as_str().len(), case * 2 + 2);
                assert!(buf.as_str()[2..].chars().all(|c| c == 'A'));
            }
        }

        #[cfg(feature = "alloc")]
        {
            let case = 129;

            let displayed = Display::from(&bytes[..case]);

            {
                let mut buf = Buffer::new();
                write!(&mut buf, "{}", &displayed).unwrap();
                assert_eq!(buf.as_str().len(), case * 2);
                assert!(buf.as_str().chars().all(|c| c == 'a'));
            }

            {
                let mut buf = Buffer::new();
                write!(&mut buf, "{:X}", &displayed).unwrap();
                assert_eq!(buf.as_str().len(), case * 2);
                assert!(buf.as_str().chars().all(|c| c == 'A'));
            }

            {
                let mut buf = Buffer::new();
                write!(&mut buf, "{:#x}", &displayed).unwrap();
                assert_eq!(buf.as_str().len(), case * 2 + 2);
                assert!(buf.as_str()[2..].chars().all(|c| c == 'a'));
            }

            {
                let mut buf = Buffer::new();
                write!(&mut buf, "{:#X}", &displayed).unwrap();
                assert_eq!(buf.as_str().len(), case * 2 + 2);
                assert!(buf.as_str()[2..].chars().all(|c| c == 'A'));
            }
        }

        #[cfg(not(feature = "alloc"))]
        {
            let case = 129;

            let displayed = Display::from(&bytes[..case]);

            {
                let mut buf = Buffer::new();
                write!(&mut buf, "{}", &displayed).unwrap_err();
            }

            {
                let mut buf = Buffer::new();
                write!(&mut buf, "{:X}", &displayed).unwrap_err();
            }

            {
                let mut buf = Buffer::new();
                write!(&mut buf, "{:#x}", &displayed).unwrap_err();
            }

            {
                let mut buf = Buffer::new();
                write!(&mut buf, "{:#X}", &displayed).unwrap_err();
            }
        }
    }

    #[test]
    #[cfg(feature = "alloc")]
    fn test_display_alloc() {
        use alloc::format;

        let bytes = &[0xde, 0xad, 0xbe, 0xef];

        let displayed = Display::from(bytes);

        assert_eq!(format!("{}", &displayed), "deadbeef");
        assert_eq!(format!("{:X}", &displayed), "DEADBEEF");
        assert_eq!(format!("{:#x}", &displayed), "0xdeadbeef");
        assert_eq!(format!("{:#X}", &displayed), "0xDEADBEEF");
    }
}