tinycbor 0.12.2

A tiny CBOR codec library.
Documentation
//! Containers.

use crate::{CborLen, Encode, Encoder, primitive};
#[cfg(feature = "alloc")]
use crate::{Decode, Decoder};

pub mod bounded;
pub mod map;

/// Possible errors when decoding containers.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Error<E> {
    /// Container header is malformed.
    Malformed(primitive::Error),
    /// Error decoding the content.
    Content(E),
}

impl<E> Error<E> {
    /// Map a function on the inner error.
    pub fn map<O>(self, f: impl FnOnce(E) -> O) -> Error<O> {
        match self {
            Error::Malformed(e) => Error::Malformed(e),
            Error::Content(e) => Error::Content(f(e)),
        }
    }
}

impl<E: core::fmt::Display> core::fmt::Display for Error<E> {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            Error::Malformed(_) => write!(f, "container header is malformed"),
            Error::Content(_) => write!(f, "container payload error"),
        }
    }
}

impl<E> From<primitive::Error> for Error<E> {
    fn from(e: primitive::Error) -> Self {
        Error::Malformed(e)
    }
}

impl<E> From<crate::EndOfInput> for Error<E> {
    fn from(_: crate::EndOfInput) -> Self {
        Error::Malformed(primitive::Error::EndOfInput)
    }
}

impl<E> From<crate::InvalidHeader> for Error<E> {
    fn from(_: crate::InvalidHeader) -> Self {
        Error::Malformed(primitive::Error::InvalidHeader)
    }
}

impl<E> From<bounded::Error<E>> for Error<bounded::Error<E>> {
    fn from(e: bounded::Error<E>) -> Self {
        Self::Content(e)
    }
}

impl<E: core::error::Error + 'static> core::error::Error for Error<E> {
    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
        match self {
            Error::Malformed(e) => Some(e),
            Error::Content(e) => Some(e),
        }
    }
}

#[cfg(feature = "alloc")]
impl<'b, T> Decode<'b> for alloc::collections::BinaryHeap<T>
where
    T: Decode<'b> + Ord,
{
    type Error = Error<T::Error>;

    fn decode(d: &mut Decoder<'b>) -> Result<Self, Self::Error> {
        let mut visitor = d.array_visitor()?;
        let mut v = Self::new();
        while let Some(elem) = visitor.visit() {
            v.push(elem.map_err(Error::Content)?);
        }

        Ok(v)
    }
}

#[cfg(feature = "std")]
impl<'b, T, S> Decode<'b> for std::collections::HashSet<T, S>
where
    T: Decode<'b> + Eq + std::hash::Hash,
    S: std::hash::BuildHasher + std::default::Default,
{
    type Error = Error<T::Error>;

    fn decode(d: &mut Decoder<'b>) -> Result<Self, Self::Error> {
        let mut visitor = d.array_visitor()?;
        let mut v = Self::default();
        while let Some(elem) = visitor.visit() {
            v.insert(elem.map_err(Error::Content)?);
        }

        Ok(v)
    }
}

#[cfg(feature = "alloc")]
impl<'b, T> Decode<'b> for alloc::collections::BTreeSet<T>
where
    T: Decode<'b> + Ord,
{
    type Error = Error<T::Error>;

    fn decode(d: &mut Decoder<'b>) -> Result<Self, Self::Error> {
        let mut visitor = d.array_visitor()?;
        let mut v = Self::new();
        while let Some(elem) = visitor.visit() {
            v.insert(elem.map_err(Error::Content)?);
        }
        Ok(v)
    }
}

#[cfg(feature = "alloc")]
macro_rules! decode_sequential {
    ($($t:ty, $push:ident)*) => {
        $(
            impl<'b, T: Decode<'b>> Decode<'b> for $t {
                type Error = Error<T::Error>;

                fn decode(d: &mut Decoder<'b>) -> Result<Self, Self::Error> {
                    let mut visitor = d.array_visitor()?;
                    let mut v = Self::new();
                    while let Some(x) = visitor.visit() {
                        v.$push(x.map_err(Error::Content)?)
                    }
                    Ok(v)
                }
            }
        )*
    }
}

#[cfg(feature = "alloc")]
decode_sequential! {
    alloc::vec::Vec<T>, push
    alloc::collections::VecDeque<T>, push_back
    alloc::collections::LinkedList<T>, push_back
}

macro_rules! encode_sequential {
    ($($t:ty)*) => {
        $(
            impl<T: Encode> Encode for $t {
                fn encode<W: embedded_io::Write>(&self, e: &mut Encoder<W>) -> Result<(), W::Error> {
                    e.array(self.len())?;
                    for x in self {
                        x.encode(e)?
                    }
                    Ok(())
                }
            }

            impl<T: CborLen> CborLen for $t {
                fn cbor_len(&self) -> usize {
                    let n = self.len();
                    n.cbor_len() + self.iter().map(|x| x.cbor_len()).sum::<usize>()
                }
            }
        )*
    }
}

#[cfg(feature = "alloc")]
encode_sequential! {
    alloc::vec::Vec<T>
    alloc::collections::VecDeque<T>
    alloc::collections::LinkedList<T>
    alloc::collections::BinaryHeap<T>
    alloc::collections::BTreeSet<T>
}

#[cfg(feature = "std")]
encode_sequential! {
    std::collections::HashSet<T>
}

encode_sequential!([T]);

#[cfg(all(test, feature = "alloc"))]
mod tests {
    use crate::{CborLen, Decode, Decoder, Encode, Encoder, test};

