calypso_filety 3.0.0

Binary file type descriptions, parsers, etc. for Calypso
Documentation
use indexmap::{map::IntoIter, IndexMap};

use std::mem;

mod parse;

/// A CCFF container file.
///
/// This will contain all the sections of data (somewhat
/// similar in theory to ELF sections) as well as some basic metadata about the
/// file: its [ABI version](Self::set_abiver) and
/// [file type](Self::set_filety), both of which are user-defined.
///
/// It also stores a list of [`Section`]s, which can be accessed and modified
/// via methods on this structure such as [`get_section`][Self::get_section]
/// and [`add_section`][Self::add_section]. The order of these sections is
/// stored, however this is not guaranteed or required and may be changed in
/// the future.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ContainerFile {
    abiver: u16,
    filety: u8,
    sections: IndexMap<String, Section>,
}

type NomError = nom::Err<nom::error::Error<Vec<u8>>>;

impl ContainerFile {
    /// Create a new container file. The ABI version (`abiver`) and file type
    /// (`filety`) may be any arbitrary user-defined value.
    #[must_use]
    pub fn new(abiver: u16, filety: u8) -> Self {
        Self {
            abiver,
            filety,
            sections: IndexMap::new(),
        }
    }

    /// Set the ABI version of the container file. This may be any arbitrary
    /// user-defined value.
    pub fn set_abiver(&mut self, abiver: u16) {
        self.abiver = abiver;
    }

    /// Get the ABI version of the container file.
    #[must_use]
    pub fn get_abiver(&self) -> u16 {
        self.abiver
    }

    /// Set the file type of the container file. This may be any arbitrary
    /// user-defined value.
    pub fn set_filety(&mut self, filety: u8) {
        self.filety = filety;
    }

    /// Get the file type of the container file.
    #[must_use]
    pub fn get_filety(&self) -> u8 {
        self.filety
    }

    /// Add a section to the container file. If there was already a section
    /// with this name present, it will be replaced and returned.
    ///
    /// # Panics
    ///
    /// This function will panic if the name of the section was longer than 255
    /// characters, if the name of the section contained non-printable ASCII
    /// characters (less than `0x21` or greater than `0x7E`), or if there were
    /// already 255 sections in the container file.
    pub fn add_section(&mut self, name: String, section: Section) -> Option<Section> {
        let name_as_str = name.as_str();
        assert!(
            name_as_str.len() <= 255,
            "section name must be shorter than 256 characters"
        );
        assert!(
            name_as_str.chars().all(|c| c.is_ascii_graphic()),
            "section name must not contain non-printable ASCII characters (less than `0x21` or greater than `0x7E`)"
        );
        assert!(
            self.sections.len() <= 255,
            "container file can only contain up to 255 sections"
        );
        self.sections.insert(name, section)
    }

    /// Remove a section from the container file. The removed section, if any,
    /// will be returned.
    pub fn remove_section(&mut self, name: &str) -> Option<Section> {
        self.sections.shift_remove(name)
    }

    /// Get a reference to a section in the container file.
    #[must_use]
    pub fn get_section(&self, name: &str) -> Option<&Section> {
        self.sections.get(name)
    }

    /// Get a mutable reference to a section in the container file.
    pub fn get_section_mut(&mut self, name: &str) -> Option<&mut Section> {
        self.sections.get_mut(name)
    }

    /// Iterate over the sections in the container file.
    pub fn sections(&self) -> impl Iterator<Item = (&String, &Section)> {
        self.sections.iter()
    }

    /// Iterate mutably over the sections in the container file.
    pub fn sections_mut(&mut self) -> impl Iterator<Item = (&String, &mut Section)> {
        self.sections.iter_mut()
    }

    /// Get the size of the entire container file.
    #[must_use]
    pub fn size(&self) -> usize {
        4 // magic bytes
        + mem::size_of::<u16>() // abiver
        + mem::size_of::<u8>() // filety
        + mem::size_of::<u8>() // len(sections)
        + self.sections().map(|(name, _)| Section::sizeof(name)).sum::<usize>()
        + self.sections().map(|(_, section)| section.get_data().len()).sum::<usize>()
    }

    /// Decode this container file from the buffer provided.
    ///
    /// # Errors
    ///
    /// This function will return an error if the input fails to
    /// parse.
    pub fn decode(buf: &'_ [u8]) -> Result<Self, NomError> {
        Ok(parse::container_file(buf)
            .map_err(
                // This appears to be erroneously triggering here.
                #[allow(clippy::redundant_closure_for_method_calls)]
                |e| e.to_owned(),
            )?
            .1)
    }

