image4 0.8.2

A no_std-friendly library for parsing and generation of Image4 images written in pure Rust.
Documentation
use super::{ComprInfo, PayloadPropsRef};
use crate::{util, Tag};
use core::ops::Add;
use der::{
    asn1::{Ia5StringRef, OctetStringRef},
    Decode, DecodeValue, Encode, EncodeValue, ErrorKind, FixedTag, Header, Length, Reader,
    TagNumber, Writer,
};
#[cfg(any(feature = "alloc", test))]
use {
    super::Payload,
    der::referenced::{OwnedToRef, RefToOwned},
};

const MAGIC_DER: &[u8] = &[0x16, 0x04, 0x49, 0x4D, 0x34, 0x50];
const MAGIC_AND_TAG_DER_LEN: u16 = 12;

fn decode_4cc_tag<'a, R: Reader<'a>>(decoder: &mut R) -> der::Result<Tag> {
    let position = decoder.position();
    let tag_4cc: Ia5StringRef<'_> = decoder.decode()?;
    Tag::try_from(tag_4cc.as_bytes()).map_err(|_| {
        ErrorKind::Value {
            tag: der::Tag::Ia5String,
        }
        .at(position)
    })
}

/// An Image4 payload that doesn't own its data.
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct PayloadRef<'a> {
    pub(super) tag_4cc: Tag,
    pub(super) builder_string: Ia5StringRef<'a>,
    pub(super) data: OctetStringRef<'a>,
    pub(super) keybags: Option<OctetStringRef<'a>>,
    pub(super) compr_info: Option<ComprInfo>,
    pub(super) props: Option<PayloadPropsRef<'a>>,
}

impl<'a> PayloadRef<'a> {
    /// Creates a new payload from a tag, builder string and its contents.
    pub fn new(tag: Tag, builder_string: &'a str, data: &'a [u8]) -> der::Result<Self> {
        Ok(Self {
            tag_4cc: tag,
            builder_string: Ia5StringRef::new(builder_string)?,
            data: OctetStringRef::new(data)?,
            keybags: None,
            compr_info: None,
            props: None,
        })
    }

    /// Parses a DER-encoded payload into a [`PayloadRef`].
    pub fn parse(bytes: &'a [u8]) -> der::Result<Self> {
        Self::from_der(bytes)
    }

    /// Returns the 4CC tag of the payload.
    pub fn tag(&self) -> Tag {
        self.tag_4cc
    }

    /// Sets the 4CC tag of the payload.
    pub fn set_tag(&mut self, tag: Tag) {
        self.tag_4cc = tag;
    }

    /// Returns the string identifying the program used to build the payload.
    pub fn builder_string(&self) -> &str {
        self.builder_string.as_str()
    }

    /// Sets the string identifying the program used to build the payload.
    ///
    /// # Errors
    ///
    /// This function will return an error in case either the string contains characters that do not
    /// belong to the IA5 alphabet (ASCII characters larger than 127) or the length of the string is
    /// larger than the maximum length supported by the [`der`] crate ([`Length::MAX`]).
    pub fn set_builder_string(&mut self, s: &'a str) -> der::Result<()> {
        self.builder_string = Ia5StringRef::new(s)?;
        Ok(())
    }

    /// Returns a reference to the payload's data.
    pub fn data(&self) -> &[u8] {
        self.data.as_bytes()
    }

    /// Sets the payload's data.
    ///
    /// # Errors
    ///
    /// This function will return an error in case the length of the data is larger than the maximum
    /// length supported by the [`der`] crate ([`Length::MAX`]).
    pub fn set_data(&mut self, data: &'a [u8]) -> der::Result<()> {
        self.data = OctetStringRef::new(data)?;
        Ok(())
    }

