// Copyright 2023 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! CBOR serialization code. use alloc::{vec, vec::Vec}; use ciborium::value::Value; /// Marker structure indicating that the EOF was encountered when reading CBOR data. #[derive(Debug)] pub struct EndOfFile; /// Error type for failures in encoding or decoding CBOR types. pub enum CborError { /// CBOR decoding failure. DecodeFailed(ciborium::de::Error), /// CBOR encoding failure. EncodeFailed, /// CBOR input had extra data. ExtraneousData, /// Integer value outside expected range. OutOfRangeIntegerValue, /// Integer value that doesn't match expected set of allowed enum values. NonEnumValue, /// Unexpected CBOR item encountered (got, want). UnexpectedItem(&'static str, &'static str), /// Value conversion failure. InvalidValue, /// Allocation failure. AllocationFailed, } impl From> for CborError { fn from(e: ciborium::de::Error) -> Self { // Make sure we use our [`EndOfFile`] marker. use ciborium::de::Error::{Io, RecursionLimitExceeded, Semantic, Syntax}; let e = match e { Io(_) => Io(EndOfFile), Syntax(x) => Syntax(x), Semantic(a, b) => Semantic(a, b), RecursionLimitExceeded => RecursionLimitExceeded, }; CborError::DecodeFailed(e) } } impl From> for CborError { fn from(_e: ciborium::ser::Error) -> Self { CborError::EncodeFailed } } impl core::fmt::Debug for CborError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { CborError::DecodeFailed(de) => write!(f, "decode CBOR failure: {:?}", de), CborError::EncodeFailed => write!(f, "encode CBOR failure"), CborError::ExtraneousData => write!(f, "extraneous data in CBOR input"), CborError::OutOfRangeIntegerValue => write!(f, "out of range integer value"), CborError::NonEnumValue => write!(f, "integer not a valid enum value"), CborError::UnexpectedItem(got, want) => write!(f, "got {}, expected {}", got, want), CborError::InvalidValue => write!(f, "invalid CBOR value"), CborError::AllocationFailed => write!(f, "allocation failed"), } } } /// Return an error indicating that an unexpected CBOR type was encountered. pub fn cbor_type_error(value: &Value, want: &'static str) -> Result { let got = match value { Value::Integer(_) => "int", Value::Bytes(_) => "bstr", Value::Text(_) => "tstr", Value::Array(_) => "array", Value::Map(_) => "map", Value::Tag(_, _) => "tag", Value::Float(_) => "float", Value::Bool(_) => "bool", Value::Null => "null", _ => "unknown", }; Err(CborError::UnexpectedItem(got, want)) } /// Read a [`Value`] from a byte slice, failing if any extra data remains after the `Value` has been /// read. pub fn read_to_value(mut slice: &[u8]) -> Result { let value = ciborium::de::from_reader_with_recursion_limit(&mut slice, 16)?; if slice.is_empty() { Ok(value) } else { Err(CborError::ExtraneousData) } } /// Trait for types that can be converted to/from a [`Value`]. This is essentially the same as the /// `coset::AsCborValue` trait, but defining a copy locally allows us to implement it for primitive /// types without falling foul of the orphan trait rules. pub trait AsCborValue: Sized { /// Convert a [`Value`] into an instance of the type. fn from_cbor_value(value: Value) -> Result; /// Convert the object into a [`Value`], consuming it along the way. fn to_cbor_value(self) -> Result; /// Create an object instance from serialized CBOR data in a slice. fn from_slice(slice: &[u8]) -> Result { Self::from_cbor_value(read_to_value(slice)?) } /// Serialize this object to a vector, consuming it along the way. fn into_vec(self) -> Result, CborError> { let mut data = Vec::new(); ciborium::ser::into_writer(&self.to_cbor_value()?, &mut data)?; Ok(data) } } /// An `Option` encodes as `( ? t )`, where `t` is whatever `T` encodes as in CDDL. impl AsCborValue for Option { fn from_cbor_value(value: Value) -> Result { let mut arr = match value { Value::Array(a) => a, _ => return Err(CborError::UnexpectedItem("non-arr", "arr")), }; match arr.len() { 0 => Ok(None), 1 => Ok(Some(::from_cbor_value(arr.remove(0))?)), _ => Err(CborError::UnexpectedItem("arr len >1", "arr len 0/1")), } } fn to_cbor_value(self) -> Result { match self { Some(t) => Ok(Value::Array(vec![t.to_cbor_value()?])), None => Ok(Value::Array(Vec::new())), } } } impl AsCborValue for [T; N] { fn from_cbor_value(value: Value) -> Result { let arr = match value { Value::Array(a) => a, _ => return cbor_type_error(&value, "arr"), }; let results: Result, _> = arr.into_iter().map(::from_cbor_value).collect(); let results: Vec<_> = results?; results.try_into().map_err(|_e| CborError::UnexpectedItem("arr other len", "arr fixed len")) } fn to_cbor_value(self) -> Result { let values: Result, _> = self.into_iter().map(|v| v.to_cbor_value()).collect(); Ok(Value::Array(values?)) } } impl AsCborValue for Vec { fn from_cbor_value(value: Value) -> Result { match value { Value::Bytes(bstr) => Ok(bstr), _ => cbor_type_error(&value, "bstr"), } } fn to_cbor_value(self) -> Result { Ok(Value::Bytes(self)) } } impl AsCborValue for i32 { fn from_cbor_value(value: Value) -> Result { match value { Value::Integer(i) => i.try_into().map_err(|_| CborError::OutOfRangeIntegerValue), _ => crate::cbor_type_error(&value, "i32"), } } fn to_cbor_value(self) -> Result { Ok(Value::Integer(self.into())) } }