1 // Copyright 2023 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 //! CBOR serialization code.
16
17 use alloc::{vec, vec::Vec};
18 use ciborium::value::Value;
19
20 /// Marker structure indicating that the EOF was encountered when reading CBOR data.
21 #[derive(Debug)]
22 pub struct EndOfFile;
23
24 /// Error type for failures in encoding or decoding CBOR types.
25 pub enum CborError {
26 /// CBOR decoding failure.
27 DecodeFailed(ciborium::de::Error<EndOfFile>),
28 /// CBOR encoding failure.
29 EncodeFailed,
30 /// CBOR input had extra data.
31 ExtraneousData,
32 /// Integer value outside expected range.
33 OutOfRangeIntegerValue,
34 /// Integer value that doesn't match expected set of allowed enum values.
35 NonEnumValue,
36 /// Unexpected CBOR item encountered (got, want).
37 UnexpectedItem(&'static str, &'static str),
38 /// Value conversion failure.
39 InvalidValue,
40 /// Allocation failure.
41 AllocationFailed,
42 }
43
44 impl<T> From<ciborium::de::Error<T>> for CborError {
from(e: ciborium::de::Error<T>) -> Self45 fn from(e: ciborium::de::Error<T>) -> Self {
46 // Make sure we use our [`EndOfFile`] marker.
47 use ciborium::de::Error::{Io, RecursionLimitExceeded, Semantic, Syntax};
48 let e = match e {
49 Io(_) => Io(EndOfFile),
50 Syntax(x) => Syntax(x),
51 Semantic(a, b) => Semantic(a, b),
52 RecursionLimitExceeded => RecursionLimitExceeded,
53 };
54 CborError::DecodeFailed(e)
55 }
56 }
57
58 impl<T> From<ciborium::ser::Error<T>> for CborError {
from(_e: ciborium::ser::Error<T>) -> Self59 fn from(_e: ciborium::ser::Error<T>) -> Self {
60 CborError::EncodeFailed
61 }
62 }
63
64 impl core::fmt::Debug for CborError {
fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result65 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
66 match self {
67 CborError::DecodeFailed(de) => write!(f, "decode CBOR failure: {:?}", de),
68 CborError::EncodeFailed => write!(f, "encode CBOR failure"),
69 CborError::ExtraneousData => write!(f, "extraneous data in CBOR input"),
70 CborError::OutOfRangeIntegerValue => write!(f, "out of range integer value"),
71 CborError::NonEnumValue => write!(f, "integer not a valid enum value"),
72 CborError::UnexpectedItem(got, want) => write!(f, "got {}, expected {}", got, want),
73 CborError::InvalidValue => write!(f, "invalid CBOR value"),
74 CborError::AllocationFailed => write!(f, "allocation failed"),
75 }
76 }
77 }
78
79 /// Return an error indicating that an unexpected CBOR type was encountered.
cbor_type_error<T>(value: &Value, want: &'static str) -> Result<T, CborError>80 pub fn cbor_type_error<T>(value: &Value, want: &'static str) -> Result<T, CborError> {
81 let got = match value {
82 Value::Integer(_) => "int",
83 Value::Bytes(_) => "bstr",
84 Value::Text(_) => "tstr",
85 Value::Array(_) => "array",
86 Value::Map(_) => "map",
87 Value::Tag(_, _) => "tag",
88 Value::Float(_) => "float",
89 Value::Bool(_) => "bool",
90 Value::Null => "null",
91 _ => "unknown",
92 };
93 Err(CborError::UnexpectedItem(got, want))
94 }
95
96 /// Read a [`Value`] from a byte slice, failing if any extra data remains after the `Value` has been
97 /// read.
read_to_value(mut slice: &[u8]) -> Result<Value, CborError>98 pub fn read_to_value(mut slice: &[u8]) -> Result<Value, CborError> {
99 let value = ciborium::de::from_reader_with_recursion_limit(&mut slice, 16)?;
100 if slice.is_empty() {
101 Ok(value)
102 } else {
103 Err(CborError::ExtraneousData)
104 }
105 }
106
107 /// Trait for types that can be converted to/from a [`Value`]. This is essentially the same as the
108 /// `coset::AsCborValue` trait, but defining a copy locally allows us to implement it for primitive
109 /// types without falling foul of the orphan trait rules.
110 pub trait AsCborValue: Sized {
111 /// Convert a [`Value`] into an instance of the type.
from_cbor_value(value: Value) -> Result<Self, CborError>112 fn from_cbor_value(value: Value) -> Result<Self, CborError>;
113
114 /// Convert the object into a [`Value`], consuming it along the way.
to_cbor_value(self) -> Result<Value, CborError>115 fn to_cbor_value(self) -> Result<Value, CborError>;
116
117 /// Create an object instance from serialized CBOR data in a slice.
from_slice(slice: &[u8]) -> Result<Self, CborError>118 fn from_slice(slice: &[u8]) -> Result<Self, CborError> {
119 Self::from_cbor_value(read_to_value(slice)?)
120 }
121
122 /// Serialize this object to a vector, consuming it along the way.
into_vec(self) -> Result<Vec<u8>, CborError>123 fn into_vec(self) -> Result<Vec<u8>, CborError> {
124 let mut data = Vec::new();
125 ciborium::ser::into_writer(&self.to_cbor_value()?, &mut data)?;
126 Ok(data)
127 }
128 }
129
130 /// An `Option<T>` encodes as `( ? t )`, where `t` is whatever `T` encodes as in CDDL.
131 impl<T: AsCborValue> AsCborValue for Option<T> {
from_cbor_value(value: Value) -> Result<Self, CborError>132 fn from_cbor_value(value: Value) -> Result<Self, CborError> {
133 let mut arr = match value {
134 Value::Array(a) => a,
135 _ => return Err(CborError::UnexpectedItem("non-arr", "arr")),
136 };
137 match arr.len() {
138 0 => Ok(None),
139 1 => Ok(Some(<T>::from_cbor_value(arr.remove(0))?)),
140 _ => Err(CborError::UnexpectedItem("arr len >1", "arr len 0/1")),
141 }
142 }
to_cbor_value(self) -> Result<Value, CborError>143 fn to_cbor_value(self) -> Result<Value, CborError> {
144 match self {
145 Some(t) => Ok(Value::Array(vec![t.to_cbor_value()?])),
146 None => Ok(Value::Array(Vec::new())),
147 }
148 }
149 }
150
151 impl<T: AsCborValue, const N: usize> AsCborValue for [T; N] {
from_cbor_value(value: Value) -> Result<Self, CborError>152 fn from_cbor_value(value: Value) -> Result<Self, CborError> {
153 let arr = match value {
154 Value::Array(a) => a,
155 _ => return cbor_type_error(&value, "arr"),
156 };
157 let results: Result<Vec<_>, _> = arr.into_iter().map(<T>::from_cbor_value).collect();
158 let results: Vec<_> = results?;
159 results.try_into().map_err(|_e| CborError::UnexpectedItem("arr other len", "arr fixed len"))
160 }
to_cbor_value(self) -> Result<Value, CborError>161 fn to_cbor_value(self) -> Result<Value, CborError> {
162 let values: Result<Vec<_>, _> = self.into_iter().map(|v| v.to_cbor_value()).collect();
163 Ok(Value::Array(values?))
164 }
165 }
166
167 impl AsCborValue for Vec<u8> {
from_cbor_value(value: Value) -> Result<Self, CborError>168 fn from_cbor_value(value: Value) -> Result<Self, CborError> {
169 match value {
170 Value::Bytes(bstr) => Ok(bstr),
171 _ => cbor_type_error(&value, "bstr"),
172 }
173 }
to_cbor_value(self) -> Result<Value, CborError>174 fn to_cbor_value(self) -> Result<Value, CborError> {
175 Ok(Value::Bytes(self))
176 }
177 }
178
179 impl AsCborValue for i32 {
from_cbor_value(value: Value) -> Result<Self, CborError>180 fn from_cbor_value(value: Value) -> Result<Self, CborError> {
181 match value {
182 Value::Integer(i) => i.try_into().map_err(|_| CborError::OutOfRangeIntegerValue),
183 _ => crate::cbor_type_error(&value, "i32"),
184 }
185 }
to_cbor_value(self) -> Result<Value, CborError>186 fn to_cbor_value(self) -> Result<Value, CborError> {
187 Ok(Value::Integer(self.into()))
188 }
189 }
190