CBOR maps use Vec instead of BTreeMap (#303)
* CBOR uses Vec for map internally * remove BTreeMap from get_info * rename cbor_map_btree and clean up cbor_array_vec * destructure now takes Vec, not BTreeMap * adds dedup in CBOR writer * fail to write CBOR maps with duplicates * CBOR interface refinements * macro documentation for CBOR map and array
This commit is contained in:
@@ -13,14 +13,14 @@
|
||||
// limitations under the License.
|
||||
|
||||
use crate::values::{KeyType, Value};
|
||||
use alloc::collections::btree_map;
|
||||
use alloc::vec;
|
||||
use core::cmp::Ordering;
|
||||
use core::iter::Peekable;
|
||||
|
||||
/// This macro generates code to extract multiple values from a `BTreeMap<KeyType, Value>` at once
|
||||
/// in an optimized manner, consuming the input map.
|
||||
/// This macro generates code to extract multiple values from a `Vec<(KeyType, Value)>` at once
|
||||
/// in an optimized manner, consuming the input vector.
|
||||
///
|
||||
/// It takes as input a `BTreeMap` as well as a list of identifiers and keys, and generates code
|
||||
/// It takes as input a `Vec` as well as a list of identifiers and keys, and generates code
|
||||
/// that assigns the corresponding values to new variables using the given identifiers. Each of
|
||||
/// these variables has type `Option<Value>`, to account for the case where keys aren't found.
|
||||
///
|
||||
@@ -32,16 +32,14 @@ use core::iter::Peekable;
|
||||
/// the keys are indeed sorted. This macro is therefore **not suitable for dynamic keys** that can
|
||||
/// change at runtime.
|
||||
///
|
||||
/// Semantically, provided that the keys are sorted as specified above, the following two snippets
|
||||
/// of code are equivalent, but the `destructure_cbor_map!` version is more optimized, as it doesn't
|
||||
/// re-balance the `BTreeMap` for each key, contrary to the `BTreeMap::remove` operations.
|
||||
/// Example usage:
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate alloc;
|
||||
/// # use cbor::destructure_cbor_map;
|
||||
/// #
|
||||
/// # fn main() {
|
||||
/// # let map = alloc::collections::BTreeMap::new();
|
||||
/// # let map = alloc::vec::Vec::new();
|
||||
/// destructure_cbor_map! {
|
||||
/// let {
|
||||
/// 1 => x,
|
||||
@@ -50,17 +48,6 @@ use core::iter::Peekable;
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate alloc;
|
||||
/// #
|
||||
/// # fn main() {
|
||||
/// # let mut map = alloc::collections::BTreeMap::<cbor::KeyType, _>::new();
|
||||
/// use cbor::values::IntoCborKey;
|
||||
/// let x: Option<cbor::Value> = map.remove(&1.into_cbor_key());
|
||||
/// let y: Option<cbor::Value> = map.remove(&"key".into_cbor_key());
|
||||
/// # }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! destructure_cbor_map {
|
||||
( let { $( $key:expr => $variable:ident, )+ } = $map:expr; ) => {
|
||||
@@ -100,7 +87,7 @@ macro_rules! destructure_cbor_map {
|
||||
/// would be inlined for every use case. As of June 2020, this saves ~40KB of binary size for the
|
||||
/// CTAP2 application of OpenSK.
|
||||
pub fn destructure_cbor_map_peek_value(
|
||||
it: &mut Peekable<btree_map::IntoIter<KeyType, Value>>,
|
||||
it: &mut Peekable<vec::IntoIter<(KeyType, Value)>>,
|
||||
needle: KeyType,
|
||||
) -> Option<Value> {
|
||||
loop {
|
||||
@@ -145,6 +132,23 @@ macro_rules! assert_sorted_keys {
|
||||
};
|
||||
}
|
||||
|
||||
/// Creates a CBOR Value of type Map with the specified key-value pairs.
|
||||
///
|
||||
/// Keys and values are expressions and converted into CBOR Keys and Values.
|
||||
/// The syntax for these pairs is `key_expression => value_expression,`.
|
||||
/// Duplicate keys will lead to invalid CBOR, i.e. writing these values fails.
|
||||
/// Keys do not have to be sorted.
|
||||
///
|
||||
/// Example usage:
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate alloc;
|
||||
/// # use cbor::cbor_map;
|
||||
/// let map = cbor_map! {
|
||||
/// 0x01 => false,
|
||||
/// "02" => -3,
|
||||
/// };
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! cbor_map {
|
||||
// trailing comma case
|
||||
@@ -157,15 +161,35 @@ macro_rules! cbor_map {
|
||||
// The import is unused if the list is empty.
|
||||
#[allow(unused_imports)]
|
||||
use $crate::values::{IntoCborKey, IntoCborValue};
|
||||
let mut _map = ::alloc::collections::BTreeMap::new();
|
||||
let mut _map = ::alloc::vec::Vec::new();
|
||||
$(
|
||||
_map.insert($key.into_cbor_key(), $value.into_cbor_value());
|
||||
_map.push(($key.into_cbor_key(), $value.into_cbor_value()));
|
||||
)*
|
||||
$crate::values::Value::Map(_map)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Creates a CBOR Value of type Map with key-value pairs where values can be Options.
|
||||
///
|
||||
/// Keys and values are expressions and converted into CBOR Keys and Value Options.
|
||||
/// The map entry is included iff the Value is not an Option or Option is Some.
|
||||
/// The syntax for these pairs is `key_expression => value_expression,`.
|
||||
/// Duplicate keys will lead to invalid CBOR, i.e. writing these values fails.
|
||||
/// Keys do not have to be sorted.
|
||||
///
|
||||
/// Example usage:
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate alloc;
|
||||
/// # use cbor::cbor_map_options;
|
||||
/// let missing_value: Option<bool> = None;
|
||||
/// let map = cbor_map_options! {
|
||||
/// 0x01 => Some(false),
|
||||
/// "02" => -3,
|
||||
/// "not in map" => missing_value,
|
||||
/// };
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! cbor_map_options {
|
||||
// trailing comma case
|
||||
@@ -178,12 +202,12 @@ macro_rules! cbor_map_options {
|
||||
// The import is unused if the list is empty.
|
||||
#[allow(unused_imports)]
|
||||
use $crate::values::{IntoCborKey, IntoCborValueOption};
|
||||
let mut _map = ::alloc::collections::BTreeMap::<_, $crate::values::Value>::new();
|
||||
let mut _map = ::alloc::vec::Vec::<(_, $crate::values::Value)>::new();
|
||||
$(
|
||||
{
|
||||
let opt: Option<$crate::values::Value> = $value.into_cbor_value_option();
|
||||
if let Some(val) = opt {
|
||||
_map.insert($key.into_cbor_key(), val);
|
||||
_map.push(($key.into_cbor_key(), val));
|
||||
}
|
||||
}
|
||||
)*
|
||||
@@ -192,13 +216,25 @@ macro_rules! cbor_map_options {
|
||||
};
|
||||
}
|
||||
|
||||
/// Creates a CBOR Value of type Map from a Vec<(KeyType, Value)>.
|
||||
#[macro_export]
|
||||
macro_rules! cbor_map_btree {
|
||||
( $tree:expr ) => {
|
||||
$crate::values::Value::Map($tree)
|
||||
};
|
||||
macro_rules! cbor_map_collection {
|
||||
( $tree:expr ) => {{
|
||||
$crate::values::Value::from($tree)
|
||||
}};
|
||||
}
|
||||
|
||||
/// Creates a CBOR Value of type Array with the given elements.
|
||||
///
|
||||
/// Elements are expressions and converted into CBOR Values. Elements are comma-separated.
|
||||
///
|
||||
/// Example usage:
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate alloc;
|
||||
/// # use cbor::cbor_array;
|
||||
/// let array = cbor_array![1, "2"];
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! cbor_array {
|
||||
// trailing comma case
|
||||
@@ -216,6 +252,7 @@ macro_rules! cbor_array {
|
||||
};
|
||||
}
|
||||
|
||||
/// Creates a CBOR Value of type Array from a Vec<Value>.
|
||||
#[macro_export]
|
||||
macro_rules! cbor_array_vec {
|
||||
( $vec:expr ) => {{
|
||||
@@ -329,7 +366,6 @@ macro_rules! cbor_key_bytes {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::values::{KeyType, SimpleValue, Value};
|
||||
use alloc::collections::BTreeMap;
|
||||
|
||||
#[test]
|
||||
fn test_cbor_simple_values() {
|
||||
@@ -421,7 +457,7 @@ mod test {
|
||||
Value::KeyValue(KeyType::Unsigned(0)),
|
||||
Value::KeyValue(KeyType::Unsigned(1)),
|
||||
]),
|
||||
Value::Map(BTreeMap::new()),
|
||||
Value::Map(Vec::new()),
|
||||
Value::Map(
|
||||
[(KeyType::Unsigned(2), Value::KeyValue(KeyType::Unsigned(3)))]
|
||||
.iter()
|
||||
@@ -518,7 +554,7 @@ mod test {
|
||||
Value::KeyValue(KeyType::Unsigned(1)),
|
||||
]),
|
||||
),
|
||||
(KeyType::Unsigned(9), Value::Map(BTreeMap::new())),
|
||||
(KeyType::Unsigned(9), Value::Map(Vec::new())),
|
||||
(
|
||||
KeyType::Unsigned(10),
|
||||
Value::Map(
|
||||
@@ -589,7 +625,7 @@ mod test {
|
||||
Value::KeyValue(KeyType::Unsigned(1)),
|
||||
]),
|
||||
),
|
||||
(KeyType::Unsigned(9), Value::Map(BTreeMap::new())),
|
||||
(KeyType::Unsigned(9), Value::Map(Vec::new())),
|
||||
(
|
||||
KeyType::Unsigned(10),
|
||||
Value::Map(
|
||||
@@ -608,30 +644,26 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cbor_map_btree_empty() {
|
||||
let a = cbor_map_btree!(BTreeMap::new());
|
||||
let b = Value::Map(BTreeMap::new());
|
||||
fn test_cbor_map_collection_empty() {
|
||||
let a = cbor_map_collection!(Vec::<(_, _)>::new());
|
||||
let b = Value::Map(Vec::new());
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cbor_map_btree_foo() {
|
||||
let a = cbor_map_btree!(
|
||||
[(KeyType::Unsigned(2), Value::KeyValue(KeyType::Unsigned(3)))]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect()
|
||||
);
|
||||
let b = Value::Map(
|
||||
[(KeyType::Unsigned(2), Value::KeyValue(KeyType::Unsigned(3)))]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect(),
|
||||
);
|
||||
fn test_cbor_map_collection_foo() {
|
||||
let a = cbor_map_collection!(vec![(
|
||||
KeyType::Unsigned(2),
|
||||
Value::KeyValue(KeyType::Unsigned(3))
|
||||
)]);
|
||||
let b = Value::Map(vec![(
|
||||
KeyType::Unsigned(2),
|
||||
Value::KeyValue(KeyType::Unsigned(3)),
|
||||
)]);
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
|
||||
fn extract_map(cbor_value: Value) -> BTreeMap<KeyType, Value> {
|
||||
fn extract_map(cbor_value: Value) -> Vec<(KeyType, Value)> {
|
||||
match cbor_value {
|
||||
Value::Map(map) => map,
|
||||
_ => panic!("Expected CBOR map."),
|
||||
|
||||
@@ -13,8 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
use super::values::{Constants, KeyType, SimpleValue, Value};
|
||||
use crate::{cbor_array_vec, cbor_bytes_lit, cbor_map_btree, cbor_text, cbor_unsigned};
|
||||
use alloc::collections::BTreeMap;
|
||||
use crate::{cbor_array_vec, cbor_bytes_lit, cbor_map_collection, cbor_text, cbor_unsigned};
|
||||
use alloc::str;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
@@ -174,7 +173,7 @@ impl<'a> Reader<'a> {
|
||||
size_value: u64,
|
||||
remaining_depth: i8,
|
||||
) -> Result<Value, DecoderError> {
|
||||
let mut value_map = BTreeMap::new();
|
||||
let mut value_map = Vec::new();
|
||||
let mut last_key_option = None;
|
||||
for _ in 0..size_value {
|
||||
let key_value = self.decode_complete_data_item(remaining_depth - 1)?;
|
||||
@@ -185,12 +184,12 @@ impl<'a> Reader<'a> {
|
||||
}
|
||||
}
|
||||
last_key_option = Some(key.clone());
|
||||
value_map.insert(key, self.decode_complete_data_item(remaining_depth - 1)?);
|
||||
value_map.push((key, self.decode_complete_data_item(remaining_depth - 1)?));
|
||||
} else {
|
||||
return Err(DecoderError::IncorrectMapKeyType);
|
||||
}
|
||||
}
|
||||
Ok(cbor_map_btree!(value_map))
|
||||
Ok(cbor_map_collection!(value_map))
|
||||
}
|
||||
|
||||
fn decode_to_simple_value(
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::string::{String, ToString};
|
||||
use alloc::vec::Vec;
|
||||
use core::cmp::Ordering;
|
||||
@@ -21,7 +20,7 @@ use core::cmp::Ordering;
|
||||
pub enum Value {
|
||||
KeyValue(KeyType),
|
||||
Array(Vec<Value>),
|
||||
Map(BTreeMap<KeyType, Value>),
|
||||
Map(Vec<(KeyType, Value)>),
|
||||
// TAG is omitted
|
||||
Simple(SimpleValue),
|
||||
}
|
||||
@@ -183,6 +182,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<(KeyType, Value)>> for Value {
|
||||
fn from(map: Vec<(KeyType, Value)>) -> Self {
|
||||
Value::Map(map)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for Value {
|
||||
fn from(b: bool) -> Self {
|
||||
Value::bool_value(b)
|
||||
|
||||
@@ -56,8 +56,14 @@ impl<'a> Writer<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Map(map) => {
|
||||
self.start_item(5, map.len() as u64);
|
||||
Value::Map(mut map) => {
|
||||
map.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
let map_len = map.len();
|
||||
map.dedup_by(|a, b| a.0.eq(&b.0));
|
||||
if map_len != map.len() {
|
||||
return false;
|
||||
}
|
||||
self.start_item(5, map_len as u64);
|
||||
for (k, v) in map {
|
||||
if !self.encode_cbor(Value::KeyValue(k), remaining_depth - 1) {
|
||||
return false;
|
||||
@@ -209,9 +215,16 @@ mod test {
|
||||
#[test]
|
||||
fn test_write_map() {
|
||||
let value_map = cbor_map! {
|
||||
"aa" => "AA",
|
||||
"e" => "E",
|
||||
"" => ".",
|
||||
0 => "a",
|
||||
23 => "b",
|
||||
24 => "c",
|
||||
std::u8::MAX as i64 => "d",
|
||||
256 => "e",
|
||||
std::u16::MAX as i64 => "f",
|
||||
65536 => "g",
|
||||
std::u32::MAX as i64 => "h",
|
||||
4294967296_i64 => "i",
|
||||
std::i64::MAX => "j",
|
||||
-1 => "k",
|
||||
-24 => "l",
|
||||
-25 => "m",
|
||||
@@ -224,16 +237,9 @@ mod test {
|
||||
b"a" => 2,
|
||||
b"bar" => 3,
|
||||
b"foo" => 4,
|
||||
0 => "a",
|
||||
23 => "b",
|
||||
24 => "c",
|
||||
std::u8::MAX as i64 => "d",
|
||||
256 => "e",
|
||||
std::u16::MAX as i64 => "f",
|
||||
65536 => "g",
|
||||
std::u32::MAX as i64 => "h",
|
||||
4294967296_i64 => "i",
|
||||
std::i64::MAX => "j",
|
||||
"" => ".",
|
||||
"e" => "E",
|
||||
"aa" => "AA",
|
||||
};
|
||||
let expected_cbor = vec![
|
||||
0xb8, 0x19, // map of 25 pairs:
|
||||
@@ -288,6 +294,67 @@ mod test {
|
||||
assert_eq!(write_return(value_map), Some(expected_cbor));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_map_sorted() {
|
||||
let sorted_map = cbor_map! {
|
||||
0 => "a",
|
||||
1 => "b",
|
||||
-1 => "c",
|
||||
-2 => "d",
|
||||
b"a" => "e",
|
||||
b"b" => "f",
|
||||
"" => "g",
|
||||
"c" => "h",
|
||||
};
|
||||
let unsorted_map = cbor_map! {
|
||||
1 => "b",
|
||||
-2 => "d",
|
||||
b"b" => "f",
|
||||
"c" => "h",
|
||||
"" => "g",
|
||||
b"a" => "e",
|
||||
-1 => "c",
|
||||
0 => "a",
|
||||
};
|
||||
assert_eq!(write_return(sorted_map), write_return(unsorted_map));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_map_duplicates() {
|
||||
let duplicate0 = cbor_map! {
|
||||
0 => "a",
|
||||
-1 => "c",
|
||||
b"a" => "e",
|
||||
"c" => "g",
|
||||
0 => "b",
|
||||
};
|
||||
assert_eq!(write_return(duplicate0), None);
|
||||
let duplicate1 = cbor_map! {
|
||||
0 => "a",
|
||||
-1 => "c",
|
||||
b"a" => "e",
|
||||
"c" => "g",
|
||||
-1 => "d",
|
||||
};
|
||||
assert_eq!(write_return(duplicate1), None);
|
||||
let duplicate2 = cbor_map! {
|
||||
0 => "a",
|
||||
-1 => "c",
|
||||
b"a" => "e",
|
||||
"c" => "g",
|
||||
b"a" => "f",
|
||||
};
|
||||
assert_eq!(write_return(duplicate2), None);
|
||||
let duplicate3 = cbor_map! {
|
||||
0 => "a",
|
||||
-1 => "c",
|
||||
b"a" => "e",
|
||||
"c" => "g",
|
||||
"c" => "h",
|
||||
};
|
||||
assert_eq!(write_return(duplicate3), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_map_with_array() {
|
||||
let value_map = cbor_map! {
|
||||
|
||||
Reference in New Issue
Block a user