lilliput-core 0.1.0

Low-level implementation of encoding/decoding logic for lilliput format
Documentation
use core::num::TryFromIntError;

use num_traits::{Signed, Unsigned};

use crate::{
    error::{Error, Result},
    header::{CompactIntHeader, ExtendedIntHeader, IntHeader},
    marker::Marker,
    num::FromZigZag,
    value::{IntValue, SignedIntValue, UnsignedIntValue},
};

use super::{Decoder, Read};

impl<'de, R> Decoder<R>
where
    R: Read<'de>,
{
    // MARK: - Value

    /// Decodes a 8-bit signed integer value.
    #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
    pub fn decode_i8(&mut self) -> Result<i8> {
        self.decode_signed_int()
    }

    /// Decodes a 16-bit signed integer value.
    #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
    pub fn decode_i16(&mut self) -> Result<i16> {
        self.decode_signed_int()
    }

    /// Decodes a 32-bit signed integer value.
    #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
    pub fn decode_i32(&mut self) -> Result<i32> {
        self.decode_signed_int()
    }

    /// Decodes a 64-bit signed integer value.
    #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
    pub fn decode_i64(&mut self) -> Result<i64> {
        self.decode_signed_int()
    }

    /// Decodes a 8-bit unsigned integer value.
    #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
    pub fn decode_u8(&mut self) -> Result<u8> {
        self.decode_unsigned_int()
    }

    /// Decodes a 16-bit unsigned integer value.
    #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
    pub fn decode_u16(&mut self) -> Result<u16> {
        self.decode_unsigned_int()
    }

    /// Decodes a 32-bit unsigned integer value.
    #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
    pub fn decode_u32(&mut self) -> Result<u32> {
        self.decode_unsigned_int()
    }

    /// Decodes a 64-bit unsigned integer value.
    #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
    pub fn decode_u64(&mut self) -> Result<u64> {
        self.decode_unsigned_int()
    }

    /// Decodes a signed integer value.
    #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
    pub fn decode_signed_int<T>(&mut self) -> Result<T>
    where
        T: Signed + TryFrom<SignedIntValue, Error = TryFromIntError>,
    {
        let pos = self.pos;

        self.decode_signed_int_value()?
            .try_into()
            .map_err(|_| Error::number_out_of_range(Some(pos)))
    }

    /// Decodes a unsigned integer value.
    #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
    pub fn decode_unsigned_int<T>(&mut self) -> Result<T>
    where
        T: Unsigned + TryFrom<UnsignedIntValue, Error = TryFromIntError>,
    {
        let pos = self.pos;

        self.decode_unsigned_int_value()?
            .try_into()
            .map_err(|_| Error::number_out_of_range(Some(pos)))
    }

    /// Decodes a signed integer value, as a `SignedIntValue`.
    #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
    pub fn decode_signed_int_value(&mut self) -> Result<SignedIntValue> {
        let pos = self.pos;

        self.decode_int_value()?
            .to_signed()
            .map_err(|_| Error::number_out_of_range(Some(pos)))
    }

    /// Decodes a unsigned integer value, as a `UnsignedIntValue`.
    #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
    pub fn decode_unsigned_int_value(&mut self) -> Result<UnsignedIntValue> {
        let pos = self.pos;

        self.decode_int_value()?
            .to_unsigned()
            .map_err(|_| Error::number_out_of_range(Some(pos)))
    }

    /// Decodes a integer value, as an `IntValue`.
    #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
    pub fn decode_int_value(&mut self) -> Result<IntValue> {
        let header = self.decode_int_header()?;
        self.decode_int_value_of(header)
    }

    // MARK: - Header

    /// Decodes a integer value's header.
    #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
    pub fn decode_int_header(&mut self) -> Result<IntHeader> {
        let byte = self.pull_byte_expecting(Marker::Int)?;

        if (byte & IntHeader::COMPACT_VARIANT_BIT) != 0b0 {
            let is_signed = (byte & IntHeader::SIGNEDNESS_BIT) != 0b0;
            let bits = byte & IntHeader::COMPACT_VALUE_BITS;

            #[cfg(feature = "tracing")]
            tracing::debug!(
                byte = crate::binary::fmt_byte(byte),
                is_compact = true,
                is_signed = is_signed,
                bits = bits
            );

            Ok(IntHeader::Compact(CompactIntHeader { is_signed, bits }))
        } else {
            let is_signed = (byte & IntHeader::SIGNEDNESS_BIT) != 0b0;
            let width = 1 + (byte & IntHeader::EXTENDED_WIDTH_BITS);

            #[cfg(feature = "tracing")]
            tracing::debug!(
                byte = crate::binary::fmt_byte(byte),
                is_compact = false,
                is_signed = is_signed,
                width = width
            );

            Ok(IntHeader::Extended(ExtendedIntHeader { is_signed, width }))
        }
    }

    // MARK: - Skip

    /// Skips the integer value for a given `header`.
    #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
    pub fn skip_int_value_of(&mut self, header: IntHeader) -> Result<()> {
        let header = match header {
            IntHeader::Compact(_) => return Ok(()),
            IntHeader::Extended(header) => header,
        };

        self.reader.skip(header.width().into())
    }

    // MARK: - Body

    /// Decodes integer value for a given `header`, as an `IntValue`.
    #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
    pub fn decode_int_value_of(&mut self, header: IntHeader) -> Result<IntValue> {
        let (is_signed, width): (bool, usize) = match header {
            IntHeader::Compact(CompactIntHeader { is_signed, bits }) => {
                if is_signed {
                    let value = i8::from_zig_zag(bits);

                    #[cfg(feature = "tracing")]
                    tracing::debug!(value = value);

                    return Ok(IntValue::Signed(SignedIntValue::I8(value)));
                } else {
                    let value = bits;

                    #[cfg(feature = "tracing")]
                    tracing::debug!(value = value);

                    return Ok(IntValue::Unsigned(UnsignedIntValue::U8(value)));
                }
            }
            IntHeader::Extended(ExtendedIntHeader { is_signed, width }) => {
                (is_signed, width as usize)
            }
        };

        match width {
            1..=1 => {
                const MAX_WIDTH: usize = 1;
                let mut padded_be_bytes: [u8; MAX_WIDTH] = [0b0; MAX_WIDTH];
                self.pull_bytes_into(&mut padded_be_bytes[(MAX_WIDTH - width)..])?;

                #[cfg(feature = "tracing")]
                let bytes = crate::binary::fmt_bytes(&padded_be_bytes[(MAX_WIDTH - width)..]);

                let value = u8::from_be_bytes(padded_be_bytes);

                if is_signed {
                    let value = i8::from_zig_zag(value);

                    #[cfg(feature = "tracing")]
                    tracing::debug!(bytes = bytes, value = value);

                    Ok(IntValue::Signed(SignedIntValue::I8(value)))
                } else {
                    #[cfg(feature = "tracing")]
                    tracing::debug!(bytes = bytes, value = value);

                    Ok(IntValue::Unsigned(UnsignedIntValue::U8(value)))
                }
            }
            2..=2 => {
                const MAX_WIDTH: usize = 2;
                let mut padded_be_bytes: [u8; MAX_WIDTH] = [0b0; MAX_WIDTH];
                self.pull_bytes_into(&mut padded_be_bytes[(MAX_WIDTH - width)..])?;

                #[cfg(feature = "tracing")]
                let bytes = crate::binary::fmt_bytes(&padded_be_bytes[(MAX_WIDTH - width)..]);

                let value = u16::from_be_bytes(padded_be_bytes);

                if is_signed {
                    let value = i16::from_zig_zag(value);

                    #[cfg(feature = "tracing")]
                    tracing::debug!(bytes = bytes, value = value);

                    Ok(IntValue::Signed(SignedIntValue::I16(value)))
                } else {
                    #[cfg(feature = "tracing")]
                    tracing::debug!(bytes = bytes, value = value);

                    Ok(IntValue::Unsigned(UnsignedIntValue::U16(value)))
                }
            }
            3..=4 => {
                const MAX_WIDTH: usize = 4;
                let mut padded_be_bytes: [u8; MAX_WIDTH] = [0b0; MAX_WIDTH];
                self.pull_bytes_into(&mut padded_be_bytes[(MAX_WIDTH - width)..])?;

                #[cfg(feature = "tracing")]
                let bytes = crate::binary::fmt_bytes(&padded_be_bytes[(MAX_WIDTH - width)..]);

                let value = u32::from_be_bytes(padded_be_bytes);

                if is_signed {
                    let value = i32::from_zig_zag(value);

                    #[cfg(feature = "tracing")]
                    tracing::debug!(bytes = bytes, value = value);

                    Ok(IntValue::Signed(SignedIntValue::I32(value)))
                } else {
                    #[cfg(feature = "tracing")]
                    tracing::debug!(bytes = bytes, value = value);

                    Ok(IntValue::Unsigned(UnsignedIntValue::U32(value)))
                }
            }
            5..=8 => {
                const MAX_WIDTH: usize = 8;
                let mut padded_be_bytes: [u8; MAX_WIDTH] = [0b0; MAX_WIDTH];
                self.pull_bytes_into(&mut padded_be_bytes[(MAX_WIDTH - width)..])?;

                #[cfg(feature = "tracing")]
                let bytes = crate::binary::fmt_bytes(&padded_be_bytes[(MAX_WIDTH - width)..]);

                let value = u64::from_be_bytes(padded_be_bytes);

                if is_signed {
                    let value = i64::from_zig_zag(value);

                    #[cfg(feature = "tracing")]
                    tracing::debug!(bytes = bytes, value = value);

                    Ok(IntValue::Signed(SignedIntValue::I64(value)))
                } else {
                    #[cfg(feature = "tracing")]
                    tracing::debug!(bytes = bytes, value = value);

                    Ok(IntValue::Unsigned(UnsignedIntValue::U64(value)))
                }
            }
            _ => unreachable!(),
        }
    }
}