    const EMPTY_ARRAY: &[u8] = &[0x80];
    const EMPTY_INDEF: &[u8] = &[0x9f, 0xff];

    #[cfg(feature = "alloc")]
    fn test_binary_heap<'a, T>(cbor: &'a [u8], expected: &[T])
    where
        T: Decode<'a> + Encode + CborLen + Ord + PartialEq + core::fmt::Debug,
    {
        use alloc::collections::BinaryHeap;

        let heap = BinaryHeap::<T>::decode(&mut Decoder(cbor)).unwrap();
        let mut buf = Vec::new();
        heap.encode(&mut Encoder(&mut buf)).unwrap();
        assert_eq!(heap.cbor_len(), buf.len());
        assert_eq!(heap.into_sorted_vec(), expected);
    }

    #[test]
    fn definite() {
        #[cfg(feature = "alloc")]
        {
            use alloc::{collections::*, string::String, vec::Vec};

            assert!(test::<Vec<&str>>(Vec::new(), EMPTY_ARRAY).unwrap());
            assert!(
                test(
                    vec!["a", "b", "c"],
                    &[0x83, 0x61, 0x61, 0x61, 0x62, 0x61, 0x63]
                )
                .unwrap()
            );

            assert!(test::<VecDeque<String>>(VecDeque::new(), EMPTY_ARRAY).unwrap());
            assert!(
                test(
                    VecDeque::from([String::from("hi"), String::from("yo")]),
                    &[0x82, 0x62, 0x68, 0x69, 0x62, 0x79, 0x6f]
                )
                .unwrap()
            );

            assert!(test::<LinkedList<Option<i32>>>(LinkedList::new(), EMPTY_ARRAY).unwrap());
            assert!(
                test(
                    LinkedList::from([Some(-5), None, Some(10)]),
                    &[0x83, 0x24, 0xf6, 0x0a]
                )
                .unwrap()
            );

            test_binary_heap::<usize>(EMPTY_ARRAY, &[]);
            test_binary_heap::<usize>(
                &[0x83, 0x18, 0x64, 0x18, 0xc8, 0x18, 0x96],
                &[100, 150, 200],
            );

            assert!(test::<BTreeSet<i32>>(BTreeSet::new(), EMPTY_ARRAY).unwrap());
            assert!(
                test(
                    BTreeSet::from([-10, 0, 10, 20]),
                    &[0x84, 0x29, 0x00, 0x0a, 0x14]
                )
                .unwrap()
            );
        }

        #[cfg(feature = "std")]
        {
            use std::collections::HashSet;
            assert!(test::<HashSet<&str>>(HashSet::new(), EMPTY_ARRAY).unwrap());
            test(
                HashSet::from(["foo", "bar"]),
                &[0x82, 0x63, 0x66, 0x6f, 0x6f, 0x63, 0x62, 0x61, 0x72],
            )
            .unwrap();
        }
    }

    #[cfg(feature = "alloc")]
    #[test]
    fn indefinite() {
        use alloc::{collections::*, string::String, vec::Vec};

        assert!(!test::<Vec<&str>>(Vec::new(), EMPTY_INDEF).unwrap());
        assert!(!test(vec!["x", "y"], &[0x9f, 0x61, 0x78, 0x61, 0x79, 0xff]).unwrap());

        assert!(!test::<VecDeque<String>>(VecDeque::new(), EMPTY_INDEF).unwrap());
        assert!(
            !test(
                VecDeque::from([String::from("a")]),
                &[0x9f, 0x61, 0x61, 0xff]
            )
            .unwrap()
        );

        assert!(!test::<LinkedList<Option<i32>>>(LinkedList::new(), EMPTY_INDEF).unwrap());
        assert!(!test(LinkedList::from([Some(1), None]), &[0x9f, 0x01, 0xf6, 0xff]).unwrap());

        test_binary_heap::<usize>(EMPTY_INDEF, &[]);
        test_binary_heap::<usize>(&[0x9f, 0x18, 0x32, 0x18, 0x64, 0xff], &[50, 100]);

        assert!(!test::<BTreeSet<i32>>(BTreeSet::new(), EMPTY_INDEF).unwrap());
        assert!(!test(BTreeSet::from([5, 10]), &[0x9f, 0x05, 0x0a, 0xff]).unwrap());

        #[cfg(feature = "std")]
        {
            use std::collections::HashSet;
            assert!(!test::<HashSet<&str>>(HashSet::new(), EMPTY_INDEF).unwrap());
            assert!(
                !test(
                    HashSet::from(["test"]),
                    &[0x9f, 0x64, 0x74, 0x65, 0x73, 0x74, 0xff]
                )
                .unwrap()
            );
        }
    }

    #[cfg(feature = "alloc")]
    #[test]
    fn nested() {
        use alloc::{string::String, vec};
        assert!(
            test(
                vec![
                    vec![String::from("a")],
                    vec![String::from("b"), String::from("c")]
                ],
                &[0x82, 0x81, 0x61, 0x61, 0x82, 0x61, 0x62, 0x61, 0x63]
            )
            .unwrap()
        );
    }

    #[cfg(feature = "alloc")]
    #[test]
    fn long() {
        use alloc::vec::Vec;
        let mut cbor = vec![0x98, 25];
        for i in 0..25 {
            cbor.push(0x18);
            cbor.push(i);
        }
        assert!(!test((0..25).map(|i| i as usize).collect::<Vec<usize>>(), &cbor).unwrap());
    }
}