image4 0.8.2

A no_std-friendly library for parsing and generation of Image4 images written in pure Rust.
Documentation
mod borrowed {
    use crate::{property::DictRef, util};
    use core::ops::Add;
    use der::{
        asn1::Ia5StringRef, DecodeValue, Encode, EncodeValue, FixedTag, Header, Length, Reader,
        Writer,
    };
    #[cfg(any(feature = "alloc", test))]
    use {
        super::PayloadProps,
        crate::{property::Value, Tag},
        alloc::collections::BTreeMap,
        der::referenced::RefToOwned,
    };

    /// A reference to data that could be decoded as payload properties (aka PAYP) found in Image4
    /// containers.
    ///
    /// These store some metadata about the contents.
    #[derive(Clone, Debug)]
    #[cfg_attr(test, derive(Eq, PartialEq))]
    pub struct PayloadPropsRef<'a> {
        pub(super) body: DictRef<'a>,
    }

    impl<'a> PayloadPropsRef<'a> {
        /// Decodes the part of payload properties 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(PayloadPropsRef {
                body: decoder.decode()?,
            })
        }

        /// Decodes the body into a [`BTreeMap`].
        #[cfg(any(feature = "alloc", test))]
        pub fn decode_body(&self) -> der::Result<BTreeMap<Tag, Value>> {
            self.body.decode_owned()
        }
    }

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

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

    impl EncodeValue for PayloadPropsRef<'_> {
        fn value_len(&self) -> der::Result<Length> {
            let magic_len = Ia5StringRef::new(b"PAYP")?.encoded_len()?;
            let body_len = self.body.encoded_len()?;
            magic_len.add(body_len)
        }

        fn encode_value(&self, encoder: &mut impl Writer) -> der::Result<()> {
            let magic = Ia5StringRef::new(b"PAYP")?;
            magic.encode(encoder)?;
            self.body.encode(encoder)
        }
    }

    #[cfg(any(feature = "alloc", test))]
    impl<'a> From<&'a PayloadProps> for PayloadPropsRef<'a> {
        fn from(value: &'a PayloadProps) -> Self {
            Self {
                body: (&value.body).into(),
            }
        }
    }

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

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

#[cfg(any(feature = "alloc", test))]
mod owned {
    use super::PayloadPropsRef;
    use crate::{
        property::{Dict, Value},
        Tag,
    };
    use alloc::collections::BTreeMap;
    use der::{
        referenced::OwnedToRef, DecodeValue, EncodeValue, FixedTag, Header, Length, Reader, Writer,
    };

    /// Owned data that could be decoded as payload properties (aka PAYP) found in Image4
    /// containers.
    ///
    /// These store some metadata about the contents.
    #[derive(Clone, Debug)]
    #[cfg_attr(test, derive(Eq, PartialEq))]
    pub struct PayloadProps {
        pub(super) body: Dict,
    }

    impl PayloadProps {
        /// Decodes the part of payload properties 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<'a, R: Reader<'a>>(decoder: &mut R) -> der::Result<Self> {
            Ok(PayloadProps {
                body: decoder.decode()?,
            })
        }

        /// Encodes a [`BTreeMap`] describing a dictionary of Image4 properties into a
        /// [`PayloadProps`].
        pub fn encode_from(value: &BTreeMap<Tag, Value>) -> der::Result<Self> {
            Ok(Self {
                body: Dict::encode_from(value)?,
            })
        }

        /// Decodes the body into a [`BTreeMap`].
        pub fn decode_body(&self) -> der::Result<BTreeMap<Tag, Value>> {
            self.body.decode_owned()
        }
    }

    impl From<PayloadPropsRef<'_>> for PayloadProps {
        fn from(value: PayloadPropsRef<'_>) -> Self {
            (&value).into()
        }
    }

    impl From<&'_ PayloadPropsRef<'_>> for PayloadProps {
        fn from(value: &'_ PayloadPropsRef<'_>) -> Self {
            Self {
                body: (&value.body).into(),
            }
        }
    }

    impl FixedTag for PayloadProps {
        const TAG: der::Tag = der::Tag::Sequence;
    }

    impl<'a> DecodeValue<'a> for PayloadProps {
        fn decode_value<R: Reader<'a>>(decoder: &mut R, header: Header) -> der::Result<Self> {
            PayloadPropsRef::decode_value(decoder, header).map(PayloadProps::from)
        }
    }

    impl EncodeValue for PayloadProps {
        fn value_len(&self) -> der::Result<Length> {
            PayloadPropsRef::from(self).value_len()
        }

        fn encode_value(&self, encoder: &mut impl Writer) -> der::Result<()> {
            PayloadPropsRef::from(self).encode_value(encoder)
        }
    }

    impl OwnedToRef for PayloadProps {
        type Borrowed<'a> = PayloadPropsRef<'a>;

        fn owned_to_ref(&self) -> Self::Borrowed<'_> {
            self.into()
        }
    }
}

pub use borrowed::PayloadPropsRef;
#[cfg(any(feature = "alloc", test))]
pub use owned::PayloadProps;

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{property::Value, Tag};
    use alloc::{collections::BTreeMap, vec::Vec};
    use der::{Decode, Encode};
    use hex_literal::hex;

    const TEST_PROPS: &[u8] = &hex!(
        "3081B71604504159503181AEFF86DB8DCA7013301116046B636570020900FFFF
             FFF007D38540FF86DB8DD8660E300C16046B636C66020403550000FF86DB8DD8
             6F13301116046B636C6F020900FFFFFFF007004000FF86DB8DD87A0E300C1604
             6B636C7A020400F20000FF86DB8DE4660B300916046B637266020100FF86DB8D
             E47A0E300C16046B63727A0204031D8000FF86DB8DEE660E300C16046B637766
             0204031D8000FF86DB8DEE7A0D300B16046B63777A0203378000"
    );

    fn make_test_dict() -> BTreeMap<Tag, Value> {
        let mut dict = BTreeMap::new();
        dict.insert(Tag::from_bytes(*b"kcep"), 18446744005121377600u64.into());
        dict.insert(Tag::from_bytes(*b"kclf"), 55902208u64.into());
        dict.insert(Tag::from_bytes(*b"kclo"), 18446744005107531776u64.into());
        dict.insert(Tag::from_bytes(*b"kclz"), 15859712u64.into());
        dict.insert(Tag::from_bytes(*b"kcrf"), 0u64.into());
        dict.insert(Tag::from_bytes(*b"kcrz"), 52264960u64.into());
        dict.insert(Tag::from_bytes(*b"kcwf"), 52264960u64.into());
        dict.insert(Tag::from_bytes(*b"kcwz"), 3637248u64.into());
        dict
    }

    #[test]
    fn test_decode() {
        let props = PayloadPropsRef::from_der(TEST_PROPS).unwrap();
        let body = props.decode_body().unwrap();
        let expected = make_test_dict();

        assert_eq!(expected, body);
    }

    #[test]
    fn test_encode() {
        let props = PayloadPropsRef::from_der(TEST_PROPS).unwrap();
        let mut encoded = Vec::new();
        props.encode_to_vec(&mut encoded).unwrap();

        assert_eq!(encoded, TEST_PROPS);
    }
}