image4 0.8.2

A no_std-friendly library for parsing and generation of Image4 images written in pure Rust.
Documentation
use super::{
    iter::{ArrayIter, DictIter},
    PropertyRef,
};
use core::marker::PhantomData;
use der::{
    asn1::OctetStringRef, Decode, DecodeValue, Encode, ErrorKind, Length, Reader, Tagged, Writer,
};
#[cfg(any(feature = "alloc", test))]
use {
    super::{common, Array, Dict},
    crate::Tag,
    alloc::{collections::BTreeMap, vec::Vec},
    der::{referenced::RefToOwned, Header, SliceReader},
};

macro_rules! impl_borrowed_collection {
    ($(#[$m:meta])* $name:ident, $owned:ident, $def_value_ty:ident, $iter:ident, $tag:ident) => {
        #[derive(Clone, Debug)]
        #[cfg_attr(test, derive(Eq, PartialEq))]
        $(#[$m])*
        pub struct $name<'a, V = $def_value_ty<'a>> {
            pub(super) bytes: &'a [u8],
            pub(super) header_length: Length,
            #[cfg(any(feature = "alloc", test))]
            pub(super) length: Length,
            pub(super) _marker: PhantomData<V>,
        }

        impl<'a, V> $name<'a, V> {
            #[doc = concat!(
                "Returns a reference to the bytes wrapped by a ", stringify!($name), "."
            )]
            pub fn as_bytes(&self) -> &'a [u8] {
                self.bytes
            }
        }

        impl<'a, V: PropertyRef<'a> + 'a> $name<'a, V> {
            #[doc = concat!("Returns an iterator over the contents of a ", stringify!($name), ".")]
            pub fn iter(&self) -> der::Result<$iter<'a, V>> {
                let mut reader = ::der::SliceReader::new(self.as_bytes())?;
                reader.read_slice(self.header_length)?;

                Ok($iter::new(reader))
            }
        }

        impl<'a, V> Decode<'a> for $name<'a, V> {
            fn decode<R: Reader<'a>>(decoder: &mut R) -> der::Result<Self> {
                let header = decoder.peek_header()?;
                if header.tag != der::Tag::$tag {
                    return Err(decoder.error(ErrorKind::TagUnexpected {
                        expected: Some(der::Tag::$tag),
                        actual: header.tag,
                    }));
                }

                let header_length = header.encoded_len()?;
                let full_length = (header_length + header.length)?;

                Ok(Self {
                    bytes: decoder.read_slice(full_length)?,
                    header_length: header.encoded_len()?,
                    #[cfg(any(feature = "alloc", test))]
                    length: full_length,
                    _marker: Default::default(),
                })
            }
        }

        impl<V> Encode for $name<'_, V> {
            fn encoded_len(&self) -> der::Result<Length> {
                Length::try_from(self.bytes.len())
            }

            fn encode(&self, encoder: &mut impl Writer) -> der::Result<()> {
                encoder.write(self.bytes)
            }
        }

        #[cfg(any(feature = "alloc", test))]
        impl<'a, V: PropertyRef<'a> + 'a> From<&'a $owned<V::Owned>> for $name<'a, V> {
            fn from(value: &'a $owned<V::Owned>) -> Self {
                Self {
                    bytes: &value.bytes,
                    header_length: value.header_length,
                    length: value.length,
                    _marker: Default::default(),
                }
            }
        }

        #[cfg(any(feature = "alloc", test))]
        impl<'a, V: PropertyRef<'a> + 'a> RefToOwned<'a> for $name<'a, V> {
            type Owned = $owned<V::Owned>;

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

impl_borrowed_collection!(
    /// Holds a reference to the body of a value that should be decodable as a set of Image4
    /// properties (aka a dictionary). The actual decoding of the contents
    ///
    /// # Usage
    ///
    /// This type is expected to be used as follows:
    ///
    /// 1. A DER set of Image4 properties is decoded into it using the [`Decode`] trait from the
    /// [`der`] crate;
    /// 2. The values inside are either parsed into a [`BTreeMap`] using the
    /// [`DictRef::decode_owned`] method or ignored.
    ///
    /// This type can be converted to an owned [`Dict`] type.
    ///
    /// [`Encode`] trait is also implemented so a [`DictRef`] may be encoded back into DER without
    /// any serialization.
    ///
    /// # Validating the contents
    ///
    /// The contents of a dictionary aren't validated anyhow during decoding and may even be an
    /// invalid DER encoding. Currently the only way to validate the contents is by calling the
    /// [`DictRef::decode_owned`] method.
    DictRef,
    Dict,
    ValueRef,
    DictIter,
    Set
);