    /// Encode this container file to the buffer provided. To allocate a
    /// sufficiently sized buffer, use [`Vec::with_capacity`] using the size
    /// given by [`ContainerFile::size`].
    ///
    /// # Panics
    ///
    /// This function will panic if a section was too large (larger than
    /// [`u32::MAX`] in bytes) or if there was too much data in the container
    /// file (due to architectural limitations, they are capped at around 4GiB)
    // We know that sections.len() will be <=255 as we do not allow adding
    // sections if there are already that amount.
    #[allow(clippy::cast_possible_truncation)]
    pub fn encode_to(self, buf: &mut Vec<u8>) {
        buf.extend(b"CCFF");
        buf.extend(self.abiver.to_le_bytes());
        buf.push(self.filety);
        buf.push(self.sections.len() as u8);

        let shdrs_size = self
            .sections()
            .map(|(name, _)| Section::sizeof(name))
            .sum::<usize>();

        let mut data = Vec::with_capacity(
            self.sections()
                .map(|(_, section)| section.get_data().len())
                .sum(),
        );

        self.sections.into_iter().fold(
            (buf.len() + shdrs_size) as u32,
            |data_offset, (name, section)| {
                let data_size = section.data.len();
                assert!(
                    data_size < u32::MAX as usize,
                    "section data must be less than 4GiB in size"
                );
                let name = name.as_str();
                data.extend(section.data);
                buf.push(section.stype);
                buf.extend(section.flags.to_le_bytes());
                buf.extend(data_offset.to_le_bytes());
                buf.extend((data_size as u32).to_le_bytes());
                buf.push(name.len() as u8);
                buf.extend(name.as_bytes());

                data_offset + data_size as u32
            },
        );
        buf.extend(data);
    }

    /// Encode this container file to a newly allocated buffer.
    #[must_use]
    pub fn encode(self) -> Vec<u8> {
        let mut buf = Vec::with_capacity(self.size());
        self.encode_to(&mut buf);
        buf
    }
}

impl IntoIterator for ContainerFile {
    type IntoIter = IntoIter<String, Section>;
    type Item = (String, Section);

    fn into_iter(self) -> Self::IntoIter {
        self.sections.into_iter()
    }
}

/// The section is the actual container of data with in a CCFF container file.
///
/// It stores some basic metadata: a [section type](Section::set_type) and
/// [flags](Section::set_flags), both of which are user-defined. The data can
/// be accessed and modified via the [`get_data`](Self::get_data),
/// [`get_data_mut`](Self::get_data_mut), and [`set_data`](Self::set_data)
/// functions.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Section {
    stype: u8,
    flags: u32,
    offset: Option<u32>,
    data: Vec<u8>,
}

impl Section {
    /// Create a section. The section type (`stype`) or flags may be any
    /// arbitrary user-defined value.
    #[must_use]
    pub fn new(stype: u8, flags: u32) -> Self {
        Self {
            stype,
            flags,
            offset: None,
            data: Vec::new(),
        }
    }

    /// Set the type of the section. This may be any arbitrary user-defined
    /// value.
    pub fn set_type(&mut self, stype: u8) {
        self.stype = stype;
    }

    /// Get the type of the section.
    #[must_use]
    pub fn get_type(&self) -> u8 {
        self.stype
    }

    /// Set the flags of the section. This may be any arbitrary user-defined
    /// value.
    pub fn set_flags(&mut self, flags: u32) {
        self.flags = flags;
    }

    /// Get the flags of the section.
    #[must_use]
    pub fn get_flags(&self) -> u32 {
        self.flags
    }

    /// Set the data of the section. This may be any arbitrary user-defined
    /// data. The previous data will be returned.
    pub fn set_data(&mut self, data: Vec<u8>) -> Vec<u8> {
        mem::replace(&mut self.data, data)
    }

    /// Get a reference to the data of the section.
    #[must_use]
    pub fn get_data(&self) -> &[u8] {
        &self.data
    }

    /// Get a mutable reference to the data of the section.
    pub fn get_data_mut(&mut self) -> &mut Vec<u8> {
        &mut self.data
    }

    /// Get the offset of the data in the container file. This is only present
    /// when loading from a file and cannot be set manually in order to prevent
    /// errors.
    #[must_use]
    pub fn get_offset(&self) -> Option<u32> {
        self.offset
    }

    fn sizeof(name: &str) -> usize {
        mem::size_of::<u8>() // type
            + mem::size_of::<u32>() // flags
            + mem::size_of::<u32>() // offset
            + mem::size_of::<u32>() // size
            + mem::size_of::<u8>() // sizeof(name)
            + name.len() // name
    }
}