wolfcose 0.1.0

Safe Rust API for wolfSSL wolfCOSE.
#![allow(missing_docs)]

use std::collections::BTreeMap;
use wolfcose::{
    from_slice, to_slice, to_vec, ByteBuf, ByteStr, CborDeserialize, CborDeserializer,
    CborSerialize, CborSerializer, CborValue, Error,
};

#[derive(Debug, Eq, PartialEq)]
struct SensorReading {
    id: u32,
    label: String,
    samples: Vec<i32>,
    signature: ByteBuf,
}

impl CborSerialize for SensorReading {
    fn serialize(&self, serializer: &mut CborSerializer<'_>) -> wolfcose::Result<()> {
        serializer.map(4)?;
        "id".serialize(serializer)?;
        self.id.serialize(serializer)?;
        "label".serialize(serializer)?;
        self.label.serialize(serializer)?;
        "samples".serialize(serializer)?;
        self.samples.serialize(serializer)?;
        "signature".serialize(serializer)?;
        self.signature.serialize(serializer)?;
        Ok(())
    }
}

impl<'de> CborDeserialize<'de> for SensorReading {
    fn deserialize(deserializer: &mut CborDeserializer<'de>) -> wolfcose::Result<Self> {
        let len = deserializer.map()?;
        let mut id = None;
        let mut label = None;
        let mut samples = None;
        let mut signature = None;

        for _ in 0..len {
            match <&str>::deserialize(deserializer)? {
                "id" => id = Some(u32::deserialize(deserializer)?),
                "label" => label = Some(String::deserialize(deserializer)?),
                "samples" => samples = Some(Vec::<i32>::deserialize(deserializer)?),
                "signature" => signature = Some(ByteBuf::deserialize(deserializer)?),
                _ => return Err(Error::CborType),
            }
        }

        Ok(Self {
            id: id.ok_or(Error::CborMalformed)?,
            label: label.ok_or(Error::CborMalformed)?,
            samples: samples.ok_or(Error::CborMalformed)?,
            signature: signature.ok_or(Error::CborMalformed)?,
        })
    }
}

#[test]
fn serde_like_struct_round_trip() {
    let reading = SensorReading {
        id: 7,
        label: "temp".to_owned(),
        samples: vec![-1, 0, 1],
        signature: ByteBuf(vec![0xaa, 0xbb, 0xcc]),
    };

    let encoded = to_vec(&reading).unwrap();
    let decoded: SensorReading = from_slice(&encoded).unwrap();
    assert_eq!(decoded, reading);
}

#[test]
fn to_slice_reports_small_buffer() {
    let mut out = [0u8; 1];
    assert_eq!(to_slice(&1000u64, &mut out), Err(Error::BufferTooSmall));
}

#[test]
fn primitives_options_tuples_and_maps_round_trip() {
    let tuple = (true, Some(42u8), None::<u16>, "wolfCOSE".to_owned());
    let encoded = to_vec(&tuple).unwrap();
    let decoded: (bool, Option<u8>, Option<u16>, String) = from_slice(&encoded).unwrap();
    assert_eq!(decoded, tuple);

    let mut map = BTreeMap::new();
    map.insert("a".to_owned(), 1u64);
    map.insert("b".to_owned(), 2u64);
    let decoded_map: BTreeMap<String, u64> = from_slice(&to_vec(&map).unwrap()).unwrap();
    assert_eq!(decoded_map, map);
}

#[test]
fn byte_string_wrapper_round_trip() {
    let encoded = to_vec(&ByteStr(b"raw bytes")).unwrap();
    let borrowed: &[u8] = from_slice(&encoded).unwrap();
    assert_eq!(borrowed, b"raw bytes");

    let owned: ByteBuf = from_slice(&encoded).unwrap();
    assert_eq!(owned, ByteBuf(b"raw bytes".to_vec()));
}

#[test]
fn dynamic_value_round_trip() {
    let value = CborValue::Map(vec![
        (
            CborValue::Text("alg".to_owned()),
            CborValue::Integer(wolfcose::Algorithm::ES256.id() as i64),
        ),
        (
            CborValue::Text("payload".to_owned()),
            CborValue::Array(vec![
                CborValue::Bool(true),
                CborValue::Null,
                CborValue::Bytes(vec![1, 2, 3]),
            ]),
        ),
    ]);

    let encoded = to_vec(&value).unwrap();
    let decoded: CborValue = from_slice(&encoded).unwrap();
    assert_eq!(decoded, value);
}

#[test]
fn from_slice_rejects_trailing_data() {
    let mut encoded = to_vec(&1u8).unwrap();
    encoded.push(0);
    assert_eq!(from_slice::<u8>(&encoded), Err(Error::CborMalformed));
}