impl<'a, V: PropertyRef<'a>> DictRef<'a, V> {
    /// Decodes the contents of the body into an owned type.
    ///
    /// This function completely validates the contents and currently is the only way to do
    /// this.
    #[cfg(any(feature = "alloc", test))]
    pub fn decode_owned(&self) -> der::Result<BTreeMap<Tag, V::Owned>> {
        let header = Header {
            tag: der::Tag::Set,
            length: (self.length - self.header_length).expect("invalid lengths"),
        };

        let mut decoder = SliceReader::new(self.bytes)?;
        decoder.read_slice(self.header_length)?;

        common::decode_dict_body(&mut decoder, header)
    }
}

impl_borrowed_collection!(
    /// Holds a reference to the body of a value that may be decoded as a sequence of Image4
    /// properties (aka an array).
    ///
    /// # Usage
    ///
    /// This type is expected to be used as follows:
    ///
    /// 1. A DER sequence of Image4 properties is decoded into it using the [`Decode`] trait from
    /// the [`der`] crate;
    /// 2. The values inside are either parsed into a [`Vec`] using the [`ArrayRef::decode_owned`]
    /// method or ignored.
    ///
    /// This type can be converted to an owned [`Array`] type.
    ///
    /// [`Encode`] trait is also implemented so an [`ArrayRef`] may be encoded back into DER without
    /// any serialization.
    ///
    /// # Validating the contents
    ///
    /// The contents of an array aren't validated anyhow during decoding and may even be an
    /// invalid DER encoding. Currently the only way to validate the contents is by calling the
    /// [`ArrayRef::decode_owned`] method.
    ArrayRef,
    Array,
    ValueRef,
    ArrayIter,
    Sequence
);

impl<'a, V: PropertyRef<'a>> ArrayRef<'a, V> {
    /// Decodes the contents of the body into an owned type.
    ///
    /// This function completely validates the contents and currently is the only way to do
    /// this.
    #[cfg(any(feature = "alloc", test))]
    pub fn decode_owned(&self) -> der::Result<Vec<V::Owned>> {
        let header = Header {
            tag: der::Tag::Set,
            length: (self.length - self.header_length).expect("invalid lengths"),
        };

        let mut decoder = SliceReader::new(self.bytes)?;
        decoder.read_slice(self.header_length)?;

        let value = Vec::decode_value(&mut decoder, header)?;
        decoder.finish(value)
    }
}

