synta 0.2.4

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
//! DER/BER encoder implementation

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

/// Book-keeping record pushed onto the stack when a constructed element is opened.
///
/// When [`Encoder::start_constructed`] is called the encoder reserves space in
/// the output buffer for the length field and records the positions needed to
/// back-patch it.  The actual length value is filled in by
/// [`Encoder::backpatch_length`] when the element is closed.
struct LengthFixup {
    /// Position in buffer where length bytes start
    length_position: usize,
    /// Number of bytes reserved for length
    reserved_bytes: usize,
    /// Position where content starts
    content_start: usize,
}

/// Encoder for ASN.1 DER/BER data
///
/// Uses a backpatching approach to avoid double-traversal for length calculation.
pub struct Encoder {
    buffer: Vec<u8>,
    encoding: Encoding,
    /// Stack of length positions that need backpatching
    length_stack: Vec<LengthFixup>,
}

impl Encoder {
    /// Create a new encoder
    pub fn new(encoding: Encoding) -> Self {
        Self {
            buffer: Vec::new(),
            encoding,
            length_stack: Vec::new(),
        }
    }

    /// Create an encoder with a preallocated byte-buffer capacity.
    ///
    /// `capacity` is the initial capacity of the underlying `Vec<u8>` in
    /// **bytes** (not in number of ASN.1 elements).  Use this when the
    /// approximate output size is known in advance to avoid reallocation.
    pub fn with_capacity(encoding: Encoding, capacity: usize) -> Self {
        Self {
            buffer: Vec::with_capacity(capacity),
            encoding,
            length_stack: Vec::new(),
        }
    }

    /// Get the encoding type
    pub fn encoding(&self) -> Encoding {
        self.encoding
    }

    /// Write a tag
    pub fn write_tag(&mut self, tag: Tag) -> Result<()> {
        tag.encode(&mut self.buffer)
    }

    /// Write a definite length (used for primitive types)
    pub fn write_length(&mut self, length: usize) -> Result<()> {
        Length::Definite(length).encode(&mut self.buffer)
    }

    /// Write raw bytes to the buffer
    pub fn write_bytes(&mut self, bytes: &[u8]) {
        self.buffer.extend_from_slice(bytes);
    }

    /// Encode a value using the Encode trait
    pub fn encode<T: crate::traits::Encode>(&mut self, value: &T) -> Result<()> {
        value.encode(self)
    }

    /// Encode a value with explicit tag
    pub fn encode_with_tag<T: crate::traits::Encode>(&mut self, tag: Tag, value: &T) -> Result<()> {
        self.write_tag(tag)?;
        let len = value.encoded_len()?;
        self.write_length(len)?;
        value.encode(self)
    }

    /// Open a constructed ASN.1 element (SEQUENCE, SET, or any constructed tag)
    /// and return an RAII guard that back-patches the length on drop.
    ///
    /// The tag is written immediately to the buffer.  Five bytes are reserved
    /// for the length field; the actual content length is computed and written
    /// when the returned [`ConstructedGuard`] is dropped or when
    /// [`ConstructedGuard::finish`] is called explicitly.
    ///
    /// # Errors
    ///
    /// Returns an error if encoding the tag fails.
    pub fn start_constructed(&mut self, tag: Tag) -> Result<ConstructedGuard<'_>> {
        // Write the tag
        self.write_tag(tag)?;

        // Reserve maximum space for length (5 bytes for lengths up to 2^32-1)
        let length_position = self.buffer.len();
        self.buffer.extend_from_slice(&[0; 5]);
        let content_start = self.buffer.len();

        self.length_stack.push(LengthFixup {
            length_position,
            reserved_bytes: 5,
            content_start,
        });

