synta 0.1.4

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
//! ASN.1 length encoding and decoding
//!
//! ASN.1 uses two forms:
//! - Short form: Length < 128, encoded in one byte
//! - Long form: Length >= 128, first byte indicates number of length bytes
//! - Indefinite form: Only in BER/CER, not allowed in DER

use crate::error::{Error, Result};
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;

/// ASN.1 length
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Length {
    /// Definite length
    Definite(usize),
    /// Indefinite length (only in BER/CER, not allowed in DER)
    Indefinite,
}

impl Length {
    /// Calculate how many bytes this length requires when encoded
    pub fn encoded_len(&self) -> Result<usize> {
        match self {
            Length::Definite(len) => {
                if *len < 128 {
                    Ok(1)
                } else {
                    let mut temp = *len;
                    let mut num_bytes = 0usize;
                    while temp > 0 {
                        temp >>= 8;
                        num_bytes = num_bytes.checked_add(1).ok_or(Error::LengthOverflow)?;
                    }
                    // 1 byte for length-of-length + num_bytes
                    num_bytes.checked_add(1).ok_or(Error::LengthOverflow)
                }
            }
            Length::Indefinite => Ok(1), // Single 0x80 byte
        }
    }

    /// Encode this length to a buffer
    pub fn encode(&self, buffer: &mut Vec<u8>) -> Result<()> {
        match self {
            Length::Definite(len) => {
                if *len < 128 {
                    // Short form
                    buffer.push(*len as u8);
                } else {
                    // Long form
                    let mut temp = *len;
                    let mut num_bytes = 0;
                    while temp > 0 {
                        temp >>= 8;
                        num_bytes += 1;
                    }

                    if num_bytes > 127 {
                        return Err(Error::LengthTooLarge);
                    }

                    // First byte: 0x80 | num_bytes
                    buffer.push(0x80 | num_bytes);

                    // Encode length bytes in big-endian order
                    for i in (0..num_bytes).rev() {
                        buffer.push((*len >> (i * 8)) as u8);
                    }
                }
                Ok(())
            }
            Length::Indefinite => {
                buffer.push(0x80);
                Ok(())
            }
        }
    }

    /// Decode a length from a byte slice
    /// Returns (length, bytes_consumed)
    pub fn decode(bytes: &[u8], position: usize) -> Result<(Self, usize)> {
        if bytes.is_empty() {
            return Err(Error::UnexpectedEof { position });
        }

        let first_byte = bytes[0];

        if first_byte < 0x80 {
            // Short form (fast path - most common)
            Ok((Length::Definite(first_byte as usize), 1))
        } else if first_byte == 0x80 {
            // Indefinite form
            Ok((Length::Indefinite, 1))
        } else {
            // Long form (cold path)
            #[cfg(feature = "unchecked")]
            {
                Self::decode_long_form_unchecked(bytes, first_byte, position)
            }
            #[cfg(not(feature = "unchecked"))]
            {
                Self::decode_long_form_checked(bytes, first_byte, position)
            }
        }
    }

    /// Decode long form length with validation (safe)
    #[cfg(not(feature = "unchecked"))]
    #[cold]
    #[inline(never)]
    fn decode_long_form_checked(
        bytes: &[u8],
        first_byte: u8,
        position: usize,
    ) -> Result<(Self, usize)> {
        let num_bytes = (first_byte & 0x7F) as usize;

        if num_bytes == 0 {
            // 0x80 is indefinite, handled above
            // 0x80 with zero bytes is invalid
            return Err(Error::InvalidLength { position });
        }

        if num_bytes > 8 {
            // usize is at most 8 bytes on 64-bit systems
            return Err(Error::InvalidLength { position });
        }

        if bytes.len() < 1 + num_bytes {
            return Err(Error::UnexpectedEof {
                position: position + bytes.len(),
            });
        }

        let mut length: usize = 0;
        for i in 0..num_bytes {
            length = length.checked_shl(8).ok_or(Error::LengthOverflow)?;
            length = length
                .checked_add(bytes[1 + i] as usize)
                .ok_or(Error::LengthOverflow)?;
        }

        // DER requires shortest encoding (no leading zeros)
        // We'll check this in DER-specific validation
        // For now, just decode

        Ok((Length::Definite(length), 1 + num_bytes))
    }

    /// Decode long form length without validation (fast but assumes valid DER)
    #[cfg(feature = "unchecked")]
    #[cold]
    #[inline(never)]
    fn decode_long_form_unchecked(
        bytes: &[u8],
        first_byte: u8,
        _position: usize,
    ) -> Result<(Self, usize)> {
        let num_bytes = (first_byte & 0x7F) as usize;

        // Skip validation - assume valid DER
        let mut length: usize = 0;
        for i in 0..num_bytes {
            length = (length << 8) | (bytes[1 + i] as usize);
        }

        Ok((Length::Definite(length), 1 + num_bytes))
    }

    /// Get the definite length value, or error if indefinite
    pub fn definite(&self) -> Result<usize> {
        match self {
            Length::Definite(len) => Ok(*len),
            Length::Indefinite => Err(Error::Custom(
                #[cfg(feature = "std")]
                "Expected definite length".to_string(),
                #[cfg(not(feature = "std"))]
                "Expected definite length",
            )),
        }
    }

    /// Check if this is definite length
    pub fn is_definite(&self) -> bool {
        matches!(self, Length::Definite(_))
    }

    /// Check if this is indefinite length
    pub fn is_indefinite(&self) -> bool {
        matches!(self, Length::Indefinite)
    }
}