aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrei Homescu <ahomescu@google.com>2023-10-17 05:18:36 +0000
committerAndrei Homescu <ahomescu@google.com>2023-10-17 05:18:36 +0000
commit02178b638951eeedf32f57cef31180a87f7cb448 (patch)
treee1ef9eee9dc3ce7c25b77e4420438e97fc6d777c
parent223e2a0e7fc00c1d6ff5e1a7cdc18f34868865dd (diff)
parent1377d4e18e9e36adc0c561944f9429dbc4b3b8a4 (diff)
downloadciborium-trusty-main.tar.gz
Merge remote-tracking branch 'aosp/main' into trusty-maintrusty-main
Change-Id: I3929bf4d6c6364712b1ae1ba318c8f8b7230cb2d
-rw-r--r--.cargo_vcs_info.json5
-rw-r--r--Android.bp39
-rw-r--r--Cargo.toml9
-rw-r--r--Cargo.toml.orig10
-rw-r--r--METADATA19
-rw-r--r--OWNERS2
-rw-r--r--README.md6
-rw-r--r--TEST_MAPPING13
-rw-r--r--cargo2android.json18
-rw-r--r--src/de/mod.rs127
-rw-r--r--src/lib.rs16
-rw-r--r--src/ser/mod.rs3
-rw-r--r--src/tag.rs14
-rw-r--r--src/value/canonical.rs124
-rw-r--r--src/value/de.rs6
-rw-r--r--src/value/integer.rs59
-rw-r--r--src/value/mod.rs213
-rw-r--r--src/value/ser.rs14
-rw-r--r--tests/canonical.rs111
-rw-r--r--tests/codec.rs38
-rw-r--r--tests/error.rs14
-rw-r--r--tests/recursion.rs46
-rw-r--r--tests/tag.rs4
23 files changed, 814 insertions, 96 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644
index 0000000..4212810
--- /dev/null
+++ b/.cargo_vcs_info.json
@@ -0,0 +1,5 @@
+{
+ "git": {
+ "sha1": "7d8f6e499db51fe52f5d3c2ce1d0e0be61c7eaa2"
+ }
+}
diff --git a/Android.bp b/Android.bp
index 3cdbd00..fad6722 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,8 +1,6 @@
-// This file is generated by cargo2android.py --run --device --dependencies.
+// This file is generated by cargo2android.py --config cargo2android.json.
// Do not modify this file as changes will be overridden on upgrade.
-
-
package {
default_applicable_licenses: ["external_rust_crates_ciborium_license"],
}
@@ -25,7 +23,7 @@ rust_library {
host_supported: true,
crate_name: "ciborium",
cargo_env_compat: true,
- cargo_pkg_version: "0.2.0",
+ cargo_pkg_version: "0.2.1",
srcs: ["src/lib.rs"],
edition: "2021",
features: [
@@ -37,4 +35,37 @@ rust_library {
"libciborium_ll",
"libserde",
],
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
+ product_available: true,
+ vendor_available: true,
+}
+
+rust_library_rlib {
+ name: "libciborium_nostd",
+ crate_name: "ciborium",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.2.1",
+ srcs: ["src/lib.rs"],
+ edition: "2021",
+ rustlibs: [
+ "libciborium_io_nostd",
+ "libciborium_ll_nostd",
+ "libserde_nostd",
+ ],
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
+ prefer_rlib: true,
+ no_stdlibs: true,
+ stdlibs: [
+ "liballoc.rust_sysroot",
+ "libcompiler_builtins.rust_sysroot",
+ "libcore.rust_sysroot",
+ ],
+ product_available: true,
+ vendor_available: true,
}
diff --git a/Cargo.toml b/Cargo.toml
index ae4f377..f82b8b9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,8 +11,9 @@
[package]
edition = "2021"
+rust-version = "1.56"
name = "ciborium"
-version = "0.2.0"
+version = "0.2.1"
authors = ["Nathaniel McCallum <npmccallum@profian.com>"]
description = "serde implementation of CBOR using ciborium-basic"
homepage = "https://github.com/enarx/ciborium"
@@ -21,12 +22,14 @@ keywords = ["cbor", "serde"]
categories = ["data-structures", "embedded", "encoding", "no-std", "parsing"]
license = "Apache-2.0"
repository = "https://github.com/enarx/ciborium"
+[package.metadata.docs.rs]
+all-features = true
[dependencies.ciborium-io]
-version = "0.2.0"
+version = "0.2.1"
features = ["alloc"]
[dependencies.ciborium-ll]
-version = "0.2.0"
+version = "0.2.1"
[dependencies.serde]
version = "1.0"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
index 9e575ab..d1e14d3 100644
--- a/Cargo.toml.orig
+++ b/Cargo.toml.orig
@@ -1,9 +1,10 @@
[package]
name = "ciborium"
-version = "0.2.0"
+version = "0.2.1"
authors = ["Nathaniel McCallum <npmccallum@profian.com>"]
license = "Apache-2.0"
edition = "2021"
+rust-version = "1.56"
homepage = "https://github.com/enarx/ciborium"
repository = "https://github.com/enarx/ciborium"
description = "serde implementation of CBOR using ciborium-basic"
@@ -20,8 +21,8 @@ is-it-maintained-issue-resolution = { repository = "enarx/ciborium" }
is-it-maintained-open-issues = { repository = "enarx/ciborium" }
[dependencies]
-ciborium-ll = { path = "../ciborium-ll", version = "0.2.0" }
-ciborium-io = { path = "../ciborium-io", version = "0.2.0", features = ["alloc"] }
+ciborium-ll = { path = "../ciborium-ll", version = "0.2.1" }
+ciborium-io = { path = "../ciborium-io", version = "0.2.1", features = ["alloc"] }
serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] }
[dev-dependencies]
@@ -33,3 +34,6 @@ hex = "0.4"
[features]
default = ["std"]
std = ["ciborium-io/std", "serde/std"]
+
+[package.metadata.docs.rs]
+all-features = true
diff --git a/METADATA b/METADATA
index f59b026..7eda68e 100644
--- a/METADATA
+++ b/METADATA
@@ -1,7 +1,9 @@
-name: "ciborium"
-description:
- "serde implementation of CBOR using ciborium-basic"
+# This project was upgraded with external_updater.
+# Usage: tools/external_updater/updater.sh update rust/crates/ciborium
+# For more info, check https://cs.android.com/android/platform/superproject/+/master:tools/external_updater/README.md
+name: "ciborium"
+description: "serde implementation of CBOR using ciborium-basic"
third_party {
url {
type: HOMEPAGE
@@ -9,10 +11,13 @@ third_party {
}
url {
type: ARCHIVE
- value: "https://static.crates.io/crates/ciborium/ciborium-0.2.0.crate"
+ value: "https://static.crates.io/crates/ciborium/ciborium-0.2.1.crate"
}
- version: "0.2.0"
- last_upgrade_date { year: 2022 month: 1 day: 17 }
+ version: "0.2.1"
license_type: NOTICE
+ last_upgrade_date {
+ year: 2023
+ month: 6
+ day: 14
+ }
}
-
diff --git a/OWNERS b/OWNERS
index 598ca58..3e2a0de 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,4 +1,4 @@
-include platform/prebuilts/rust:master:/OWNERS
+include platform/prebuilts/rust:main:/OWNERS
# Android Hardware Security
asbel@google.com
jbires@google.com
diff --git a/README.md b/README.md
index 2e35ff1..b60c35e 100644
--- a/README.md
+++ b/README.md
@@ -11,12 +11,12 @@ Ciborium contains CBOR serialization and deserialization implementations for ser
## Quick Start
-You're probably looking for [`de::from_reader()`](crate::de::from_reader)
-and [`ser::into_writer()`](crate::ser::into_writer), which are
+You're probably looking for [`from_reader()`](crate::de::from_reader)
+and [`into_writer()`](crate::ser::into_writer), which are
the main functions. Note that byte slices are also readers and writers and can be
passed to these functions just as streams can.
-For dynamic CBOR value creation/inspection, see [`value::Value`](crate::value::Value).
+For dynamic CBOR value creation/inspection, see [`Value`](crate::value::Value).
## Design Decisions
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 2f7de78..2903e5f 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -1,13 +1,14 @@
// Generated by update_crate_tests.py for tests that depend on this crate.
{
- "presubmit": [
+ "imports": [
{
- "name": "libcert_request_validator_tests"
- }
- ],
- "presubmit-rust": [
+ "path": "external/rust/crates/coset"
+ },
+ {
+ "path": "system/keymint/derive"
+ },
{
- "name": "libcert_request_validator_tests"
+ "path": "system/keymint/hal"
}
]
}
diff --git a/cargo2android.json b/cargo2android.json
new file mode 100644
index 0000000..adac568
--- /dev/null
+++ b/cargo2android.json
@@ -0,0 +1,18 @@
+{
+ "device": true,
+ "run": true,
+ "dependencies": true,
+ "variants": [
+ {
+ },
+ {
+ "alloc": true,
+ "dependency_suffix": "_nostd",
+ "features": "",
+ "force-rlib": true,
+ "no-host": true,
+ "suffix": "_nostd",
+ "no-std": true
+ }
+ ]
+}
diff --git a/src/de/mod.rs b/src/de/mod.rs
index 63e5109..b68458d 100644
--- a/src/de/mod.rs
+++ b/src/de/mod.rs
@@ -51,7 +51,7 @@ struct Deserializer<'b, R: Read> {
recurse: usize,
}
-impl<'de, 'a, 'b, R: Read> Deserializer<'b, R>
+impl<'a, R: Read> Deserializer<'a, R>
where
R::Error: core::fmt::Debug,
{
@@ -348,7 +348,7 @@ where
let mut segments = self.decoder.text(len);
while let Some(mut segment) = segments.pull()? {
- while let Some(chunk) = segment.pull(&mut self.scratch)? {
+ while let Some(chunk) = segment.pull(self.scratch)? {
buffer.push_str(chunk);
}
}
@@ -371,6 +371,11 @@ where
visitor.visit_bytes(&self.scratch[..len])
}
+ Header::Array(len) => self.recurse(|me| {
+ let access = Access(me, len);
+ visitor.visit_seq(access)
+ }),
+
header => Err(header.expected("bytes")),
};
}
@@ -389,7 +394,7 @@ where
let mut segments = self.decoder.bytes(len);
while let Some(mut segment) = segments.pull()? {
- while let Some(chunk) = segment.pull(&mut self.scratch)? {
+ while let Some(chunk) = segment.pull(self.scratch)? {
buffer.extend_from_slice(chunk);
}
}
@@ -397,7 +402,12 @@ where
visitor.visit_byte_buf(buffer)
}
- header => Err(header.expected("expected byte buffer")),
+ Header::Array(len) => self.recurse(|me| {
+ let access = Access(me, len);
+ visitor.visit_seq(access)
+ }),
+
+ header => Err(header.expected("byte buffer")),
};
}
}
@@ -412,6 +422,19 @@ where
visitor.visit_seq(access)
}),
+ Header::Bytes(len) => {
+ let mut buffer = Vec::new();
+
+ let mut segments = self.decoder.bytes(len);
+ while let Some(mut segment) = segments.pull()? {
+ while let Some(chunk) = segment.pull(self.scratch)? {
+ buffer.extend_from_slice(chunk);
+ }
+ }
+
+ visitor.visit_seq(BytesAccess::<R>(0, buffer, core::marker::PhantomData))
+ }
+
header => Err(header.expected("array")),
};
}
@@ -462,7 +485,28 @@ where
self,
visitor: V,
) -> Result<V::Value, Self::Error> {
- self.deserialize_str(visitor)
+ loop {
+ let offset = self.decoder.offset();
+
+ return match self.decoder.pull()? {
+ Header::Tag(..) => continue,
+
+ Header::Text(Some(len)) if len <= self.scratch.len() => {
+ self.decoder.read_exact(&mut self.scratch[..len])?;
+
+ match core::str::from_utf8(&self.scratch[..len]) {
+ Ok(s) => visitor.visit_str(s),
+ Err(..) => Err(Error::Syntax(offset)),
+ }
+ }
+ Header::Bytes(Some(len)) if len <= self.scratch.len() => {
+ self.decoder.read_exact(&mut self.scratch[..len])?;
+ visitor.visit_bytes(&self.scratch[..len])
+ }
+
+ header => Err(header.expected("str or bytes")),
+ };
+ }
}
fn deserialize_ignored_any<V: de::Visitor<'de>>(
@@ -474,16 +518,13 @@ where
#[inline]
fn deserialize_option<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
- loop {
- return match self.decoder.pull()? {
- Header::Simple(simple::UNDEFINED) => visitor.visit_none(),
- Header::Simple(simple::NULL) => visitor.visit_none(),
- Header::Tag(..) => continue,
- header => {
- self.decoder.push(header);
- visitor.visit_some(self)
- }
- };
+ match self.decoder.pull()? {
+ Header::Simple(simple::UNDEFINED) => visitor.visit_none(),
+ Header::Simple(simple::NULL) => visitor.visit_none(),
+ header => {
+ self.decoder.push(header);
+ visitor.visit_some(self)
+ }
}
}
@@ -683,6 +724,36 @@ where
}
}
+struct BytesAccess<R: Read>(usize, Vec<u8>, core::marker::PhantomData<R>);
+
+impl<'de, R: Read> de::SeqAccess<'de> for BytesAccess<R>
+where
+ R::Error: core::fmt::Debug,
+{
+ type Error = Error<R::Error>;
+
+ #[inline]
+ fn next_element_seed<U: de::DeserializeSeed<'de>>(
+ &mut self,
+ seed: U,
+ ) -> Result<Option<U::Value>, Self::Error> {
+ use de::IntoDeserializer;
+
+ if self.0 < self.1.len() {
+ let byte = self.1[self.0];
+ self.0 += 1;
+ seed.deserialize(byte.into_deserializer()).map(Some)
+ } else {
+ Ok(None)
+ }
+ }
+
+ #[inline]
+ fn size_hint(&self) -> Option<usize> {
+ Some(self.1.len() - self.0)
+ }
+}
+
struct TagAccess<'a, 'b, R: Read>(&'a mut Deserializer<'b, R>, usize);
impl<'de, 'a, 'b, R: Read> de::Deserializer<'de> for &mut TagAccess<'a, 'b, R>
@@ -746,7 +817,7 @@ where
/// Deserializes as CBOR from a type with [`impl ciborium_io::Read`](ciborium_io::Read)
#[inline]
-pub fn from_reader<'de, T: de::Deserialize<'de>, R: Read>(reader: R) -> Result<T, Error<R::Error>>
+pub fn from_reader<T: de::DeserializeOwned, R: Read>(reader: R) -> Result<T, Error<R::Error>>
where
R::Error: core::fmt::Debug,
{
@@ -760,3 +831,27 @@ where
T::deserialize(&mut reader)
}
+
+/// Deserializes as CBOR from a type with [`impl ciborium_io::Read`](ciborium_io::Read), with
+/// a specified maximum recursion limit. Inputs that are nested beyond the specified limit
+/// will result in [`Error::RecursionLimitExceeded`] .
+///
+/// Set a high recursion limit at your own risk (of stack exhaustion)!
+#[inline]
+pub fn from_reader_with_recursion_limit<T: de::DeserializeOwned, R: Read>(
+ reader: R,
+ recurse_limit: usize,
+) -> Result<T, Error<R::Error>>
+where
+ R::Error: core::fmt::Debug,
+{
+ let mut scratch = [0; 4096];
+
+ let mut reader = Deserializer {
+ decoder: reader.into(),
+ scratch: &mut scratch,
+ recurse: recurse_limit,
+ };
+
+ T::deserialize(&mut reader)
+}
diff --git a/src/lib.rs b/src/lib.rs
index 78c4bbd..9696e5f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -6,12 +6,12 @@
//!
//! # Quick Start
//!
-//! You're probably looking for [`de::from_reader()`](crate::de::from_reader)
-//! and [`ser::into_writer()`](crate::ser::into_writer), which are
+//! You're probably looking for [`from_reader()`](crate::de::from_reader)
+//! and [`into_writer()`](crate::ser::into_writer), which are
//! the main functions. Note that byte slices are also readers and writers and can be
//! passed to these functions just as streams can.
//!
-//! For dynamic CBOR value creation/inspection, see [`value::Value`](crate::value::Value).
+//! For dynamic CBOR value creation/inspection, see [`Value`](crate::value::Value).
//!
//! # Design Decisions
//!
@@ -97,6 +97,16 @@ pub mod ser;
pub mod tag;
pub mod value;
+// Re-export the [items recommended by serde](https://serde.rs/conventions.html).
+#[doc(inline)]
+pub use crate::de::from_reader;
+
+#[doc(inline)]
+pub use crate::ser::into_writer;
+
+#[doc(inline)]
+pub use crate::value::Value;
+
/// Build a `Value` conveniently.
///
/// The syntax should be intuitive if you are familiar with JSON. You can also
diff --git a/src/ser/mod.rs b/src/ser/mod.rs
index 0488d72..ff3d118 100644
--- a/src/ser/mod.rs
+++ b/src/ser/mod.rs
@@ -495,6 +495,5 @@ where
W::Error: core::fmt::Debug,
{
let mut encoder = Serializer::from(writer);
- value.serialize(&mut encoder)?;
- Ok(encoder.0.flush()?)
+ value.serialize(&mut encoder)
}
diff --git a/src/tag.rs b/src/tag.rs
index bc18753..1196575 100644
--- a/src/tag.rs
+++ b/src/tag.rs
@@ -426,7 +426,7 @@ impl ser::Serializer for Serializer {
}
}
-impl<'a> ser::SerializeSeq for Serializer {
+impl ser::SerializeSeq for Serializer {
type Ok = u64;
type Error = Error;
@@ -441,7 +441,7 @@ impl<'a> ser::SerializeSeq for Serializer {
}
}
-impl<'a> ser::SerializeTuple for Serializer {
+impl ser::SerializeTuple for Serializer {
type Ok = u64;
type Error = Error;
@@ -456,7 +456,7 @@ impl<'a> ser::SerializeTuple for Serializer {
}
}
-impl<'a> ser::SerializeTupleStruct for Serializer {
+impl ser::SerializeTupleStruct for Serializer {
type Ok = u64;
type Error = Error;
@@ -471,7 +471,7 @@ impl<'a> ser::SerializeTupleStruct for Serializer {
}
}
-impl<'a> ser::SerializeTupleVariant for Serializer {
+impl ser::SerializeTupleVariant for Serializer {
type Ok = u64;
type Error = Error;
@@ -486,7 +486,7 @@ impl<'a> ser::SerializeTupleVariant for Serializer {
}
}
-impl<'a> ser::SerializeMap for Serializer {
+impl ser::SerializeMap for Serializer {
type Ok = u64;
type Error = Error;
@@ -506,7 +506,7 @@ impl<'a> ser::SerializeMap for Serializer {
}
}
-impl<'a> ser::SerializeStruct for Serializer {
+impl ser::SerializeStruct for Serializer {
type Ok = u64;
type Error = Error;
@@ -525,7 +525,7 @@ impl<'a> ser::SerializeStruct for Serializer {
}
}
-impl<'a> ser::SerializeStructVariant for Serializer {
+impl ser::SerializeStructVariant for Serializer {
type Ok = u64;
type Error = Error;
diff --git a/src/value/canonical.rs b/src/value/canonical.rs
new file mode 100644
index 0000000..f1196f4
--- /dev/null
+++ b/src/value/canonical.rs
@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::value::Value;
+use alloc::vec::Vec;
+use core::cmp::Ordering;
+use serde::{de, ser};
+
+/// Manually serialize values to compare them.
+fn serialized_canonical_cmp(v1: &Value, v2: &Value) -> Ordering {
+ // There is an optimization to be done here, but it would take a lot more code
+ // and using mixing keys, Arrays or Maps as CanonicalValue is probably not the
+ // best use of this type as it is meant mainly to be used as keys.
+
+ let mut bytes1 = Vec::new();
+ let _ = crate::ser::into_writer(v1, &mut bytes1);
+ let mut bytes2 = Vec::new();
+ let _ = crate::ser::into_writer(v2, &mut bytes2);
+
+ match bytes1.len().cmp(&bytes2.len()) {
+ Ordering::Equal => bytes1.cmp(&bytes2),
+ x => x,
+ }
+}
+
+/// Compares two values uses canonical comparison, as defined in both
+/// RFC 7049 Section 3.9 (regarding key sorting) and RFC 8949 4.2.3 (as errata).
+///
+/// In short, the comparison follow the following rules:
+/// - If two keys have different lengths, the shorter one sorts earlier;
+/// - If two keys have the same length, the one with the lower value in
+/// (byte-wise) lexical order sorts earlier.
+///
+/// This specific comparison allows Maps and sorting that respect these two rules.
+pub fn cmp_value(v1: &Value, v2: &Value) -> Ordering {
+ use Value::*;
+
+ match (v1, v2) {
+ (Integer(i), Integer(o)) => {
+ // Because of the first rule above, two numbers might be in a different
+ // order than regular i128 comparison. For example, 10 < -1 in
+ // canonical ordering, since 10 serializes to `0x0a` and -1 to `0x20`,
+ // and -1 < -1000 because of their lengths.
+ i.canonical_cmp(o)
+ }
+ (Text(s), Text(o)) => match s.len().cmp(&o.len()) {
+ Ordering::Equal => s.cmp(o),
+ x => x,
+ },
+ (Bool(s), Bool(o)) => s.cmp(o),
+ (Null, Null) => Ordering::Equal,
+ (Tag(t, v), Tag(ot, ov)) => match Value::from(*t).partial_cmp(&Value::from(*ot)) {
+ Some(Ordering::Equal) | None => match v.partial_cmp(ov) {
+ Some(x) => x,
+ None => serialized_canonical_cmp(v1, v2),
+ },
+ Some(x) => x,
+ },
+ (_, _) => serialized_canonical_cmp(v1, v2),
+ }
+}
+
+/// A CBOR Value that impl Ord and Eq to allow sorting of values as defined in both
+/// RFC 7049 Section 3.9 (regarding key sorting) and RFC 8949 4.2.3 (as errata).
+///
+/// Since a regular [Value] can be
+#[derive(Clone, Debug)]
+pub struct CanonicalValue(Value);
+
+impl PartialEq for CanonicalValue {
+ fn eq(&self, other: &Self) -> bool {
+ self.cmp(other) == Ordering::Equal
+ }
+}
+
+impl Eq for CanonicalValue {}
+
+impl From<Value> for CanonicalValue {
+ fn from(v: Value) -> Self {
+ Self(v)
+ }
+}
+
+impl From<CanonicalValue> for Value {
+ fn from(v: CanonicalValue) -> Self {
+ v.0
+ }
+}
+
+impl ser::Serialize for CanonicalValue {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: ser::Serializer,
+ {
+ self.0.serialize(serializer)
+ }
+}
+
+impl<'de> de::Deserialize<'de> for CanonicalValue {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ Value::deserialize(deserializer).map(Into::into)
+ }
+
+ fn deserialize_in_place<D>(deserializer: D, place: &mut Self) -> Result<(), D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ Value::deserialize_in_place(deserializer, &mut place.0)
+ }
+}
+
+impl Ord for CanonicalValue {
+ fn cmp(&self, other: &Self) -> Ordering {
+ cmp_value(&self.0, &other.0)
+ }
+}
+
+impl PartialOrd for CanonicalValue {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ Some(cmp_value(&self.0, &other.0))
+ }
+}
diff --git a/src/value/de.rs b/src/value/de.rs
index 0d30a86..6323190 100644
--- a/src/value/de.rs
+++ b/src/value/de.rs
@@ -173,7 +173,7 @@ impl<'de> de::Deserialize<'de> for Value {
struct Deserializer<T>(T);
-impl<'a, 'de> Deserializer<&'a Value> {
+impl<'a> Deserializer<&'a Value> {
fn integer<N>(&self, kind: &'static str) -> Result<N, Error>
where
N: TryFrom<u128>,
@@ -233,7 +233,7 @@ impl<'a, 'de> de::Deserializer<'de> for Deserializer<&'a Value> {
Value::Null => visitor.visit_none(),
Value::Tag(t, v) => {
- let parent: Deserializer<&Value> = Deserializer(&*v);
+ let parent: Deserializer<&Value> = Deserializer(v);
let access = crate::tag::TagAccess::new(parent, Some(*t));
visitor.visit_enum(access)
}
@@ -487,7 +487,7 @@ impl<'a, 'de> de::Deserializer<'de> for Deserializer<&'a Value> {
v => (None, v),
};
- let parent: Deserializer<&Value> = Deserializer(&*val);
+ let parent: Deserializer<&Value> = Deserializer(val);
let access = crate::tag::TagAccess::new(parent, tag);
return visitor.visit_enum(access);
}
diff --git a/src/value/integer.rs b/src/value/integer.rs
index 4dd1f3a..ef6ea3d 100644
--- a/src/value/integer.rs
+++ b/src/value/integer.rs
@@ -1,4 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
+use core::cmp::Ordering;
macro_rules! implfrom {
($( $(#[$($attr:meta)+])? $t:ident)+) => {
@@ -33,6 +34,64 @@ macro_rules! implfrom {
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Integer(i128);
+impl Integer {
+ /// Returns the canonical length this integer will have when serialized to bytes.
+ /// This is called `canonical` as it is only used for canonically comparing two
+ /// values. It shouldn't be used in any other context.
+ fn canonical_len(&self) -> usize {
+ let x = self.0;
+
+ if let Ok(x) = u8::try_from(x) {
+ if x < 24 {
+ 1
+ } else {
+ 2
+ }
+ } else if let Ok(x) = i8::try_from(x) {
+ if x >= -24i8 {
+ 1
+ } else {
+ 2
+ }
+ } else if u16::try_from(x).is_ok() || i16::try_from(x).is_ok() {
+ 3
+ } else if u32::try_from(x).is_ok() || i32::try_from(x).is_ok() {
+ 5
+ } else if u64::try_from(x).is_ok() || i64::try_from(x).is_ok() {
+ 9
+ } else {
+ // Ciborium serializes u128/i128 as BigPos if they don't fit in 64 bits.
+ // In this special case we have to calculate the length.
+ // The Tag itself will always be 1 byte.
+ x.to_be_bytes().len() + 1
+ }
+ }
+
+ /// Compare two integers as if we were to serialize them, but more efficiently.
+ pub fn canonical_cmp(&self, other: &Self) -> Ordering {
+ match self.canonical_len().cmp(&other.canonical_len()) {
+ Ordering::Equal => {
+ // Negative numbers are higher in byte-order than positive numbers.
+ match (self.0.is_negative(), other.0.is_negative()) {
+ (false, true) => Ordering::Less,
+ (true, false) => Ordering::Greater,
+ (true, true) => {
+ // For negative numbers the byte order puts numbers closer to 0 which
+ // are lexically higher, lower. So -1 < -2 when sorting by be_bytes().
+ match self.0.cmp(&other.0) {
+ Ordering::Less => Ordering::Greater,
+ Ordering::Equal => Ordering::Equal,
+ Ordering::Greater => Ordering::Less,
+ }
+ }
+ (_, _) => self.0.cmp(&other.0),
+ }
+ }
+ x => x,
+ }
+ }
+}
+
implfrom! {
u8 u16 u32 u64
i8 i16 i32 i64
diff --git a/src/value/mod.rs b/src/value/mod.rs
index 40988b0..7233026 100644
--- a/src/value/mod.rs
+++ b/src/value/mod.rs
@@ -2,12 +2,14 @@
//! A dynamic CBOR value
+mod canonical;
mod integer;
mod de;
mod error;
mod ser;
+pub use canonical::CanonicalValue;
pub use error::Error;
pub use integer::Integer;
@@ -49,7 +51,7 @@ impl Value {
/// Returns true if the `Value` is an `Integer`. Returns false otherwise.
///
/// ```
- /// # use ciborium::value::Value;
+ /// # use ciborium::Value;
/// #
/// let value = Value::Integer(17.into());
///
@@ -63,7 +65,7 @@ impl Value {
/// Returns None otherwise.
///
/// ```
- /// # use ciborium::value::Value;
+ /// # use ciborium::Value;
/// #
/// let value = Value::Integer(17.into());
///
@@ -77,10 +79,29 @@ impl Value {
}
}
+ /// If the `Value` is a `Integer`, returns a the associated `Integer` data as `Ok`.
+ /// Returns `Err(Self)` otherwise.
+ ///
+ /// ```
+ /// # use ciborium::{Value, value::Integer};
+ /// #
+ /// let value = Value::Integer(17.into());
+ /// assert_eq!(value.into_integer(), Ok(Integer::from(17)));
+ ///
+ /// let value = Value::Bool(true);
+ /// assert_eq!(value.into_integer(), Err(Value::Bool(true)));
+ /// ```
+ pub fn into_integer(self) -> Result<Integer, Self> {
+ match self {
+ Value::Integer(int) => Ok(int),
+ other => Err(other),
+ }
+ }
+
/// Returns true if the `Value` is a `Bytes`. Returns false otherwise.
///
/// ```
- /// # use ciborium::value::Value;
+ /// # use ciborium::Value;
/// #
/// let value = Value::Bytes(vec![104, 101, 108, 108, 111]);
///
@@ -94,7 +115,7 @@ impl Value {
/// Returns None otherwise.
///
/// ```
- /// # use ciborium::value::Value;
+ /// # use ciborium::Value;
/// #
/// let value = Value::Bytes(vec![104, 101, 108, 108, 111]);
///
@@ -111,7 +132,7 @@ impl Value {
/// Returns None otherwise.
///
/// ```
- /// # use ciborium::value::Value;
+ /// # use ciborium::Value;
/// #
/// let mut value = Value::Bytes(vec![104, 101, 108, 108, 111]);
/// value.as_bytes_mut().unwrap().clear();
@@ -125,10 +146,29 @@ impl Value {
}
}
+ /// If the `Value` is a `Bytes`, returns a the associated `Vec<u8>` data as `Ok`.
+ /// Returns `Err(Self)` otherwise.
+ ///
+ /// ```
+ /// # use ciborium::Value;
+ /// #
+ /// let value = Value::Bytes(vec![104, 101, 108, 108, 111]);
+ /// assert_eq!(value.into_bytes(), Ok(vec![104, 101, 108, 108, 111]));
+ ///
+ /// let value = Value::Bool(true);
+ /// assert_eq!(value.into_bytes(), Err(Value::Bool(true)));
+ /// ```
+ pub fn into_bytes(self) -> Result<Vec<u8>, Self> {
+ match self {
+ Value::Bytes(vec) => Ok(vec),
+ other => Err(other),
+ }
+ }
+
/// Returns true if the `Value` is a `Float`. Returns false otherwise.
///
/// ```
- /// # use ciborium::value::Value;
+ /// # use ciborium::Value;
/// #
/// let value = Value::Float(17.0.into());
///
@@ -142,7 +182,7 @@ impl Value {
/// Returns None otherwise.
///
/// ```
- /// # use ciborium::value::Value;
+ /// # use ciborium::Value;
/// #
/// let value = Value::Float(17.0.into());
///
@@ -156,10 +196,29 @@ impl Value {
}
}
+ /// If the `Value` is a `Float`, returns a the associated `f64` data as `Ok`.
+ /// Returns `Err(Self)` otherwise.
+ ///
+ /// ```
+ /// # use ciborium::Value;
+ /// #
+ /// let value = Value::Float(17.);
+ /// assert_eq!(value.into_float(), Ok(17.));
+ ///
+ /// let value = Value::Bool(true);
+ /// assert_eq!(value.into_float(), Err(Value::Bool(true)));
+ /// ```
+ pub fn into_float(self) -> Result<f64, Self> {
+ match self {
+ Value::Float(f) => Ok(f),
+ other => Err(other),
+ }
+ }
+
/// Returns true if the `Value` is a `Text`. Returns false otherwise.
///
/// ```
- /// # use ciborium::value::Value;
+ /// # use ciborium::Value;
/// #
/// let value = Value::Text(String::from("hello"));
///
@@ -173,7 +232,7 @@ impl Value {
/// Returns None otherwise.
///
/// ```
- /// # use ciborium::value::Value;
+ /// # use ciborium::Value;
/// #
/// let value = Value::Text(String::from("hello"));
///
@@ -191,7 +250,7 @@ impl Value {
/// Returns None otherwise.
///
/// ```
- /// # use ciborium::value::Value;
+ /// # use ciborium::Value;
/// #
/// let mut value = Value::Text(String::from("hello"));
/// value.as_text_mut().unwrap().clear();
@@ -205,10 +264,29 @@ impl Value {
}
}
+ /// If the `Value` is a `String`, returns a the associated `String` data as `Ok`.
+ /// Returns `Err(Self)` otherwise.
+ ///
+ /// ```
+ /// # use ciborium::Value;
+ /// #
+ /// let value = Value::Text(String::from("hello"));
+ /// assert_eq!(value.into_text().as_deref(), Ok("hello"));
+ ///
+ /// let value = Value::Bool(true);
+ /// assert_eq!(value.into_text(), Err(Value::Bool(true)));
+ /// ```
+ pub fn into_text(self) -> Result<String, Self> {
+ match self {
+ Value::Text(s) => Ok(s),
+ other => Err(other),
+ }
+ }
+
/// Returns true if the `Value` is a `Bool`. Returns false otherwise.
///
/// ```
- /// # use ciborium::value::Value;
+ /// # use ciborium::Value;
/// #
/// let value = Value::Bool(false);
///
@@ -222,7 +300,7 @@ impl Value {
/// otherwise.
///
/// ```
- /// # use ciborium::value::Value;
+ /// # use ciborium::Value;
/// #
/// let value = Value::Bool(false);
///
@@ -235,10 +313,29 @@ impl Value {
}
}
+ /// If the `Value` is a `Bool`, returns a the associated `bool` data as `Ok`.
+ /// Returns `Err(Self)` otherwise.
+ ///
+ /// ```
+ /// # use ciborium::Value;
+ /// #
+ /// let value = Value::Bool(false);
+ /// assert_eq!(value.into_bool(), Ok(false));
+ ///
+ /// let value = Value::Float(17.);
+ /// assert_eq!(value.into_bool(), Err(Value::Float(17.)));
+ /// ```
+ pub fn into_bool(self) -> Result<bool, Self> {
+ match self {
+ Value::Bool(b) => Ok(b),
+ other => Err(other),
+ }
+ }
+
/// Returns true if the `Value` is a `Null`. Returns false otherwise.
///
/// ```
- /// # use ciborium::value::Value;
+ /// # use ciborium::Value;
/// #
/// let value = Value::Null;
///
@@ -251,7 +348,7 @@ impl Value {
/// Returns true if the `Value` is a `Tag`. Returns false otherwise.
///
/// ```
- /// # use ciborium::value::Value;
+ /// # use ciborium::Value;
/// #
/// let value = Value::Tag(61, Box::from(Value::Null));
///
@@ -265,7 +362,7 @@ impl Value {
/// Returns None otherwise.
///
/// ```
- /// # use ciborium::value::Value;
+ /// # use ciborium::Value;
/// #
/// let value = Value::Tag(61, Box::from(Value::Bytes(vec![104, 101, 108, 108, 111])));
///
@@ -284,7 +381,7 @@ impl Value {
/// to the tag `Value`. Returns None otherwise.
///
/// ```
- /// # use ciborium::value::Value;
+ /// # use ciborium::Value;
/// #
/// let mut value = Value::Tag(61, Box::from(Value::Bytes(vec![104, 101, 108, 108, 111])));
///
@@ -300,10 +397,29 @@ impl Value {
}
}
+ /// If the `Value` is a `Tag`, returns a the associated pair of `u64` and `Box<value>` data as `Ok`.
+ /// Returns `Err(Self)` otherwise.
+ ///
+ /// ```
+ /// # use ciborium::Value;
+ /// #
+ /// let value = Value::Tag(7, Box::new(Value::Float(12.)));
+ /// assert_eq!(value.into_tag(), Ok((7, Box::new(Value::Float(12.)))));
+ ///
+ /// let value = Value::Bool(true);
+ /// assert_eq!(value.into_tag(), Err(Value::Bool(true)));
+ /// ```
+ pub fn into_tag(self) -> Result<(u64, Box<Value>), Self> {
+ match self {
+ Value::Tag(tag, value) => Ok((tag, value)),
+ other => Err(other),
+ }
+ }
+
/// Returns true if the `Value` is an Array. Returns false otherwise.
///
/// ```
- /// # use ciborium::value::Value;
+ /// # use ciborium::Value;
/// #
/// let value = Value::Array(
/// vec![
@@ -322,7 +438,7 @@ impl Value {
/// otherwise.
///
/// ```
- /// # use ciborium::value::Value;
+ /// # use ciborium::Value;
/// #
/// let value = Value::Array(
/// vec![
@@ -336,7 +452,7 @@ impl Value {
/// ```
pub fn as_array(&self) -> Option<&Vec<Value>> {
match *self {
- Value::Array(ref array) => Some(&*array),
+ Value::Array(ref array) => Some(array),
_ => None,
}
}
@@ -345,7 +461,7 @@ impl Value {
/// Returns None otherwise.
///
/// ```
- /// # use ciborium::value::Value;
+ /// # use ciborium::Value;
/// #
/// let mut value = Value::Array(
/// vec![
@@ -364,10 +480,34 @@ impl Value {
}
}
+ /// If the `Value` is a `Array`, returns a the associated `Vec<Value>` data as `Ok`.
+ /// Returns `Err(Self)` otherwise.
+ ///
+ /// ```
+ /// # use ciborium::{Value, value::Integer};
+ /// #
+ /// let mut value = Value::Array(
+ /// vec![
+ /// Value::Integer(17.into()),
+ /// Value::Float(18.),
+ /// ]
+ /// );
+ /// assert_eq!(value.into_array(), Ok(vec![Value::Integer(17.into()), Value::Float(18.)]));
+ ///
+ /// let value = Value::Bool(true);
+ /// assert_eq!(value.into_array(), Err(Value::Bool(true)));
+ /// ```
+ pub fn into_array(self) -> Result<Vec<Value>, Self> {
+ match self {
+ Value::Array(vec) => Ok(vec),
+ other => Err(other),
+ }
+ }
+
/// Returns true if the `Value` is a Map. Returns false otherwise.
///
/// ```
- /// # use ciborium::value::Value;
+ /// # use ciborium::Value;
/// #
/// let value = Value::Map(
/// vec![
@@ -385,7 +525,7 @@ impl Value {
/// otherwise.
///
/// ```
- /// # use ciborium::value::Value;
+ /// # use ciborium::Value;
/// #
/// let value = Value::Map(
/// vec![
@@ -413,7 +553,7 @@ impl Value {
/// Returns None otherwise.
///
/// ```
- /// # use ciborium::value::Value;
+ /// # use ciborium::Value;
/// #
/// let mut value = Value::Map(
/// vec![
@@ -431,6 +571,29 @@ impl Value {
_ => None,
}
}
+
+ /// If the `Value` is a `Map`, returns a the associated `Vec<(Value, Value)>` data as `Ok`.
+ /// Returns `Err(Self)` otherwise.
+ ///
+ /// ```
+ /// # use ciborium::Value;
+ /// #
+ /// let mut value = Value::Map(
+ /// vec![
+ /// (Value::Text(String::from("key")), Value::Float(18.)),
+ /// ]
+ /// );
+ /// assert_eq!(value.into_map(), Ok(vec![(Value::Text(String::from("key")), Value::Float(18.))]));
+ ///
+ /// let value = Value::Bool(true);
+ /// assert_eq!(value.into_map(), Err(Value::Bool(true)));
+ /// ```
+ pub fn into_map(self) -> Result<Vec<(Value, Value)>, Self> {
+ match self {
+ Value::Map(map) => Ok(map),
+ other => Err(other),
+ }
+ }
}
macro_rules! implfrom {
@@ -483,7 +646,7 @@ impl From<u128> for Value {
}
let mut bytes = &value.to_be_bytes()[..];
- while let Some(0) = bytes.get(0) {
+ while let Some(0) = bytes.first() {
bytes = &bytes[1..];
}
@@ -504,7 +667,7 @@ impl From<i128> for Value {
};
let mut bytes = &raw.to_be_bytes()[..];
- while let Some(0) = bytes.get(0) {
+ while let Some(0) = bytes.first() {
bytes = &bytes[1..];
}
diff --git a/src/value/ser.rs b/src/value/ser.rs
index 6c739d3..a399719 100644
--- a/src/value/ser.rs
+++ b/src/value/ser.rs
@@ -270,7 +270,7 @@ impl ser::Serializer for Serializer<()> {
}
}
-impl<'a> ser::SerializeSeq for Serializer<Vec<Value>> {
+impl ser::SerializeSeq for Serializer<Vec<Value>> {
type Ok = Value;
type Error = Error;
@@ -286,7 +286,7 @@ impl<'a> ser::SerializeSeq for Serializer<Vec<Value>> {
}
}
-impl<'a> ser::SerializeTuple for Serializer<Vec<Value>> {
+impl ser::SerializeTuple for Serializer<Vec<Value>> {
type Ok = Value;
type Error = Error;
@@ -302,7 +302,7 @@ impl<'a> ser::SerializeTuple for Serializer<Vec<Value>> {
}
}
-impl<'a> ser::SerializeTupleStruct for Serializer<Vec<Value>> {
+impl ser::SerializeTupleStruct for Serializer<Vec<Value>> {
type Ok = Value;
type Error = Error;
@@ -318,7 +318,7 @@ impl<'a> ser::SerializeTupleStruct for Serializer<Vec<Value>> {
}
}
-impl<'a> ser::SerializeTupleVariant for Serializer<Named<Vec<Value>>> {
+impl ser::SerializeTupleVariant for Serializer<Named<Vec<Value>>> {
type Ok = Value;
type Error = Error;
@@ -356,7 +356,7 @@ impl<'a> ser::SerializeTupleVariant for Serializer<Named<Vec<Value>>> {
}
}
-impl<'a> ser::SerializeMap for Serializer<Map> {
+impl ser::SerializeMap for Serializer<Map> {
type Ok = Value;
type Error = Error;
@@ -381,7 +381,7 @@ impl<'a> ser::SerializeMap for Serializer<Map> {
}
}
-impl<'a> ser::SerializeStruct for Serializer<Vec<(Value, Value)>> {
+impl ser::SerializeStruct for Serializer<Vec<(Value, Value)>> {
type Ok = Value;
type Error = Error;
@@ -403,7 +403,7 @@ impl<'a> ser::SerializeStruct for Serializer<Vec<(Value, Value)>> {
}
}
-impl<'a> ser::SerializeStructVariant for Serializer<Named<Vec<(Value, Value)>>> {
+impl ser::SerializeStructVariant for Serializer<Named<Vec<(Value, Value)>>> {
type Ok = Value;
type Error = Error;
diff --git a/tests/canonical.rs b/tests/canonical.rs
new file mode 100644
index 0000000..d4aa33c
--- /dev/null
+++ b/tests/canonical.rs
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier: Apache-2.0
+
+extern crate std;
+
+use ciborium::cbor;
+use ciborium::tag::Required;
+use ciborium::value::CanonicalValue;
+use rand::prelude::*;
+use std::collections::BTreeMap;
+
+macro_rules! cval {
+ ($x:expr) => {
+ CanonicalValue::from(val!($x))
+ };
+}
+
+macro_rules! val {
+ ($x:expr) => {
+ cbor!($x).unwrap()
+ };
+}
+
+#[test]
+fn rfc8949_example() {
+ let mut array: Vec<CanonicalValue> = vec![
+ cval!(10),
+ cval!(-1),
+ cval!(false),
+ cval!(100),
+ cval!("z"),
+ cval!([-1]),
+ cval!("aa"),
+ cval!([100]),
+ ];
+ let golden = array.clone();
+
+ // Shuffle the array.
+ array.shuffle(&mut rand::thread_rng());
+
+ array.sort();
+
+ assert_eq!(array, golden);
+}
+
+#[test]
+fn map() {
+ let mut map = BTreeMap::new();
+ map.insert(cval!(false), val!(2));
+ map.insert(cval!([-1]), val!(5));
+ map.insert(cval!(-1), val!(1));
+ map.insert(cval!(10), val!(0));
+ map.insert(cval!(100), val!(3));
+ map.insert(cval!([100]), val!(7));
+ map.insert(cval!("z"), val!(4));
+ map.insert(cval!("aa"), val!(6));
+
+ let mut bytes1 = Vec::new();
+ ciborium::ser::into_writer(&map, &mut bytes1).unwrap();
+
+ assert_eq!(
+ hex::encode(&bytes1),
+ "a80a002001f402186403617a048120056261610681186407"
+ );
+}
+
+#[test]
+fn negative_numbers() {
+ let mut array: Vec<CanonicalValue> = vec![
+ cval!(10),
+ cval!(-1),
+ cval!(-2),
+ cval!(-3),
+ cval!(-4),
+ cval!(false),
+ cval!(100),
+ cval!(-100),
+ cval!(-200),
+ cval!("z"),
+ cval!([-1]),
+ cval!(-300),
+ cval!("aa"),
+ cval!([100]),
+ ];
+ let golden = array.clone();
+
+ // Shuffle the array.
+ array.shuffle(&mut rand::thread_rng());
+
+ array.sort();
+
+ assert_eq!(array, golden);
+}
+
+#[test]
+fn tagged_option() {
+ let mut opt = Some(Required::<u64, 0xff>(2u32.into()));
+
+ let mut bytes = Vec::new();
+ ciborium::ser::into_writer(&opt, &mut bytes).unwrap();
+
+ let output = ciborium::de::from_reader(&bytes[..]).unwrap();
+ assert_eq!(opt, output);
+
+ opt = None;
+
+ let mut bytes = Vec::new();
+ ciborium::ser::into_writer(&opt, &mut bytes).unwrap();
+
+ let output = ciborium::de::from_reader(&bytes[..]).unwrap();
+ assert_eq!(opt, output);
+}
diff --git a/tests/codec.rs b/tests/codec.rs
index d1f6026..d5df9a7 100644
--- a/tests/codec.rs
+++ b/tests/codec.rs
@@ -10,7 +10,7 @@ use ciborium::value::Value;
use ciborium::{cbor, de::from_reader, ser::into_writer};
use rstest::rstest;
-use serde::{Deserialize, Serialize};
+use serde::{de::DeserializeOwned, Deserialize, Serialize};
macro_rules! val {
($x:expr) => {
@@ -275,7 +275,7 @@ macro_rules! map {
case(Enum::Tuple(56, 67), cbor!({"Tuple" => [56, 67]}).unwrap(), "a1655475706c658218381843", false, same), // Not In RFC
case(Enum::Struct { first: 78, second: 89 }, cbor!({ "Struct" => { "first" => 78, "second" => 89 }}).unwrap(), "a166537472756374a2656669727374184e667365636f6e641859", false, same), // Not In RFC
)]
-fn codec<'de, T: Serialize + Clone, V: Debug + PartialEq + Deserialize<'de>, F: Fn(T) -> V>(
+fn codec<'de, T: Serialize + Clone, V: Debug + PartialEq + DeserializeOwned, F: Fn(T) -> V>(
input: T,
value: Value,
bytes: &str,
@@ -397,3 +397,37 @@ enum Enum {
Tuple(u8, u16),
Struct { first: u8, second: u16 },
}
+
+#[rstest(
+ input,
+ case(vec![]),
+ case(vec![0u8, 1, 2, 3]),
+)]
+fn byte_vec_serde_bytes_compatibility(input: Vec<u8>) {
+ use serde_bytes::ByteBuf;
+
+ let mut buf = Vec::new();
+ into_writer(&input, &mut buf).unwrap();
+ let bytes: ByteBuf = from_reader(&buf[..]).unwrap();
+ assert_eq!(input, bytes.to_vec());
+
+ let mut buf = Vec::new();
+ into_writer(&ByteBuf::from(input.clone()), &mut buf).unwrap();
+ let bytes: Vec<u8> = from_reader(&buf[..]).unwrap();
+ assert_eq!(input, bytes);
+}
+
+#[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
+struct Foo {
+ bar: u8,
+}
+
+#[rstest(input, expected,
+ case("a163626172182a", Foo { bar: 42 }),
+ case("a143626172182a", Foo { bar: 42 }),
+)]
+fn handle_struct_field_names(input: &str, expected: Foo) {
+ let buf = hex::decode(input).unwrap();
+ let read = from_reader(&buf[..]).unwrap();
+ assert_eq!(expected, read);
+}
diff --git a/tests/error.rs b/tests/error.rs
index eb1711f..13bcd5a 100644
--- a/tests/error.rs
+++ b/tests/error.rs
@@ -1,6 +1,10 @@
// SPDX-License-Identifier: Apache-2.0
-use ciborium::{de::from_reader, de::Error, value::Value};
+use ciborium::{
+ de::{from_reader, Error},
+ ser::into_writer,
+ value::Value,
+};
use rstest::rstest;
#[rstest(bytes, error,
@@ -45,3 +49,11 @@ fn test(bytes: &str, error: Error<std::io::Error>) {
assert_eq!(correct, actual);
}
+
+#[test]
+fn test_long_utf8_deserialization() {
+ let s = (0..2000).map(|_| 'ボ').collect::<String>();
+ let mut v = Vec::new();
+ into_writer(&s, &mut v).unwrap();
+ let _: String = from_reader(&*v).unwrap();
+}
diff --git a/tests/recursion.rs b/tests/recursion.rs
index a340b2d..cda1ce2 100644
--- a/tests/recursion.rs
+++ b/tests/recursion.rs
@@ -7,7 +7,7 @@
//! test each of these types here to ensure there is no stack overflow.
use ciborium::{
- de::{from_reader, Error},
+ de::{from_reader, from_reader_with_recursion_limit, Error},
value::Value,
};
@@ -46,3 +46,47 @@ fn text() {
e => panic!("incorrect error: {:?}", e),
}
}
+
+#[test]
+fn array_limit() {
+ let bytes = [0x9f; 128 * 1024];
+ for limit in 16..256 {
+ match from_reader_with_recursion_limit::<Value, _>(&bytes[..], limit).unwrap_err() {
+ Error::RecursionLimitExceeded => (),
+ e => panic!("incorrect error with limit {}: {:?}", limit, e),
+ }
+ // Data that is nested beyond the limit should fail with `RecursionLimitExceeded`
+ match from_reader_with_recursion_limit::<Value, _>(&bytes[..limit + 1], limit).unwrap_err()
+ {
+ Error::RecursionLimitExceeded => (),
+ e => panic!("incorrect error with limit {}: {:?}", limit, e),
+ }
+ // Data that is nested within the limit fails with a different error.
+ match from_reader_with_recursion_limit::<Value, _>(&bytes[..limit], limit).unwrap_err() {
+ Error::Io(..) => (),
+ e => panic!("incorrect error with limit {}: {:?}", limit, e),
+ }
+ }
+}
+
+#[test]
+fn map_limit() {
+ let bytes = [0xbf; 128 * 1024];
+ for limit in 16..256 {
+ match from_reader_with_recursion_limit::<Value, _>(&bytes[..], limit).unwrap_err() {
+ Error::RecursionLimitExceeded => (),
+ e => panic!("incorrect error with limit {}: {:?}", limit, e),
+ }
+ // Data that is nested beyond the limit should fail with `RecursionLimitExceeded`
+ match from_reader_with_recursion_limit::<Value, _>(&bytes[..limit + 1], limit).unwrap_err()
+ {
+ Error::RecursionLimitExceeded => (),
+ e => panic!("incorrect error with limit {}: {:?}", limit, e),
+ }
+ // Data that is nested within the limit fails with a different error.
+ match from_reader_with_recursion_limit::<Value, _>(&bytes[..limit], limit).unwrap_err() {
+ Error::Io(..) => (),
+ e => panic!("incorrect error with limit {}: {:?}", limit, e),
+ }
+ }
+}
diff --git a/tests/tag.rs b/tests/tag.rs
index 04b6dad..c19ee00 100644
--- a/tests/tag.rs
+++ b/tests/tag.rs
@@ -4,7 +4,7 @@ extern crate alloc;
use ciborium::{de::from_reader, ser::into_writer, tag::*, value::Value};
use rstest::rstest;
-use serde::{Deserialize, Serialize};
+use serde::{de::DeserializeOwned, Serialize};
use core::fmt::Debug;
@@ -20,7 +20,7 @@ use core::fmt::Debug;
case(Accepted::<_, 6>(true), "c7f5", Value::Tag(7, Value::Bool(true).into()), false, false),
case(Accepted::<_, 6>(true), "f5", Value::Bool(true), false, true),
)]
-fn test<'de, T: Serialize + Deserialize<'de> + Debug + Eq>(
+fn test<T: Serialize + DeserializeOwned + Debug + Eq>(
item: T,
bytes: &str,
value: Value,