        Ok(ConstructedGuard { encoder: self })
    }

    /// Finish encoding and get the encoded bytes
    pub fn finish(mut self) -> Result<Vec<u8>> {
        if !self.length_stack.is_empty() {
            return Err(Error::UnfinishedConstructed);
        }

        // Shrink to fit to avoid wasted memory
        self.buffer.shrink_to_fit();
        Ok(self.buffer)
    }

    /// Open a constructed ASN.1 element without returning an RAII guard.
    ///
    /// Intended for the FFI layer or other contexts where the Rust borrow
    /// checker RAII pattern is inconvenient.  The caller **must** call
    /// [`end_constructed`](Self::end_constructed) exactly once for each call
    /// to this method.
    ///
    /// # Errors
    ///
    /// Returns an error if encoding the tag fails.
    pub fn start_constructed_no_guard(&mut self, tag: Tag) -> Result<()> {
        // Write the tag
        self.write_tag(tag)?;

        // Reserve maximum space for length (5 bytes for lengths up to 2^32-1)
        let length_position = self.buffer.len();
        self.buffer.extend_from_slice(&[0; 5]);
        let content_start = self.buffer.len();

        self.length_stack.push(LengthFixup {
            length_position,
            reserved_bytes: 5,
            content_start,
        });

        Ok(())
    }

    /// End the innermost open constructed element and back-patch its length.
    ///
    /// Manual counterpart to dropping a [`ConstructedGuard`].  Must be called
    /// exactly once for each call to
    /// [`start_constructed_no_guard`](Self::start_constructed_no_guard).
    ///
    /// # Errors
    ///
    /// Returns [`Error::NoConstructedToFinish`](crate::Error) if there is no
    /// open constructed element on the stack, or a length error if the content
    /// does not fit in the reserved bytes.
    pub fn end_constructed(&mut self) -> Result<()> {
        self.backpatch_length()
    }

    /// Internal: backpatch a length value
    fn backpatch_length(&mut self) -> Result<()> {
        let fixup = self
            .length_stack
            .pop()
            .ok_or(Error::NoConstructedToFinish)?;

        let content_len = self.buffer.len() - fixup.content_start;

        // Encode the actual length
        let mut length_bytes = Vec::with_capacity(5);
        Length::Definite(content_len).encode(&mut length_bytes)?;

        let actual_length_bytes = length_bytes.len();

        if actual_length_bytes > fixup.reserved_bytes {
            return Err(Error::LengthTooLarge);
        }

        // Calculate how many bytes we over-reserved
        let excess = fixup.reserved_bytes - actual_length_bytes;

        if excess > 0 {
            // Shift content left to remove excess reserved bytes
            let content_start = fixup.content_start;
            let new_content_start = fixup.length_position + actual_length_bytes;
            self.buffer.copy_within(content_start.., new_content_start);
            self.buffer.truncate(self.buffer.len() - excess);
        }

        // Write the actual length bytes
        self.buffer[fixup.length_position..fixup.length_position + actual_length_bytes]
            .copy_from_slice(&length_bytes);

        Ok(())
    }
}

/// RAII guard for a constructed ASN.1 element.
///
/// Returned by [`Encoder::start_constructed`].  When this guard is dropped
/// the encoder back-patches the length field of the constructed element with
/// the actual number of content bytes written since the element was opened.
///
/// If you need to handle a back-patching error, call
/// [`finish`](Self::finish) explicitly; an implicit drop discards the error.
pub struct ConstructedGuard<'a> {
    encoder: &'a mut Encoder,
}

impl<'a> ConstructedGuard<'a> {
    /// Encode a child element
    pub fn encode<T: crate::traits::Encode>(&mut self, value: &T) -> Result<()> {
        self.encoder.encode(value)
    }

    /// Write a tag for a child element
    pub fn write_tag(&mut self, tag: Tag) -> Result<()> {
        self.encoder.write_tag(tag)
    }

    /// Write a length for a child element
    pub fn write_length(&mut self, length: usize) -> Result<()> {
        self.encoder.write_length(length)
    }

    /// Write raw bytes
    pub fn write_bytes(&mut self, bytes: &[u8]) -> Result<()> {
        self.encoder.buffer.extend_from_slice(bytes);
        Ok(())
    }

    /// Explicitly finish this constructed element and back-patch its length.
    ///
    /// Identical to dropping the guard, but lets the caller propagate a
    /// back-patching error.  After this call the guard is consumed and the
    /// underlying encoder is released.
    ///
    /// # Errors
    ///
    /// Returns an error if back-patching the length fails.
    pub fn finish(self) -> Result<()> {
        // Drop will handle backpatching
        Ok(())
    }
}

impl Drop for ConstructedGuard<'_> {
    fn drop(&mut self) {
        // Backpatch the length
        // In production, handle error appropriately (e.g., panic or store in encoder)
        let _ = self.encoder.backpatch_length();
    }
}