// Copyright 2024 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
//
// https://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` implements the subset of CBOR used in [CTAP2][1].
//!
//! Parsing is injective, meaning that no two distinct byte strings (that parse
//! successfully) will result in the same value. Reserialising the result of
//! parse will always result in exactly the same byte string. (This is checked
//! by fuzzing.)
//!
//! In the other direction, serialisation is also injective. However, an
//! arbitary limit is placed on the maximum depth of parsed structures to avoid
//! degenerate inputs from consuming too much stack. Thus structures that are
//! deeper than this will serialise but that result cannot be parsed back to
//! the same value. Aside from this exception, all values should round-trip
//! correctly. This is checked with `proptest`.
//!
//! ```
//! let value = cbor::Value::String("hello".to_string());
//! let serialized = value.to_bytes();
//! assert_eq!(serialized, vec![0x65u8, 0x68, 0x65, 0x6c, 0x6c, 0x6f]);
//! assert_eq!(cbor::parse(serialized), Ok(value));
//! ```
//!
//! [1]: https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#ctap2-canonical-cbor-encoding-form
#![no_std]
#![forbid(unsafe_code)]
extern crate alloc;
extern crate bytes;
use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
use bytes::{Buf, Bytes};
use core::borrow::Borrow;
use core::cmp::Ordering;
use core::fmt;
use core::ops::Deref;
// This code assumes that `usize` fits in a `u64` because it uses `as u64` in a
// couple of places.
const _: () =
assert!(core::mem::size_of::<usize>() <= core::mem::size_of::<u64>(), "usize too large");
/// MAX_DEPTH is the maximum "depth" of a structure that will be parsed.
/// Each array or map increases the depth by one.
const MAX_DEPTH: usize = 16;
/// Error enumerates the different errors that can occur during parsing.
///
/// It is intended for debugging only. Where `usize` values are present, they
/// contain the approximate number of bytes remaining when the error occurred.
#[derive(Debug, PartialEq)]
pub enum Error {
DepthLimitExceeded(usize, usize),
InputTruncated,
InvalidUTF8(usize),
MapKeysOutOfOrder(usize, Value),
NegativeOutOfRange(u64),
NonMinimalAdditionalData(usize),
TrailingData(usize),
UnsignedOutOfRange(u64),
UnsupportedAdditionalInformation(usize, u8),
UnsupportedMajorType(usize, u8),
UnsupportedMapKeyType(usize, Value),
UnsupportedSimpleValue(u64),
}
fn get_u8(bytes: &mut Bytes) -> Result<u8, Error> {
if bytes.is_empty() {
return Err(Error::InputTruncated);
}
Ok(bytes.get_u8())
}
fn get(bytes: &mut Bytes, num_bytes: usize) -> Result<Bytes, Error> {
if bytes.len() < num_bytes {
return Err(Error::InputTruncated);
}
Ok(bytes.split_to(num_bytes))
}
/// Value represents a CBOR structure.
///
/// Integers are mapped to `i64` despite CBOR having 65-bit integers. CBOR
/// integers outside the range of an `i64` result in an error during parsing.
/// Byte strings are returned as `Bytes`s in order to avoid copies.
#[derive(PartialEq, Clone)]
pub enum Value {
Int(i64),
Bytestring(Bytes),
String(String),
Array(Vec<Value>),
Map(BTreeMap<MapKey, Value>),
Boolean(bool),
}
/// Implements pretty-printing of `Value`s and `MapKey`s to better support the
/// `Debug` trait for those types.
mod debug {
use super::{MapKey, Value};
use alloc::string::String;
use core::fmt;
const HEX_CHARS: [char; 16] =
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
fn hex_encode(bytes: &[u8]) -> String {
let mut ret = String::with_capacity(bytes.len() * 2);
for &byte in bytes {
ret.push(HEX_CHARS[(byte >> 4) as usize]);
ret.push(HEX_CHARS[(byte & 15) as usize]);
}
ret
}
/// Write a debugging representation of `key` to `f`.
pub fn map_key(f: &mut fmt::Formatter, key: &MapKey) -> fmt::Result {
match key {
MapKey::Int(i) => write!(f, "{}", i),
MapKey::Bytestring(bytes) => {
f.write_str("h\"")?;
f.write_str(&hex_encode(bytes))?;
f.write_str("\"")
}
MapKey::String(s) => {
f.write_str("\"")?;
f.write_str(s)?;
f.write_str("\"")
}
}
}
/// Write a debugging representation of `value` to `f`.
pub fn value(f: &mut fmt::Formatter, value: &Value) -> fmt::Result {
match value {
Value::Int(i) => write!(f, "{}", i),
Value::Bytestring(bytes) => {
f.write_str("h\"")?;
f.write_str(&hex_encode(bytes))?;
f.write_str("\"")
}
Value::String(s) => {
f.write_str("\"")?;
f.write_str(s)?;
f.write_str("\"")
}
Value::Boolean(b) => f.write_str(if *b { "true" } else { "false" }),
Value::Array(array) => f.debug_list().entries(array.iter()).finish(),
Value::Map(map) => f.debug_map().entries(map.iter()).finish(),
}
}
}
impl fmt::Debug for MapKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
debug::map_key(f, self)
}
}
impl fmt::Debug for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
debug::value(f, self)
}
}
// low_bits_and_length returns the bottom five bits of the initial byte of a
// CBOR value, and the number of bytes that will need to follow it in order to
// encode an argument `arg`.
fn low_bits_and_length(arg: u64) -> (u8, usize) {
if arg < 24 {
(arg as u8, 0)
} else if arg < 0x100 {
(24, 1)
} else if arg < 0x10000 {
(25, 2)
} else if arg < 0x100000000 {
(26, 4)
} else {
(27, 8)
}
}
/// write_header appends the [initial byte and "argument"][1] of a CBOR value.
///
/// [1]: https://datatracker.ietf.org/doc/html/rfc8949#name-specification-of-the-cbor-e
fn write_header(out: &mut Vec<u8>, major_type: u8, arg: u64) {
let (low_bits, num_bytes) = low_bits_and_length(arg);
out.push(major_type << 5 | low_bits);
let value_bytes = arg.to_be_bytes();
out.extend_from_slice(&value_bytes[8 - num_bytes..]);
}
impl Value {
// to_bytes serialises `self` to CBOR and returns the result.
pub fn to_bytes(&self) -> Vec<u8> {
let mut ret = Vec::new();
self.append_bytes(&mut ret);
ret
}
// append_bytes appends a serialisation of `self` to `out`.
pub fn append_bytes(&self, out: &mut Vec<u8>) {
match self {
Value::Int(v) if v >= &0 => write_header(out, 0, *v as u64),
Value::Int(v) => write_header(out, 1, !v as u64),
Value::Bytestring(s) => {
write_header(out, 2, s.len() as u64);
out.extend_from_slice(s.deref());
}
Value::String(s) => {
write_header(out, 3, s.len() as u64);
out.extend_from_slice(s.as_bytes());
}
Value::Array(a) => {
write_header(out, 4, a.len() as u64);
for elem in a {
elem.append_bytes(out);
}
}
Value::Map(m) => {
write_header(out, 5, m.len() as u64);
for (key, value) in m {
key.append_bytes(out);
value.append_bytes(out);
}
}
Value::Boolean(b) => write_header(out, 7, 20u64 + *b as u64),
}
}
}
impl From<&str> for Value {
fn from(s: &str) -> Self {
Value::String(String::from(s))
}
}
impl From<i64> for Value {
fn from(i: i64) -> Self {
Value::Int(i)
}
}
impl From<bool> for Value {
fn from(b: bool) -> Self {
Value::Boolean(b)
}
}
impl From<&[u8]> for Value {
fn from(bytes: &[u8]) -> Self {
Value::Bytestring(Bytes::from(bytes.to_vec()))
}
}
impl<const N: usize> From<&[u8; N]> for Value {
fn from(bytes: &[u8; N]) -> Self {
Value::Bytestring(Bytes::from(bytes.to_vec()))
}
}
impl From<Vec<u8>> for Value {
fn from(bytes: Vec<u8>) -> Self {
Value::Bytestring(Bytes::from(bytes))
}
}
impl From<Vec<Value>> for Value {
fn from(values: Vec<Value>) -> Self {
Value::Array(values)
}
}
impl From<BTreeMap<MapKey, Value>> for Value {
fn from(map: BTreeMap<MapKey, Value>) -> Self {
Value::Map(map)
}
}
/// The `cbor!` macro allows `Value`s to be constructed with less syntax noise.
/// For example:
///
/// ```ignore
/// cbor!({
/// "key": "value",
/// "key2": [1, 2, 3],
/// "key3": {
/// "nested": true,
/// },
/// 1: 2,
/// 3: true,
/// });
/// ```
///
/// Because of the way that the macro is implemented, when using values
/// (including variable names) that consist of more than one token they will
/// need to be wrapped in parentheses.
#[macro_export]
macro_rules! cbor {
([ $( $elem:tt ),* ]) => {
$crate::Value::Array(vec![ $( $crate::cbor!($elem) ),* ])
};
([ $( $elem:tt ),* ,]) => {
$crate::cbor!([ $( $elem ),* ])
};
({ $( $key:tt : $value:tt ),* }) => {{
let mut map = alloc::collections::BTreeMap::new();
$( map.insert($crate::MapKey::from($key), $crate::cbor!($value)); )*
$crate::Value::Map(map)
}};
({ $( $key:tt : $value:tt ),* ,}) => {
$crate::cbor!({ $( $key : $value ),* })
};
( $value:tt ) => { $crate::Value::from($value) };
}
/// A MapKey is the type of values that can key a CBOR map.
#[derive(PartialEq, Eq, Clone)]
pub enum MapKey {
// A separate `MapKey` type is used because we want to exclude things like
// maps keyed by arrays or other maps. Such structures never appear in
// CTAP and so we don't need to support them.
//
// We expect that a map will always have keys of the same type, which
// suggests that `Value::Map` could be split into `Value::IntKeyedMap` etc.
// However, that falls down when a map is empty because the parser can't
// know what the key type should be, yet calling code will want to expect
// the right type of map. Thus we end up supporting heterogeneous maps.
Int(i64),
Bytestring(Vec<u8>),
String(String),
}
impl MapKey {
fn as_ref(&self) -> MapKeyRef {
match self {
MapKey::Int(v) => MapKeyRef::Int(*v),
MapKey::Bytestring(b) => MapKeyRef::Slice(b),
MapKey::String(s) => MapKeyRef::Str(s),
}
}
fn append_bytes(&self, out: &mut Vec<u8>) {
self.as_ref().append_bytes(out)
}
}
impl PartialOrd for MapKey {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for MapKey {
fn cmp(&self, other: &Self) -> Ordering {
// It's essential that the ordering of `MapKey` and `MapKeyRef` be
// congrugent. To ensure this, the order of `MapKey` is delegated to
// `MapKeyRef`.
self.as_ref().cmp(&other.as_ref())
}
}
impl From<&str> for MapKey {
fn from(s: &str) -> Self {
MapKey::String(String::from(s))
}
}
impl From<i64> for MapKey {
fn from(i: i64) -> Self {
MapKey::Int(i)
}
}
impl From<&[u8]> for MapKey {
fn from(bytes: &[u8]) -> Self {
MapKey::Bytestring(bytes.to_vec())
}
}
impl<const N: usize> From<&[u8; N]> for MapKey {
fn from(bytes: &[u8; N]) -> Self {
MapKey::Bytestring(bytes.to_vec())
}
}
impl From<Vec<u8>> for MapKey {
fn from(bytes: Vec<u8>) -> Self {
MapKey::Bytestring(bytes)
}
}
/// MapKeyRef mirrors MapKey and allows lookups without allocation.
///
/// The `BTreeMap` in a [`Value::Map`] would normally require a [`MapKey`] as a
/// lookup key, but that may require allocating Vecs or Strings to construct.
/// Alternatively, `BTreeKey` allows lookups with any type that can be borrowed
/// from the keys, but there is not any such type for MapKey by default.
///
/// Thus MapKeyRef mirrors [`MapKey`], but uses borrowed types. Both it and
/// [`MapKey`] implement [`MapLookupKey`], and a [`MapLookupKey`] can be
/// borrowed from a [`MapKey`]. Thus, to lookup a value without allocating, do
/// something like:
///
/// ```
/// let map = std::collections::BTreeMap::from([
/// (cbor::MapKey::String(String::from("bubbles")), cbor::Value::Int(1)),
/// (cbor::MapKey::String(String::from("ducks")), cbor::Value::Int(2)),
/// ]);
/// map.get(&cbor::MapKeyRef::Str("bubbles") as &dyn cbor::MapLookupKey);
/// ```
#[derive(PartialEq, Eq, Clone)]
pub enum MapKeyRef<'a> {
Int(i64),
Slice(&'a [u8]),
Str(&'a str),
}
impl MapKeyRef<'_> {
fn type_arg_and_payload(&self) -> (u8, u64, Option<&[u8]>) {
match self {
MapKeyRef::Int(v) if v >= &0 => (0, *v as u64, None),
MapKeyRef::Int(v) => (1, !v as u64, None),
MapKeyRef::Slice(b) => (2, b.len() as u64, Some(b)),
MapKeyRef::Str(s) => (3, s.len() as u64, Some(s.as_bytes())),
}
}
fn append_bytes(&self, out: &mut Vec<u8>) {
let (major_type, arg, payload) = self.type_arg_and_payload();
write_header(out, major_type, arg);
if let Some(payload) = payload {
out.extend_from_slice(payload);
}
}
}
impl PartialOrd for MapKeyRef<'_> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for MapKeyRef<'_> {
fn cmp(&self, other: &Self) -> Ordering {
self.type_arg_and_payload().cmp(&other.type_arg_and_payload())
}
}
/// To be used with [`MapKeyRef`]. See the documentation for that type.
pub trait MapLookupKey {
fn to_key(&self) -> MapKeyRef;
}
impl PartialEq for dyn MapLookupKey + '_ {
fn eq(&self, other: &Self) -> bool {
self.to_key().eq(&other.to_key())
}
}
impl Eq for dyn MapLookupKey + '_ {}
impl PartialOrd for dyn MapLookupKey + '_ {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.to_key().cmp(&other.to_key()))
}
}
impl Ord for dyn MapLookupKey + '_ {
fn cmp(&self, other: &Self) -> Ordering {
self.to_key().cmp(&other.to_key())
}
}
impl MapLookupKey for MapKey {
fn to_key(&self) -> MapKeyRef {
self.as_ref()
}
}
impl<'a> MapLookupKey for MapKeyRef<'a> {
fn to_key(&self) -> MapKeyRef {
self.clone()
}
}
impl<'a> Borrow<dyn MapLookupKey + 'a> for MapKey {
fn borrow(&self) -> &(dyn MapLookupKey + 'a) {
self
}
}
/// Parses a CBOR structure from `input_vec`.
///
/// It takes ownership of the `Vec` because byte strings within are returned
/// as ref-counted references to the input. If you want to retain a copy of
/// the input, use `parse_bytes`.
pub fn parse(input_vec: Vec<u8>) -> Result<Value, Error> {
parse_bytes(input_vec.into())
}
/// Parses a CBOR structure from a `Slice`.
///
/// This can be used to parse CBOR within another CBOR structure without
/// having to make a copy of the intermediate byte string.
pub fn parse_bytes(mut input: Bytes) -> Result<Value, Error> {
let ret = parse_value(&mut input, 0)?;
if !input.is_empty() { Err(Error::TrailingData(input.len())) } else { Ok(ret) }
}
fn parse_value(input: &mut Bytes, depth: usize) -> Result<Value, Error> {
if depth > MAX_DEPTH {
return Err(Error::DepthLimitExceeded(input.len(), MAX_DEPTH));
}
let (major_type, arg) = parse_header(input)?;
match major_type {
0 => to_int(arg, false),
1 => to_int(arg, true),
2 => to_bytestring(input, arg),
3 => to_string(input, arg),
4 => to_array(input, arg, depth + 1),
5 => to_map(input, arg, depth + 1),
7 => to_boolean(arg),
_ => Result::Err(Error::UnsupportedMajorType(input.len(), major_type)),
}
}
fn parse_header(input: &mut Bytes) -> Result<(u8, u64), Error> {
let b = get_u8(input)?;
let major_type = b >> 5;
let info = b & 0x1f;
let arg = (match info {
0..=23 => Ok(info as u64),
24 => get_argument(input, 1),
25 => get_argument(input, 2),
26 => get_argument(input, 4),
27 => get_argument(input, 8),
_ => Err(Error::UnsupportedAdditionalInformation(input.len(), info)),
})?;
Ok((major_type, arg))
}
fn get_argument(input: &mut Bytes, num_bytes: u8) -> Result<u64, Error> {
let mut v: u64 = 0;
for _ in 0..num_bytes {
v <<= 8;
let b = get_u8(input)?;
v |= b as u64;
}
let (_, expected_num_bytes) = low_bits_and_length(v);
if num_bytes as usize != expected_num_bytes {
Err(Error::NonMinimalAdditionalData(input.len()))
} else {
Ok(v)
}
}
fn to_int(arg: u64, is_negative: bool) -> Result<Value, Error> {
if is_negative {
if arg > i64::MAX as u64 {
Err(Error::NegativeOutOfRange(arg))
} else {
Ok(Value::Int(!arg as i64))
}
} else if arg > i64::MAX as u64 {
Err(Error::UnsignedOutOfRange(arg))
} else {
Ok(Value::Int(arg as i64))
}
}
fn to_bytestring(input: &mut Bytes, len64: u64) -> Result<Value, Error> {
let Some(len): Option<usize> = len64.try_into().ok() else {
return Err(Error::InputTruncated);
};
let bytes = get(input, len)?;
Ok(Value::Bytestring(bytes))
}
fn to_string(input: &mut Bytes, len64: u64) -> Result<Value, Error> {
let Some(len): Option<usize> = len64.try_into().ok() else {
return Err(Error::InputTruncated);
};
let orig_len = input.len();
let bytes = get(input, len)?;
let vec = Vec::from(bytes.deref());
let Some(string) = String::from_utf8(vec).ok() else {
return Err(Error::InvalidUTF8(orig_len));
};
Ok(Value::String(string))
}
fn to_array(input: &mut Bytes, num_elements: u64, depth: usize) -> Result<Value, Error> {
let mut ret = Vec::new();
for _ in 0..num_elements {
ret.push(parse_value(input, depth)?);
}
Ok(Value::Array(ret))
}
fn to_map(input: &mut Bytes, num_elements: u64, depth: usize) -> Result<Value, Error> {
let mut ret = BTreeMap::new();
let mut previous_key: Option<Bytes> = Option::None;
for _ in 0..num_elements {
let mut key_slice = input.clone();
let key_value = parse_value(input, depth)?;
key_slice.truncate(key_slice.len() - input.len());
if let Some(previous_key_slice) = previous_key {
if *key_slice <= *previous_key_slice {
return Err(Error::MapKeysOutOfOrder(input.len(), key_value));
}
}
previous_key = Some(key_slice);
let key = match key_value {
Value::Int(i) => MapKey::Int(i),
Value::Bytestring(b) => MapKey::Bytestring(Vec::from(b.deref())),
Value::String(s) => MapKey::String(s),
_ => return Err(Error::UnsupportedMapKeyType(input.len(), key_value)),
};
let value = parse_value(input, depth)?;
ret.insert(key, value);
}
Ok(Value::Map(ret))
}
fn to_boolean(arg: u64) -> Result<Value, Error> {
if arg == 20 {
Ok(Value::Boolean(false))
} else if arg == 21 {
Ok(Value::Boolean(true))
} else {
Err(Error::UnsupportedSimpleValue(arg))
}
}
#[cfg(test)]
mod tests {
extern crate hex;
use super::*;
use alloc::{format, vec};
use proptest::prelude::*;
#[test]
fn test_inputs() {
let test_cases = [
("", Err(Error::InputTruncated)),
("d0", Err(Error::UnsupportedMajorType(0, 6))),
("1f", Err(Error::UnsupportedAdditionalInformation(0, 31))),
("01", Ok(Value::Int(1))),
("0100", Err(Error::TrailingData(1))),
("20", Ok(Value::Int(-1))),
("182a", Ok(Value::Int(42))),
("191388", Ok(Value::Int(5000))),
("1a02faf080", Ok(Value::Int(50000000))),
("1b000000746a528800", Ok(Value::Int(500000000000))),
("1b000000746a528800", Ok(Value::Int(500000000000))),
("1bffffffffffffffff", Err(Error::UnsignedOutOfRange(0xffffffffffffffff))),
("3bffffffffffffffff", Err(Error::NegativeOutOfRange(0xffffffffffffffff))),
("1818", Ok(Value::Int(24))),
("1817", Err(Error::NonMinimalAdditionalData(0))),
("190080", Err(Error::NonMinimalAdditionalData(0))),
("60", Ok(Value::String(String::from("")))),
("6161", Ok(Value::String(String::from("a")))),
("40", Ok(Value::Bytestring(Bytes::new()))),
("4100", Ok(Value::Bytestring(Bytes::from(b"\x00".as_slice())))),
("61ff", Err(Error::InvalidUTF8(1))),
("80", Ok(Value::Array(Vec::new()))),
("8101", Ok(Value::Array(vec![Value::Int(1)]))),
("818101", Ok(Value::Array(vec![Value::Array(vec![Value::Int(1)])]))),
(
"8181818181818181818181818181818181818180",
Err(Error::DepthLimitExceeded(3, MAX_DEPTH)),
),
("a0", Ok(Value::Map(BTreeMap::new()))),
("a10101", Ok(Value::Map(BTreeMap::from([(MapKey::Int(1), Value::Int(1))])))),
("a12101", Ok(Value::Map(BTreeMap::from([(MapKey::Int(-2), Value::Int(1))])))),
(
"a201010202",
Ok(Value::Map(BTreeMap::from([
(MapKey::Int(1), Value::Int(1)),
(MapKey::Int(2), Value::Int(2)),
]))),
),
(
"a1410a01",
Ok(Value::Map(BTreeMap::from([(
MapKey::Bytestring(hex::decode("0a").unwrap()),
Value::Int(1),
)]))),
),
// This is a COSE key to check that map ordering works correctly.
(
"a501020326200121582009ac4af6a4646b5bfe81c37f751769c768c5c41ffea633dad0f48e6e3bc3e9a0225820269fbe132c40bf11f4de4a92bec527901906fdce98bbed52df9b175b6a4f3808",
Ok(Value::Map(BTreeMap::from([
(MapKey::Int(1), Value::Int(2)),
(MapKey::Int(3), Value::Int(-7)),
(MapKey::Int(-1), Value::Int(1)),
(
MapKey::Int(-2),
Value::Bytestring(Bytes::from(
hex::decode(
"09ac4af6a4646b5bfe81c37f751769c768c5c41ffea633dad0f48e6e3bc3e9a0",
)
.unwrap(),
)),
),
(
MapKey::Int(-3),
Value::Bytestring(Bytes::from(
hex::decode(
"269fbe132c40bf11f4de4a92bec527901906fdce98bbed52df9b175b6a4f3808",
)
.unwrap(),
)),
),
]))),
),
(
"a101a101a101a101a101a101a101a101a101a101a101a101a101a101a101a101a101a101a101a0",
Err(Error::DepthLimitExceeded(6, MAX_DEPTH)),
),
("a202010102", Err(Error::MapKeysOutOfOrder(1, Value::Int(1)))),
("a1810101", Err(Error::UnsupportedMapKeyType(1, Value::Array(vec![Value::Int(1)])))),
("f4", Ok(Value::Boolean(false))),
("f5", Ok(Value::Boolean(true))),
("f6", Err(Error::UnsupportedSimpleValue(22))),
];
for test in test_cases {
let bytes = Bytes::from(hex::decode(test.0).unwrap());
let result = parse_bytes(bytes.clone());
assert_eq!(result, test.1, "{}", test.0);
if let Ok(value) = result {
let bytes2 = value.to_bytes();
assert_eq!(bytes, bytes2, "{}", test.0);
}
if bytes.len() > 0 {
// All truncations of the input should fail to parse.
for i in 0..bytes.len() - 1 {
let mut bytes2 = bytes.clone();
bytes2.truncate(i);
let result = parse_bytes(bytes2.clone());
assert!(matches!(result, Result::Err(_)));
}
}
}
}
const EXAMPLE: &str = "example";
const EXAMPLE_KEY: &dyn MapLookupKey = &MapKeyRef::Str(EXAMPLE) as &dyn MapLookupKey;
#[test]
fn test_lookup() {
let map = BTreeMap::from([(MapKey::String(String::from(EXAMPLE)), Value::Int(1))]);
assert_eq!(map.get(EXAMPLE_KEY), Some(&Value::Int(1)));
}
#[test]
fn test_macro() {
assert_eq!(cbor!(1), Value::Int(1));
assert_eq!(cbor!("test"), Value::String(String::from("test")));
assert_eq!(cbor!(b"123"), Value::Bytestring(Bytes::from(b"\x31\x32\x33".as_slice())));
assert_eq!(cbor!([1, 2]), Value::Array(vec![Value::Int(1), Value::Int(2)]));
assert_eq!(
cbor!([1, true, "str"]),
Value::Array(vec![
Value::Int(1),
Value::Boolean(true),
Value::String(String::from("str"))
])
);
assert_eq!(
cbor!({1: 2, 3: 4}),
Value::Map(BTreeMap::from([
(MapKey::Int(1), Value::Int(2)),
(MapKey::Int(3), Value::Int(4))
]))
);
assert_eq!(
cbor!({1: 2, 3: [1]}),
Value::Map(BTreeMap::from([
(MapKey::Int(1), Value::Int(2)),
(MapKey::Int(3), Value::Array(vec![Value::Int(1)]))
]))
);
assert_eq!(
cbor!({1: 2, 3: {"s":2}}),
Value::Map(BTreeMap::from([
(MapKey::Int(1), Value::Int(2)),
(
MapKey::Int(3),
Value::Map(BTreeMap::from([(
MapKey::String(String::from("s")),
Value::Int(2)
)]))
)
]))
);
assert_eq!(
cbor!({b"0": 1}),
Value::Map(BTreeMap::from([(MapKey::Bytestring(vec![0x30u8]), Value::Int(1)),]))
);
}
#[test]
fn test_debug() {
let value = cbor!({
1: 2,
"three": "four",
"five": [6, 7, "eight"],
"nine": {
"ten": [11],
"twelve": {
b"13": 14,
},
},
});
let debug = format!("{:?}", value);
assert_eq!(
debug,
"{1: 2, \"five\": [6, 7, \"eight\"], \"nine\": {\"ten\": [11], \"twelve\": {h\"3133\": 14}}, \"three\": \"four\"}"
);
}
fn arb_map_key() -> impl Strategy<Value = MapKey> {
prop_oneof![
any::<i64>().prop_map(MapKey::Int),
".*".prop_map(MapKey::String),
prop::collection::vec(0..255u8, 0..32).prop_map(MapKey::Bytestring),
]
}
fn arb_value() -> impl Strategy<Value = Value> {
let leaf = prop_oneof![
any::<i64>().prop_map(Value::Int),
prop::collection::vec(0..255u8, 0..512)
.prop_map(Bytes::from)
.prop_map(Value::Bytestring),
".*".prop_map(Value::String),
any::<bool>().prop_map(Value::Boolean),
];
leaf.prop_recursive(
8, // 8 levels deep
256, // maximum size of 256 nodes
10, // up to 10 items per collection
|inner| {
prop_oneof![
prop::collection::vec(inner.clone(), 0..10).prop_map(Value::Array),
prop::collection::btree_map(arb_map_key(), inner, 0..10).prop_map(Value::Map),
]
},
)
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(500))]
#[test]
fn test_serialize(a in arb_value()) {
assert_eq!(parse(a.to_bytes()).unwrap(), a);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn test_map_key_ordering(a in arb_map_key(), b in arb_map_key()) {
// Ordering of MapKeys should match the ordering of their
// serialisations.
let mut a_bytes = Vec::new();
a.append_bytes(&mut a_bytes);
let mut b_bytes = Vec::new();
b.append_bytes(&mut b_bytes);
assert_eq!(a_bytes.cmp(&b_bytes), a.cmp(&b));
}
}
}