// Copyright 2021 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. // //////////////////////////////////////////////////////////////////////////////// //! Common internal utilities. use crate::{ cbor::value::{Integer, Value}, common::AsCborValue, CoseError, Result, }; use alloc::{boxed::Box, string::String, vec::Vec}; #[cfg(test)] mod tests; /// Return an error indicating that an unexpected CBOR type was encountered. pub(crate) fn cbor_type_error(value: &Value, want: &'static str) -> Result { let got = match value { Value::Integer(_) => "int", Value::Bytes(_) => "bstr", Value::Float(_) => "float", Value::Text(_) => "tstr", Value::Bool(_) => "bool", Value::Null => "nul", Value::Tag(_, _) => "tag", Value::Array(_) => "array", Value::Map(_) => "map", _ => "other", }; Err(CoseError::UnexpectedItem(got, want)) } /// Trait which augments the [`Value`] type with methods for convenient conversions to contained /// types which throw a [`CoseError`] if the Value is not of the expected type. pub(crate) trait ValueTryAs where Self: Sized, { /// Extractor for [`Value::Integer`] fn try_as_integer(self) -> Result; /// Extractor for [`Value::Bytes`] fn try_as_bytes(self) -> Result>; /// Extractor for [`Value::Bytes`] which also throws an error if the byte string is zero length fn try_as_nonempty_bytes(self) -> Result>; /// Extractor for [`Value::Array`] fn try_as_array(self) -> Result>; /// Extractor for [`Value::Array`] which applies `f` to each item to build a new [`Vec`] fn try_as_array_then_convert(self, f: F) -> Result> where F: Fn(Value) -> Result; /// Extractor for [`Value::Map`] fn try_as_map(self) -> Result>; /// Extractor for [`Value::Tag`] fn try_as_tag(self) -> Result<(u64, Box)>; /// Extractor for [`Value::Text`] fn try_as_string(self) -> Result; } impl ValueTryAs for Value { fn try_as_integer(self) -> Result { if let Value::Integer(i) = self { Ok(i) } else { cbor_type_error(&self, "int") } } fn try_as_bytes(self) -> Result> { if let Value::Bytes(b) = self { Ok(b) } else { cbor_type_error(&self, "bstr") } } fn try_as_nonempty_bytes(self) -> Result> { let v = self.try_as_bytes()?; if v.is_empty() { return Err(CoseError::UnexpectedItem("empty bstr", "non-empty bstr")); } Ok(v) } fn try_as_array(self) -> Result> { if let Value::Array(a) = self { Ok(a) } else { cbor_type_error(&self, "array") } } fn try_as_array_then_convert(self, f: F) -> Result> where F: Fn(Value) -> Result, { self.try_as_array()? .into_iter() .map(f) .collect::, _>>() } fn try_as_map(self) -> Result> { if let Value::Map(a) = self { Ok(a) } else { cbor_type_error(&self, "map") } } fn try_as_tag(self) -> Result<(u64, Box)> { if let Value::Tag(a, v) = self { Ok((a, v)) } else { cbor_type_error(&self, "tag") } } fn try_as_string(self) -> Result { if let Value::Text(s) = self { Ok(s) } else { cbor_type_error(&self, "tstr") } } } /// Convert each item of an iterator to CBOR, and wrap the lot in /// a [`Value::Array`] pub fn to_cbor_array(c: C) -> Result where C: IntoIterator, C::Item: AsCborValue, { Ok(Value::Array( c.into_iter() .map(|e| e.to_cbor_value()) .collect::, _>>()?, )) } /// Check for an expected error. #[cfg(test)] pub fn expect_err( result: Result, err_msg: &str, ) { #[cfg(not(feature = "std"))] use alloc::format; match result { Ok(_) => { assert!( result.is_err(), "expected error containing '{}', got success {:?}", err_msg, result ); } Err(err) => { assert!( format!("{:?}", err).contains(err_msg), "unexpected error {:?}, doesn't contain '{}' (Debug impl)", err, err_msg ); assert!( format!("{}", err).contains(err_msg), "unexpected error {:?}, doesn't contain '{}' (Display impl)", err, err_msg ); } } } // Macros to reduce boilerplate when creating `CoseSomethingBuilder` structures. /// Add `new()` and `build()` methods to the builder. macro_rules! builder { ( $otype: ty ) => { /// Constructor for builder. pub fn new() -> Self { Self(<$otype>::default()) } /// Build the completed object. pub fn build(self) -> $otype { self.0 } }; } /// Add a setter function for a field to the builder. macro_rules! builder_set { ( $name:ident: $ftype:ty ) => { /// Set the associated field. #[must_use] pub fn $name(mut self, $name: $ftype) -> Self { self.0.$name = $name; self } }; } /// Add a setter function for an optional field to the builder. macro_rules! builder_set_optional { ( $name:ident: $ftype:ty ) => { /// Set the associated field. #[must_use] pub fn $name(mut self, $name: $ftype) -> Self { self.0.$name = Some($name); self } }; } /// Add a setter function that fills out a `ProtectedHeader` from `Header` contents. macro_rules! builder_set_protected { ( $name:ident ) => { /// Set the associated field. #[must_use] pub fn $name(mut self, hdr: $crate::Header) -> Self { self.0.$name = $crate::ProtectedHeader { original_data: None, header: hdr, }; self } }; }