    /// Returns a reference to the payload's encoded keybags if present.
    pub fn keybags(&self) -> Option<&'a [u8]> {
        self.keybags.as_ref().map(OctetStringRef::as_bytes)
    }

    /// Sets new encoded keybags for the payload.
    ///
    /// # Errors
    ///
    /// This function will return an error in case the length of the data is larger than the maximum
    /// length supported by the [`der`] crate ([`Length::MAX`]).
    pub fn set_keybags(&mut self, bytes: Option<&'a [u8]>) -> der::Result<()> {
        self.keybags = bytes.map(OctetStringRef::new).transpose()?;
        Ok(())
    }

    /// Returns a reference to the payload's compression info if present.
    pub fn compr_info(&self) -> Option<&ComprInfo> {
        self.compr_info.as_ref()
    }

    /// Sets new compression info for the payload.
    pub fn set_compr_info(&mut self, compr_info: Option<ComprInfo>) {
        self.compr_info = compr_info;
    }

    /// Returns a reference to the payload's properties if present.
    pub fn props(&self) -> Option<&PayloadPropsRef<'a>> {
        self.props.as_ref()
    }

    /// Sets new value for the payload's properties (or removes it).
    pub fn set_props(&mut self, props: Option<PayloadPropsRef<'a>>) {
        self.props = props;
    }

    /// Decodes the part of an image payload after the magic string.
    ///
    /// May be used when it is required to first identify what kind of buffer you're looking at by
    /// checking the magic string.
    pub fn decode_after_magic<R: Reader<'a>>(decoder: &mut R) -> der::Result<Self> {
        Ok(PayloadRef {
            tag_4cc: decode_4cc_tag(decoder)?,
            builder_string: decoder.decode()?,
            data: decoder.decode()?,
            keybags: decoder.decode()?,
            compr_info: decoder.decode()?,
            props: util::decode_opt_context_specific_field(decoder, TagNumber::N0, true)?,
        })
    }
}

#[cfg(any(feature = "alloc", test))]
impl<'a> From<&'a Payload> for PayloadRef<'a> {
    fn from(value: &'a Payload) -> Self {
        Self {
            tag_4cc: value.tag_4cc,
            builder_string: value.builder_string.owned_to_ref(),
            data: value.data.owned_to_ref(),
            keybags: value.keybags.owned_to_ref(),
            compr_info: value.compr_info.clone(),
            props: value.props.owned_to_ref(),
        }
    }
}

#[cfg(any(feature = "alloc", test))]
impl<'a> RefToOwned<'a> for PayloadRef<'a> {
    type Owned = Payload;

    fn ref_to_owned(&self) -> Self::Owned {
        self.into()
    }
}

impl<'a> DecodeValue<'a> for PayloadRef<'a> {
    fn decode_value<R: Reader<'a>>(decoder: &mut R, header: Header) -> der::Result<Self> {
        decoder.read_nested(header.length, |decoder| {
            util::decode_and_check_magic(decoder, b"IM4P")?;
            PayloadRef::decode_after_magic(decoder)
        })
    }
}

impl EncodeValue for PayloadRef<'_> {
    fn value_len(&self) -> der::Result<Length> {
        Length::new(MAGIC_AND_TAG_DER_LEN)
            .add(self.builder_string.encoded_len()?)?
            .add(self.data.encoded_len()?)?
            .add(self.keybags.encoded_len()?)?
            .add(self.compr_info.encoded_len()?)?
            .add(util::encoded_opt_context_specific_field_len(
                self.props.as_ref(),
                TagNumber::N0,
                true,
            )?)
    }

    fn encode_value(&self, encoder: &mut impl Writer) -> der::Result<()> {
        encoder.write(MAGIC_DER)?;

        let tag_str_bytes = self.tag_4cc.to_bytes();
        let tag_str = Ia5StringRef::new(&tag_str_bytes)?;
        tag_str.encode(encoder)?;

        self.builder_string.encode(encoder)?;
        self.data.encode(encoder)?;
        self.keybags.encode(encoder)?;
        self.compr_info.encode(encoder)?;
        util::encode_opt_context_specific_field(encoder, self.props.as_ref(), TagNumber::N0, true)?;

        Ok(())
    }
}

impl FixedTag for PayloadRef<'_> {
    const TAG: der::Tag = der::Tag::Sequence;
}