From fbe68b55cd1ea03a237c5d0e2f29cb8689368b4f Mon Sep 17 00:00:00 2001 From: David Drysdale Date: Thu, 10 Jun 2021 09:28:56 +0100 Subject: [PATCH] cbor: support tagged values --- libraries/cbor/src/macros.rs | 8 +++++ libraries/cbor/src/reader.rs | 59 +++++++++++++++++++++++++----------- libraries/cbor/src/values.rs | 15 +++++++-- libraries/cbor/src/writer.rs | 35 ++++++++++++++++++++- 4 files changed, 96 insertions(+), 21 deletions(-) diff --git a/libraries/cbor/src/macros.rs b/libraries/cbor/src/macros.rs index 18a1f61..d7e1185 100644 --- a/libraries/cbor/src/macros.rs +++ b/libraries/cbor/src/macros.rs @@ -336,6 +336,14 @@ macro_rules! cbor_bytes { }; } +/// Creates a CBOR Value of type Tag with the given tag and object. +#[macro_export] +macro_rules! cbor_tagged { + ( $t:expr, $x: expr ) => { + $crate::values::Value::Tag($t, ::alloc::boxed::Box::new($x)) + }; +} + /// Creates a CBOR Value of type Byte String with the given byte string literal. /// /// Example usage: diff --git a/libraries/cbor/src/reader.rs b/libraries/cbor/src/reader.rs index c7d4e14..d529490 100644 --- a/libraries/cbor/src/reader.rs +++ b/libraries/cbor/src/reader.rs @@ -15,7 +15,9 @@ //! Functionality for deserializing CBOR data into values. use super::values::{Constants, SimpleValue, Value}; -use crate::{cbor_array_vec, cbor_bytes_lit, cbor_map_collection, cbor_text, cbor_unsigned}; +use crate::{ + cbor_array_vec, cbor_bytes_lit, cbor_map_collection, cbor_tagged, cbor_text, cbor_unsigned, +}; use alloc::str; use alloc::vec::Vec; @@ -80,6 +82,7 @@ impl<'a> Reader<'a> { 3 => self.read_text_string_content(size_value), 4 => self.read_array_content(size_value, remaining_depth), 5 => self.read_map_content(size_value, remaining_depth), + 6 => self.read_tagged_content(size_value, remaining_depth), 7 => self.decode_to_simple_value(size_value, additional_info), _ => Err(DecoderError::UnsupportedMajorType), } @@ -187,6 +190,15 @@ impl<'a> Reader<'a> { Ok(cbor_map_collection!(value_map)) } + fn read_tagged_content( + &mut self, + tag_value: u64, + remaining_depth: i8, + ) -> Result { + let inner_value = self.decode_complete_data_item(remaining_depth - 1)?; + Ok(cbor_tagged!(tag_value, inner_value)) + } + fn decode_to_simple_value( &self, size_value: u64, @@ -546,6 +558,35 @@ mod test { assert_eq!(read(&test_cbor), Err(DecoderError::ExtranousData)); } + #[test] + fn test_read_tagged() { + let cases = vec![ + (cbor_tagged!(6, cbor_int!(0x42)), vec![0xc6, 0x18, 0x42]), + (cbor_tagged!(1, cbor_true!()), vec![0xc1, 0xf5]), + ( + cbor_tagged!( + 1000, + cbor_map! { + "a" => 1, + "b" => cbor_array![2, 3], + } + ), + vec![ + 0xd9, 0x03, 0xe8, 0xa2, // map of 2 pairs + 0x61, 0x61, // "a" + 0x01, 0x61, 0x62, // "b" + 0x82, // array with 2 elements + 0x02, 0x03, + ], + ), + ]; + for (value, mut cbor) in cases { + assert_eq!(read(&cbor), Ok(value)); + cbor.push(0x01); + assert_eq!(read(&cbor), Err(DecoderError::ExtranousData)); + } + } + #[test] fn test_read_integer_out_of_range() { let cases = vec![ @@ -779,20 +820,4 @@ mod test { assert_eq!(read(&cbor), Err(DecoderError::IncompleteCborData)); } } - - #[test] - fn test_read_unsupported_major_type() { - let cases = vec![ - vec![0xC0], - vec![0xD8, 0xFF], - // multi-dimensional array example using tags - vec![ - 0x82, 0x82, 0x02, 0x03, 0xd8, 0x41, 0x4a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, - 0x03, 0x00, 0x04, 0x00, 0x05, - ], - ]; - for cbor in cases { - assert_eq!(read(&cbor), Err(DecoderError::UnsupportedMajorType)); - } - } } diff --git a/libraries/cbor/src/values.rs b/libraries/cbor/src/values.rs index 72fab95..13b378a 100644 --- a/libraries/cbor/src/values.rs +++ b/libraries/cbor/src/values.rs @@ -15,6 +15,7 @@ //! Types for expressing CBOR values. use super::writer::write; +use alloc::boxed::Box; use alloc::string::{String, ToString}; use alloc::vec::Vec; use core::cmp::Ordering; @@ -34,7 +35,8 @@ pub enum Value { Array(Vec), /// Map of key-value pairs. Map(Vec<(Value, Value)>), - // TAG is omitted + /// Tagged value. + Tag(u64, Box), /// Simple value. Simple(SimpleValue), } @@ -100,6 +102,7 @@ impl Value { Value::TextString(_) => 3, Value::Array(_) => 4, Value::Map(_) => 5, + Value::Tag(_, _) => 6, Value::Simple(_) => 7, } } @@ -108,7 +111,7 @@ impl Value { impl Ord for Value { fn cmp(&self, other: &Value) -> Ordering { use super::values::Value::{ - Array, ByteString, Map, Negative, Simple, TextString, Unsigned, + Array, ByteString, Map, Negative, Simple, Tag, TextString, Unsigned, }; let self_type_value = self.type_label(); let other_type_value = other.type_label(); @@ -122,6 +125,7 @@ impl Ord for Value { (TextString(t1), TextString(t2)) => t1.len().cmp(&t2.len()).then(t1.cmp(t2)), (Array(a1), Array(a2)) if a1.len() != a2.len() => a1.len().cmp(&a2.len()), (Map(m1), Map(m2)) if m1.len() != m2.len() => m1.len().cmp(&m2.len()), + (Tag(t1, v1), Tag(t2, v2)) => t1.cmp(t2).then(v1.cmp(v2)), (Simple(s1), Simple(s2)) => s1.cmp(s2), (v1, v2) => { // This case could handle all of the above as well. Checking individually is faster. @@ -258,7 +262,7 @@ where #[cfg(test)] mod test { use super::*; - use crate::{cbor_array, cbor_bool, cbor_bytes, cbor_int, cbor_map, cbor_text}; + use crate::{cbor_array, cbor_bool, cbor_bytes, cbor_int, cbor_map, cbor_tagged, cbor_text}; use alloc::vec; #[test] @@ -315,12 +319,17 @@ mod test { assert!(cbor_int!(-1) < cbor_text!("s")); assert!(cbor_int!(-1) < cbor_array![]); assert!(cbor_int!(-1) < cbor_map! {}); + assert!(cbor_int!(-1) < cbor_tagged!(1, cbor_text!("s"))); assert!(cbor_int!(-1) < cbor_bool!(false)); assert!(cbor_bytes!(vec![0x00]) < cbor_array![]); assert!(cbor_bytes!(vec![0x00]) < cbor_map! {}); assert!(cbor_bytes!(vec![0x00]) < cbor_bool!(false)); + assert!(cbor_bytes!(vec![0x00]) < cbor_tagged!(1, cbor_text!("s"))); assert!(cbor_text!("s") < cbor_map! {}); assert!(cbor_text!("s") < cbor_bool!(false)); + assert!(cbor_text!("s") < cbor_tagged!(1, cbor_text!("s"))); assert!(cbor_array![] < cbor_bool!(false)); + assert!(cbor_tagged!(1, cbor_text!("s")) < cbor_tagged!(2, cbor_int!(0))); + assert!(cbor_tagged!(1, cbor_text!("s")) < cbor_bool!(false)); } } diff --git a/libraries/cbor/src/writer.rs b/libraries/cbor/src/writer.rs index 9383a2b..4770cf7 100644 --- a/libraries/cbor/src/writer.rs +++ b/libraries/cbor/src/writer.rs @@ -76,6 +76,12 @@ impl<'a> Writer<'a> { } } } + Value::Tag(tag, inner_value) => { + self.start_item(type_label, tag); + if !self.encode_cbor(*inner_value, remaining_depth - 1) { + return false; + } + } Value::Simple(simple_value) => self.start_item(type_label, simple_value as u64), } true @@ -103,7 +109,7 @@ mod test { use super::*; use crate::{ cbor_array, cbor_array_vec, cbor_bytes, cbor_false, cbor_int, cbor_map, cbor_null, - cbor_text, cbor_true, cbor_undefined, + cbor_tagged, cbor_text, cbor_true, cbor_undefined, }; use alloc::vec; @@ -396,6 +402,33 @@ mod test { assert_eq!(write_return(value_map), Some(expected_cbor)); } + #[test] + fn test_write_tagged() { + let cases = vec![ + (cbor_tagged!(6, cbor_int!(0x42)), vec![0xc6, 0x18, 0x42]), + (cbor_tagged!(1, cbor_true!()), vec![0xc1, 0xf5]), + ( + cbor_tagged!( + 1000, + cbor_map! { + "a" => 1, + "b" => cbor_array![2, 3], + } + ), + vec![ + 0xd9, 0x03, 0xe8, 0xa2, // map of 2 pairs + 0x61, 0x61, // "a" + 0x01, 0x61, 0x62, // "b" + 0x82, // array with 2 elements + 0x02, 0x03, + ], + ), + ]; + for (value, correct_cbor) in cases { + assert_eq!(write_return(value), Some(correct_cbor)); + } + } + #[test] fn test_write_simple() { let cases = vec![