blob: 9d2128130bc93562a0fc7909a0d5c4f3de8a6e38 [file] [edit]
// 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<EndOfFile>),
/// 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<T> From<ciborium::de::Error<T>> for CborError {
fn from(e: ciborium::de::Error<T>) -> 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<T> From<ciborium::ser::Error<T>> for CborError {
fn from(_e: ciborium::ser::Error<T>) -> 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<T>(value: &Value, want: &'static str) -> Result<T, CborError> {
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<Value, CborError> {
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<Self, CborError>;
/// Convert the object into a [`Value`], consuming it along the way.
fn to_cbor_value(self) -> Result<Value, CborError>;
/// Create an object instance from serialized CBOR data in a slice.
fn from_slice(slice: &[u8]) -> Result<Self, CborError> {
Self::from_cbor_value(read_to_value(slice)?)
}
/// Serialize this object to a vector, consuming it along the way.
fn into_vec(self) -> Result<Vec<u8>, CborError> {
let mut data = Vec::new();
ciborium::ser::into_writer(&self.to_cbor_value()?, &mut data)?;
Ok(data)
}
}
/// An `Option<T>` encodes as `( ? t )`, where `t` is whatever `T` encodes as in CDDL.
impl<T: AsCborValue> AsCborValue for Option<T> {
fn from_cbor_value(value: Value) -> Result<Self, CborError> {
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(<T>::from_cbor_value(arr.remove(0))?)),
_ => Err(CborError::UnexpectedItem("arr len >1", "arr len 0/1")),
}
}
fn to_cbor_value(self) -> Result<Value, CborError> {
match self {
Some(t) => Ok(Value::Array(vec![t.to_cbor_value()?])),
None => Ok(Value::Array(Vec::new())),
}
}
}
impl<T: AsCborValue, const N: usize> AsCborValue for [T; N] {
fn from_cbor_value(value: Value) -> Result<Self, CborError> {
let arr = match value {
Value::Array(a) => a,
_ => return cbor_type_error(&value, "arr"),
};
let results: Result<Vec<_>, _> = arr.into_iter().map(<T>::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<Value, CborError> {
let values: Result<Vec<_>, _> = self.into_iter().map(|v| v.to_cbor_value()).collect();
Ok(Value::Array(values?))
}
}
impl AsCborValue for Vec<u8> {
fn from_cbor_value(value: Value) -> Result<Self, CborError> {
match value {
Value::Bytes(bstr) => Ok(bstr),
_ => cbor_type_error(&value, "bstr"),
}
}
fn to_cbor_value(self) -> Result<Value, CborError> {
Ok(Value::Bytes(self))
}
}
impl AsCborValue for i32 {
fn from_cbor_value(value: Value) -> Result<Self, CborError> {
match value {
Value::Integer(i) => i.try_into().map_err(|_| CborError::OutOfRangeIntegerValue),
_ => crate::cbor_type_error(&value, "i32"),
}
}
fn to_cbor_value(self) -> Result<Value, CborError> {
Ok(Value::Integer(self.into()))
}
}