macro_rules! def_borrowed_value {
    (
        $(#[$m:meta])*
        $name:ident
        $(,
            $(#[$var_meta:meta])*
            $var:ident($var_tag_num:ident)
        )*
    ) => {
        #[derive(Clone, Debug)]
        #[cfg_attr(test, derive(Eq, PartialEq))]
        $(#[$m])*
        pub enum $name<'a> {
            /// A boolean value.
            Boolean(bool),
            /// An integer value.
            Integer(u64),
            /// A data value.
            Data(&'a [u8]),
            /// A dictionary of values.
            Dict(DictRef<'a, $name<'a>>),
            /// An array of values.
            Array(ArrayRef<'a, $name<'a>>),
            $($(#[$var_meta])* $var,)*
        }

        impl From<bool> for $name<'_> {
            fn from(value: bool) -> Self {
                $name::Boolean(value)
            }
        }

        impl From<u8> for $name<'_> {
            fn from(value: u8) -> Self {
                $name::Integer(value as u64)
            }
        }

        impl From<u16> for $name<'_> {
            fn from(value: u16) -> Self {
                $name::Integer(value as u64)
            }
        }

        impl From<u32> for $name<'_> {
            fn from(value: u32) -> Self {
                $name::Integer(value as u64)
            }
        }

        impl From<u64> for $name<'_> {
            fn from(value: u64) -> Self {
                $name::Integer(value)
            }
        }

        impl<'a> From<&'a [u8]> for $name<'a> {
            fn from(value: &'a [u8]) -> Self {
                $name::Data(value)
            }
        }

        impl<'a, const N: usize> From<&'a [u8; N]> for $name<'a> {
            fn from(value: &'a [u8; N]) -> Self {
                $name::Data(value)
            }
        }

        impl<'a> ::der::Decode<'a> for $name<'a> {
            fn decode<R: ::der::Reader<'a>>(decoder: &mut R) -> ::der::Result<Self> {
                let position = decoder.position();
                let slice = decoder.tlv_bytes()?;
                let mut decoder = ::der::SliceReader::new(slice)?;
                let header = decoder.decode::<::der::Header>()?;

                let result = match header.tag {
                    ::der::Tag::Boolean => Ok($name::Boolean(bool::decode_value(&mut decoder, header)?)),
                    ::der::Tag::Integer => Ok($name::Integer(u64::decode_value(&mut decoder, header)?)),
                    ::der::Tag::OctetString => OctetStringRef::decode_value(&mut decoder, header)
                        .map(|os| $name::Data(os.as_bytes())),
                    ::der::Tag::Set => Ok($name::Dict(DictRef {
                        bytes: slice,
                        header_length: header.encoded_len()?,
                        #[cfg(any(feature = "alloc", test))]
                        length: Length::try_from(slice.len())?,
                        _marker: Default::default(),
                    })),
                    ::der::Tag::Sequence => Ok($name::Array($crate::property::ArrayRef {
                        bytes: slice,
                        header_length: header.encoded_len()?,
                        #[cfg(any(feature = "alloc", test))]
                        length: Length::try_from(slice.len())?,
                        _marker: ::core::default::Default::default(),
                    })),
                    $(::der::Tag::ContextSpecific {
                        constructed: true,
                        number: ::der::TagNumber::$var_tag_num
                    } => <()>::decode(&mut decoder).map(|_| ConstraintRef::$var),)*
                    other => Err(decoder.error(::der::ErrorKind::TagUnexpected {
                        expected: ::core::option::Option::None,
                        actual: other,
                    })),
                };

                result.map_err(|e| $crate::util::shift_error_position(e, position))
            }
        }

        impl Tagged for $name<'_> {
            fn tag(&self) -> der::Tag {
                match self {
                    $name::Boolean(_) => ::der::Tag::Boolean,
                    $name::Integer(_) => ::der::Tag::Integer,
                    $name::Data(_) => ::der::Tag::OctetString,
                    $name::Dict(_) => ::der::Tag::Set,
                    $name::Array(_) => ::der::Tag::Sequence,
                    $($name::$var => ::der::Tag::ContextSpecific {
                        constructed: true,
                        number: ::der::TagNumber::$var_tag_num
                    },)*
                }
            }
        }

        impl ::der::Encode for $name<'_> {
            fn encoded_len(&self) -> ::der::Result<Length> {
                match self {
                    $name::Boolean(value) => value.encoded_len(),
                    $name::Integer(value) => value.encoded_len(),
                    $name::Data(data) => ::der::asn1::OctetStringRef::new(data)?.encoded_len(),
                    $name::Dict(dict) => dict.encoded_len(),
                    $name::Array(array) => array.encoded_len(),
                    $($name::$var => ::der::asn1::ContextSpecificRef {
                        tag_number: ::der::TagNumber::$var_tag_num,
                        tag_mode: ::der::TagMode::Explicit,
                        value: &(),
                    }.encoded_len(),)*
                }
            }

            fn encode(&self, encoder: &mut impl ::der::Writer) -> ::der::Result<()> {
                match self {
                    $name::Boolean(value) => value.encode(encoder),
                    $name::Integer(value) => value.encode(encoder),
                    $name::Data(data) => ::der::asn1::OctetStringRef::new(data)?.encode(encoder),
                    $name::Dict(dict) => dict.encode(encoder),
                    $name::Array(array) => array.encode(encoder),
                    $($name::$var => ::der::asn1::ContextSpecificRef {
                        tag_number: ::der::TagNumber::$var_tag_num,
                        tag_mode: ::der::TagMode::Explicit,
                        value: &(),
                    }.encode(encoder),)*
                }
            }
        }
    };
}

def_borrowed_value!(
    /// Represents an Image4 property list value that may be found in a manifest or in a payload
    /// properties blob.
    ///
    /// This is a referenced version of the [`Value`] type.
    ///
    /// [`Value`]: crate::property::Value
    ValueRef
);

def_borrowed_value!(
    /// Represents an Image4 property list value that may be found in an Apple-specific extension of
    /// X.509 certificates used to sign Image4 manifests.
    ///
    /// This is exactly the same type as [`ValueRef`] with addition of the [`ConstraintRef::Any`]
    /// and [`ConstraintRef::Deny`] variants.
    ///
    /// For an owned version see [`Constraint`].
    ///
    /// [`Constraint`]: crate::property::Constraint
    ConstraintRef,
    /// Matches any possible value.
    Any(N0),
    /// Never matches a value.
    Deny(N1)
);