aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAditya Belsare <asbel@google.com>2022-02-01 15:55:22 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2022-02-01 15:55:22 +0000
commit94192dfc73b6c71968d390a0fbb756475c5cac3d (patch)
tree600837bce299d306e8b73ff9347d0e6e48807c8d
parentc8a49e9063f719ecad43704daf5164c5a9d9e5f5 (diff)
parent0100a37967b77ae3398f87d0658e0bd69cd4da8d (diff)
downloadciborium-94192dfc73b6c71968d390a0fbb756475c5cac3d.tar.gz
Import ciborium crate. am: 48dfe2734a am: fb776aee0f am: c87a05b448 am: 0100a37967
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/ciborium/+/1966135 Change-Id: Ie96337402969808f3478dfa7d1222c67737726d7
-rw-r--r--Android.bp23
-rw-r--r--Cargo.toml61
-rw-r--r--Cargo.toml.orig35
-rw-r--r--LICENSE201
-rw-r--r--METADATA18
-rw-r--r--MODULE_LICENSE_APACHE20
-rw-r--r--OWNERS5
-rw-r--r--README.md92
-rw-r--r--src/de/error.rs73
-rw-r--r--src/de/mod.rs762
-rw-r--r--src/lib.rs212
-rw-r--r--src/ser/error.rs42
-rw-r--r--src/ser/mod.rs500
-rw-r--r--src/tag.rs545
-rw-r--r--src/value/de.rs616
-rw-r--r--src/value/error.rs32
-rw-r--r--src/value/integer.rs84
-rw-r--r--src/value/mod.rs522
-rw-r--r--src/value/ser.rs434
-rw-r--r--tests/codec.rs399
-rw-r--r--tests/error.rs47
-rw-r--r--tests/fuzz.rs64
-rw-r--r--tests/macro.rs358
-rw-r--r--tests/no_std.rs39
-rw-r--r--tests/recursion.rs48
-rw-r--r--tests/tag.rs57
26 files changed, 5269 insertions, 0 deletions
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..061cb62
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,23 @@
+// This file is generated by cargo2android.py --run --device --dependencies.
+// Do not modify this file as changes will be overridden on upgrade.
+
+
+
+rust_library {
+ name: "libciborium",
+ host_supported: true,
+ crate_name: "ciborium",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.2.0",
+ srcs: ["src/lib.rs"],
+ edition: "2021",
+ features: [
+ "default",
+ "std",
+ ],
+ rustlibs: [
+ "libciborium_io",
+ "libciborium_ll",
+ "libserde",
+ ],
+}
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..ae4f377
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,61 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+name = "ciborium"
+version = "0.2.0"
+authors = ["Nathaniel McCallum <npmccallum@profian.com>"]
+description = "serde implementation of CBOR using ciborium-basic"
+homepage = "https://github.com/enarx/ciborium"
+readme = "README.md"
+keywords = ["cbor", "serde"]
+categories = ["data-structures", "embedded", "encoding", "no-std", "parsing"]
+license = "Apache-2.0"
+repository = "https://github.com/enarx/ciborium"
+[dependencies.ciborium-io]
+version = "0.2.0"
+features = ["alloc"]
+
+[dependencies.ciborium-ll]
+version = "0.2.0"
+
+[dependencies.serde]
+version = "1.0"
+features = ["alloc", "derive"]
+default-features = false
+[dev-dependencies.hex]
+version = "0.4"
+
+[dev-dependencies.rand]
+version = "0.8"
+
+[dev-dependencies.rstest]
+version = "0.11"
+
+[dev-dependencies.serde_bytes]
+version = "0.11"
+
+[features]
+default = ["std"]
+std = ["ciborium-io/std", "serde/std"]
+[badges.github]
+repository = "enarx/ciborium"
+workflow = "test"
+
+[badges.is-it-maintained-issue-resolution]
+repository = "enarx/ciborium"
+
+[badges.is-it-maintained-open-issues]
+repository = "enarx/ciborium"
+
+[badges.maintenance]
+status = "actively-developed"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644
index 0000000..9e575ab
--- /dev/null
+++ b/Cargo.toml.orig
@@ -0,0 +1,35 @@
+[package]
+name = "ciborium"
+version = "0.2.0"
+authors = ["Nathaniel McCallum <npmccallum@profian.com>"]
+license = "Apache-2.0"
+edition = "2021"
+homepage = "https://github.com/enarx/ciborium"
+repository = "https://github.com/enarx/ciborium"
+description = "serde implementation of CBOR using ciborium-basic"
+readme = "README.md"
+keywords = ["cbor", "serde"]
+categories = ["data-structures", "embedded", "encoding", "no-std", "parsing"]
+
+[badges]
+# See https://doc.rust-lang.org/cargo/reference/manifest.html#the-badges-section
+github = { repository = "enarx/ciborium", workflow = "test" }
+#github = { repository = "enarx/ciborium", workflow = "lint" }
+maintenance = { status = "actively-developed" }
+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"] }
+serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] }
+
+[dev-dependencies]
+serde_bytes = "0.11"
+rstest = "0.11"
+rand = "0.8"
+hex = "0.4"
+
+[features]
+default = ["std"]
+std = ["ciborium-io/std", "serde/std"]
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..f59b026
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,18 @@
+name: "ciborium"
+description:
+ "serde implementation of CBOR using ciborium-basic"
+
+third_party {
+ url {
+ type: HOMEPAGE
+ value: "https://crates.io/crates/ciborium"
+ }
+ url {
+ type: ARCHIVE
+ value: "https://static.crates.io/crates/ciborium/ciborium-0.2.0.crate"
+ }
+ version: "0.2.0"
+ last_upgrade_date { year: 2022 month: 1 day: 17 }
+ license_type: NOTICE
+}
+
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..598ca58
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,5 @@
+include platform/prebuilts/rust:master:/OWNERS
+# Android Hardware Security
+asbel@google.com
+jbires@google.com
+paulcrowley@google.com
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2e35ff1
--- /dev/null
+++ b/README.md
@@ -0,0 +1,92 @@
+[![Workflow Status](https://github.com/enarx/ciborium/workflows/test/badge.svg)](https://github.com/enarx/ciborium/actions?query=workflow%3A%22test%22)
+[![Average time to resolve an issue](https://isitmaintained.com/badge/resolution/enarx/ciborium.svg)](https://isitmaintained.com/project/enarx/ciborium "Average time to resolve an issue")
+[![Percentage of issues still open](https://isitmaintained.com/badge/open/enarx/ciborium.svg)](https://isitmaintained.com/project/enarx/ciborium "Percentage of issues still open")
+![Maintenance](https://img.shields.io/badge/maintenance-activly--developed-brightgreen.svg)
+
+# ciborium
+
+Welcome to Ciborium!
+
+Ciborium contains CBOR serialization and deserialization implementations for serde.
+
+## Quick Start
+
+You're probably looking for [`de::from_reader()`](crate::de::from_reader)
+and [`ser::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).
+
+## Design Decisions
+
+### Always Serialize Numeric Values to the Smallest Size
+
+Although the CBOR specification has differing numeric widths, this is only
+a form of compression on the wire and is not intended to directly
+represent an "integer width" or "float width." Therefore, ciborium always
+serializes numbers to the smallest possible lossless encoding. For example,
+we serialize `1u128` as a single byte (`01`). Likewise, we will also freely
+decode that single byte into a `u128`.
+
+While there is some minor performance cost for this, there are several
+reasons for this choice. First, the specification seems to imply it by
+using a separate bit for the sign. Second, the specification requires
+that implementations handle leading zeroes; a liberal reading of which
+implies a requirement for lossless coercion. Third, dynamic languages like
+Python have no notion of "integer width," making this is a practical
+choice for maximizing wire compatibility with those languages.
+
+This coercion is **always** lossless. For floats, this implies that we
+only coerce to a smaller size if coercion back to the original size has
+the same raw bits as the original.
+
+### Compatibility with Other Implementations
+
+The ciborium project follows the [Robustness Principle](https://en.wikipedia.org/wiki/Robustness_principle).
+Therefore, we aim to be liberal in what we accept. This implies that we
+aim to be wire-compatible with other implementations in decoding, but
+not necessarily encoding.
+
+One notable example of this is that `serde_cbor` uses fixed-width encoding
+of numbers and doesn't losslessly coerce. This implies that `ciborium` will
+successfully decode `serde_cbor` encodings, but the opposite may not be the
+case.
+
+### Representing Map as a Sequence of Values
+
+Other serde parsers have generally taken the route of using `BTreeMap` or
+`HashMap` to implement their encoding's underlying `Map` type. This crate
+chooses to represent the `Map` type using `Vec<(Value, Value)>` instead.
+
+This decision was made because this type preserves the order of the pairs
+on the wire. Further, for those that need the properties of `BTreeMap` or
+`HashMap`, you can simply `collect()` the values into the respective type.
+This provides maximum flexibility.
+
+### Low-level Library
+
+The ciborium crate has the beginnings of a low-level library in the
+(private) `basic` module. We may extend this to be more robust and expose
+it for application consumption once we have it in a good state. If you'd
+like to collaborate with us on that, please contact us. Alternatively,
+we might fork this code into a separate crate with no serde dependency.
+
+### Internal Types
+
+The ciborium crate contains a number of internal types that implement
+useful serde traits. While these are not currently exposed, we might
+choose to expose them in the future if there is demand. Generally, this
+crate takes a conservative approach to exposing APIs to avoid breakage.
+
+### Packed Encoding?
+
+Packed encoding uses numerical offsets to represent structure field names
+and enum variant names. This can save significant space on the wire.
+
+While the authors of this crate like packed encoding, it should generally
+be avoided because it can be fragile as it exposes invariants of your Rust
+code to remote actors. We might consider adding this in the future. If you
+are interested in this, please contact us.
+
+License: Apache-2.0
diff --git a/src/de/error.rs b/src/de/error.rs
new file mode 100644
index 0000000..3334f81
--- /dev/null
+++ b/src/de/error.rs
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use alloc::string::{String, ToString};
+use core::fmt::{Debug, Display, Formatter, Result};
+
+use serde::de::{Error as DeError, StdError};
+
+/// An error occurred during deserialization
+#[derive(Debug)]
+pub enum Error<T> {
+ /// An error occurred while reading bytes
+ ///
+ /// Contains the underlying error reaturned while reading.
+ Io(T),
+
+ /// An error occurred while parsing bytes
+ ///
+ /// Contains the offset into the stream where the syntax error occurred.
+ Syntax(usize),
+
+ /// An error occurred while processing a parsed value
+ ///
+ /// Contains a description of the error that occurred and (optionally)
+ /// the offset into the stream indicating the start of the item being
+ /// processed when the error occurred.
+ Semantic(Option<usize>, String),
+
+ /// The input caused serde to recurse too much
+ ///
+ /// This error prevents a stack overflow.
+ RecursionLimitExceeded,
+}
+
+impl<T> Error<T> {
+ /// A helper method for composing a semantic error
+ #[inline]
+ pub fn semantic(offset: impl Into<Option<usize>>, msg: impl Into<String>) -> Self {
+ Self::Semantic(offset.into(), msg.into())
+ }
+}
+
+impl<T> From<T> for Error<T> {
+ #[inline]
+ fn from(value: T) -> Self {
+ Error::Io(value)
+ }
+}
+
+impl<T> From<ciborium_ll::Error<T>> for Error<T> {
+ #[inline]
+ fn from(value: ciborium_ll::Error<T>) -> Self {
+ match value {
+ ciborium_ll::Error::Io(x) => Self::Io(x),
+ ciborium_ll::Error::Syntax(x) => Self::Syntax(x),
+ }
+ }
+}
+
+impl<T: Debug> Display for Error<T> {
+ #[inline]
+ fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+ write!(f, "{:?}", self)
+ }
+}
+
+impl<T: Debug> StdError for Error<T> {}
+
+impl<T: Debug> DeError for Error<T> {
+ #[inline]
+ fn custom<U: Display>(msg: U) -> Self {
+ Self::Semantic(None, msg.to_string())
+ }
+}
diff --git a/src/de/mod.rs b/src/de/mod.rs
new file mode 100644
index 0000000..63e5109
--- /dev/null
+++ b/src/de/mod.rs
@@ -0,0 +1,762 @@
+// SPDX-License-Identifier: Apache-2.0
+
+//! Serde deserialization support for CBOR
+
+mod error;
+
+pub use error::Error;
+
+use alloc::{string::String, vec::Vec};
+
+use ciborium_io::Read;
+use ciborium_ll::*;
+use serde::{de, de::Deserializer as _, forward_to_deserialize_any};
+
+trait Expected<E: de::Error> {
+ fn expected(self, kind: &'static str) -> E;
+}
+
+impl<E: de::Error> Expected<E> for Header {
+ #[inline]
+ fn expected(self, kind: &'static str) -> E {
+ de::Error::invalid_type(
+ match self {
+ Header::Positive(x) => de::Unexpected::Unsigned(x),
+ Header::Negative(x) => de::Unexpected::Signed(x as i64 ^ !0),
+ Header::Bytes(..) => de::Unexpected::Other("bytes"),
+ Header::Text(..) => de::Unexpected::Other("string"),
+
+ Header::Array(..) => de::Unexpected::Seq,
+ Header::Map(..) => de::Unexpected::Map,
+
+ Header::Tag(..) => de::Unexpected::Other("tag"),
+
+ Header::Simple(simple::FALSE) => de::Unexpected::Bool(false),
+ Header::Simple(simple::TRUE) => de::Unexpected::Bool(true),
+ Header::Simple(simple::NULL) => de::Unexpected::Other("null"),
+ Header::Simple(simple::UNDEFINED) => de::Unexpected::Other("undefined"),
+ Header::Simple(..) => de::Unexpected::Other("simple"),
+
+ Header::Float(x) => de::Unexpected::Float(x),
+ Header::Break => de::Unexpected::Other("break"),
+ },
+ &kind,
+ )
+ }
+}
+
+struct Deserializer<'b, R: Read> {
+ decoder: Decoder<R>,
+ scratch: &'b mut [u8],
+ recurse: usize,
+}
+
+impl<'de, 'a, 'b, R: Read> Deserializer<'b, R>
+where
+ R::Error: core::fmt::Debug,
+{
+ #[inline]
+ fn recurse<V, F: FnOnce(&mut Self) -> Result<V, Error<R::Error>>>(
+ &mut self,
+ func: F,
+ ) -> Result<V, Error<R::Error>> {
+ if self.recurse == 0 {
+ return Err(Error::RecursionLimitExceeded);
+ }
+
+ self.recurse -= 1;
+ let result = func(self);
+ self.recurse += 1;
+ result
+ }
+
+ #[inline]
+ fn integer(&mut self, mut header: Option<Header>) -> Result<(bool, u128), Error<R::Error>> {
+ loop {
+ let header = match header.take() {
+ Some(h) => h,
+ None => self.decoder.pull()?,
+ };
+
+ let neg = match header {
+ Header::Positive(x) => return Ok((false, x.into())),
+ Header::Negative(x) => return Ok((true, x.into())),
+ Header::Tag(tag::BIGPOS) => false,
+ Header::Tag(tag::BIGNEG) => true,
+ Header::Tag(..) => continue,
+ header => return Err(header.expected("integer")),
+ };
+
+ let mut buffer = [0u8; 16];
+ let mut value = [0u8; 16];
+ let mut index = 0usize;
+
+ return match self.decoder.pull()? {
+ Header::Bytes(len) => {
+ let mut segments = self.decoder.bytes(len);
+ while let Some(mut segment) = segments.pull()? {
+ while let Some(chunk) = segment.pull(&mut buffer)? {
+ for b in chunk {
+ match index {
+ 16 => return Err(de::Error::custom("bigint too large")),
+ 0 if *b == 0 => continue, // Skip leading zeros
+ _ => value[index] = *b,
+ }
+
+ index += 1;
+ }
+ }
+ }
+
+ value[..index].reverse();
+ Ok((neg, u128::from_le_bytes(value)))
+ }
+
+ h => Err(h.expected("bytes")),
+ };
+ }
+ }
+}
+
+impl<'de, 'a, 'b, R: Read> de::Deserializer<'de> for &'a mut Deserializer<'b, R>
+where
+ R::Error: core::fmt::Debug,
+{
+ type Error = Error<R::Error>;
+
+ #[inline]
+ fn deserialize_any<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ let header = self.decoder.pull()?;
+ self.decoder.push(header);
+
+ match header {
+ Header::Positive(..) => self.deserialize_u64(visitor),
+ Header::Negative(x) => match i64::try_from(x) {
+ Ok(..) => self.deserialize_i64(visitor),
+ Err(..) => self.deserialize_i128(visitor),
+ },
+
+ Header::Bytes(len) => match len {
+ Some(len) if len <= self.scratch.len() => self.deserialize_bytes(visitor),
+ _ => self.deserialize_byte_buf(visitor),
+ },
+
+ Header::Text(len) => match len {
+ Some(len) if len <= self.scratch.len() => self.deserialize_str(visitor),
+ _ => self.deserialize_string(visitor),
+ },
+
+ Header::Array(..) => self.deserialize_seq(visitor),
+ Header::Map(..) => self.deserialize_map(visitor),
+
+ Header::Tag(tag) => {
+ let _: Header = self.decoder.pull()?;
+
+ // Peek at the next item.
+ let header = self.decoder.pull()?;
+ self.decoder.push(header);
+
+ // If it is bytes, capture the length.
+ let len = match header {
+ Header::Bytes(x) => x,
+ _ => None,
+ };
+
+ match (tag, len) {
+ (tag::BIGPOS, Some(len)) | (tag::BIGNEG, Some(len)) if len <= 16 => {
+ let result = match self.integer(Some(Header::Tag(tag)))? {
+ (false, raw) => return visitor.visit_u128(raw),
+ (true, raw) => i128::try_from(raw).map(|x| x ^ !0),
+ };
+
+ match result {
+ Ok(x) => visitor.visit_i128(x),
+ Err(..) => Err(de::Error::custom("integer too large")),
+ }
+ }
+
+ _ => self.recurse(|me| {
+ let access = crate::tag::TagAccess::new(me, Some(tag));
+ visitor.visit_enum(access)
+ }),
+ }
+ }
+
+ Header::Float(..) => self.deserialize_f64(visitor),
+
+ Header::Simple(simple::FALSE) => self.deserialize_bool(visitor),
+ Header::Simple(simple::TRUE) => self.deserialize_bool(visitor),
+ Header::Simple(simple::NULL) => self.deserialize_option(visitor),
+ Header::Simple(simple::UNDEFINED) => self.deserialize_option(visitor),
+ h @ Header::Simple(..) => Err(h.expected("known simple value")),
+
+ h @ Header::Break => Err(h.expected("non-break")),
+ }
+ }
+
+ #[inline]
+ fn deserialize_bool<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ loop {
+ let offset = self.decoder.offset();
+
+ return match self.decoder.pull()? {
+ Header::Tag(..) => continue,
+ Header::Simple(simple::FALSE) => visitor.visit_bool(false),
+ Header::Simple(simple::TRUE) => visitor.visit_bool(true),
+ _ => Err(Error::semantic(offset, "expected bool")),
+ };
+ }
+ }
+
+ #[inline]
+ fn deserialize_f32<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ self.deserialize_f64(visitor)
+ }
+
+ #[inline]
+ fn deserialize_f64<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ loop {
+ return match self.decoder.pull()? {
+ Header::Tag(..) => continue,
+ Header::Float(x) => visitor.visit_f64(x),
+ h => Err(h.expected("float")),
+ };
+ }
+ }
+
+ fn deserialize_i8<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ self.deserialize_i64(visitor)
+ }
+
+ fn deserialize_i16<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ self.deserialize_i64(visitor)
+ }
+
+ fn deserialize_i32<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ self.deserialize_i64(visitor)
+ }
+
+ fn deserialize_i64<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ let result = match self.integer(None)? {
+ (false, raw) => i64::try_from(raw),
+ (true, raw) => i64::try_from(raw).map(|x| x ^ !0),
+ };
+
+ match result {
+ Ok(x) => visitor.visit_i64(x),
+ Err(..) => Err(de::Error::custom("integer too large")),
+ }
+ }
+
+ fn deserialize_i128<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ let result = match self.integer(None)? {
+ (false, raw) => i128::try_from(raw),
+ (true, raw) => i128::try_from(raw).map(|x| x ^ !0),
+ };
+
+ match result {
+ Ok(x) => visitor.visit_i128(x),
+ Err(..) => Err(de::Error::custom("integer too large")),
+ }
+ }
+
+ fn deserialize_u8<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ self.deserialize_u64(visitor)
+ }
+
+ fn deserialize_u16<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ self.deserialize_u64(visitor)
+ }
+
+ fn deserialize_u32<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ self.deserialize_u64(visitor)
+ }
+
+ fn deserialize_u64<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ let result = match self.integer(None)? {
+ (false, raw) => u64::try_from(raw),
+ (true, ..) => return Err(de::Error::custom("unexpected negative integer")),
+ };
+
+ match result {
+ Ok(x) => visitor.visit_u64(x),
+ Err(..) => Err(de::Error::custom("integer too large")),
+ }
+ }
+
+ fn deserialize_u128<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ match self.integer(None)? {
+ (false, raw) => visitor.visit_u128(raw),
+ (true, ..) => Err(de::Error::custom("unexpected negative integer")),
+ }
+ }
+
+ fn deserialize_char<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ loop {
+ let offset = self.decoder.offset();
+ let header = self.decoder.pull()?;
+
+ return match header {
+ Header::Tag(..) => continue,
+
+ Header::Text(Some(len)) if len <= 4 => {
+ let mut buf = [0u8; 4];
+ self.decoder.read_exact(&mut buf[..len])?;
+
+ match core::str::from_utf8(&buf[..len]) {
+ Ok(s) => match s.chars().count() {
+ 1 => visitor.visit_char(s.chars().next().unwrap()),
+ _ => Err(header.expected("char")),
+ },
+ Err(..) => Err(Error::Syntax(offset)),
+ }
+ }
+
+ _ => Err(header.expected("char")),
+ };
+ }
+ }
+
+ fn deserialize_str<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ 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 => Err(header.expected("str")),
+ };
+ }
+ }
+
+ fn deserialize_string<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ loop {
+ return match self.decoder.pull()? {
+ Header::Tag(..) => continue,
+
+ Header::Text(len) => {
+ let mut buffer = String::new();
+
+ let mut segments = self.decoder.text(len);
+ while let Some(mut segment) = segments.pull()? {
+ while let Some(chunk) = segment.pull(&mut self.scratch)? {
+ buffer.push_str(chunk);
+ }
+ }
+
+ visitor.visit_string(buffer)
+ }
+
+ header => Err(header.expected("string")),
+ };
+ }
+ }
+
+ fn deserialize_bytes<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ loop {
+ return match self.decoder.pull()? {
+ Header::Tag(..) => continue,
+
+ 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("bytes")),
+ };
+ }
+ }
+
+ fn deserialize_byte_buf<V: de::Visitor<'de>>(
+ self,
+ visitor: V,
+ ) -> Result<V::Value, Self::Error> {
+ loop {
+ return match self.decoder.pull()? {
+ Header::Tag(..) => continue,
+
+ 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(&mut self.scratch)? {
+ buffer.extend_from_slice(chunk);
+ }
+ }
+
+ visitor.visit_byte_buf(buffer)
+ }
+
+ header => Err(header.expected("expected byte buffer")),
+ };
+ }
+ }
+
+ fn deserialize_seq<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ loop {
+ return match self.decoder.pull()? {
+ Header::Tag(..) => continue,
+
+ Header::Array(len) => self.recurse(|me| {
+ let access = Access(me, len);
+ visitor.visit_seq(access)
+ }),
+
+ header => Err(header.expected("array")),
+ };
+ }
+ }
+
+ fn deserialize_map<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ loop {
+ return match self.decoder.pull()? {
+ Header::Tag(..) => continue,
+
+ Header::Map(len) => self.recurse(|me| {
+ let access = Access(me, len);
+ visitor.visit_map(access)
+ }),
+
+ header => Err(header.expected("map")),
+ };
+ }
+ }
+
+ fn deserialize_struct<V: de::Visitor<'de>>(
+ self,
+ _name: &'static str,
+ _fields: &'static [&'static str],
+ visitor: V,
+ ) -> Result<V::Value, Self::Error> {
+ self.deserialize_map(visitor)
+ }
+
+ fn deserialize_tuple<V: de::Visitor<'de>>(
+ self,
+ _len: usize,
+ visitor: V,
+ ) -> Result<V::Value, Self::Error> {
+ self.deserialize_seq(visitor)
+ }
+
+ fn deserialize_tuple_struct<V: de::Visitor<'de>>(
+ self,
+ _name: &'static str,
+ _len: usize,
+ visitor: V,
+ ) -> Result<V::Value, Self::Error> {
+ self.deserialize_seq(visitor)
+ }
+
+ fn deserialize_identifier<V: de::Visitor<'de>>(
+ self,
+ visitor: V,
+ ) -> Result<V::Value, Self::Error> {
+ self.deserialize_str(visitor)
+ }
+
+ fn deserialize_ignored_any<V: de::Visitor<'de>>(
+ self,
+ visitor: V,
+ ) -> Result<V::Value, Self::Error> {
+ self.deserialize_any(visitor)
+ }
+
+ #[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)
+ }
+ };
+ }
+ }
+
+ #[inline]
+ fn deserialize_unit<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ loop {
+ return match self.decoder.pull()? {
+ Header::Simple(simple::UNDEFINED) => visitor.visit_unit(),
+ Header::Simple(simple::NULL) => visitor.visit_unit(),
+ Header::Tag(..) => continue,
+ header => Err(header.expected("unit")),
+ };
+ }
+ }
+
+ #[inline]
+ fn deserialize_unit_struct<V: de::Visitor<'de>>(
+ self,
+ _name: &'static str,
+ visitor: V,
+ ) -> Result<V::Value, Self::Error> {
+ self.deserialize_unit(visitor)
+ }
+
+ #[inline]
+ fn deserialize_newtype_struct<V: de::Visitor<'de>>(
+ self,
+ _name: &'static str,
+ visitor: V,
+ ) -> Result<V::Value, Self::Error> {
+ visitor.visit_newtype_struct(self)
+ }
+
+ #[inline]
+ fn deserialize_enum<V: de::Visitor<'de>>(
+ self,
+ name: &'static str,
+ _variants: &'static [&'static str],
+ visitor: V,
+ ) -> Result<V::Value, Self::Error> {
+ if name == "@@TAG@@" {
+ let tag = match self.decoder.pull()? {
+ Header::Tag(x) => Some(x),
+ header => {
+ self.decoder.push(header);
+ None
+ }
+ };
+
+ return self.recurse(|me| {
+ let access = crate::tag::TagAccess::new(me, tag);
+ visitor.visit_enum(access)
+ });
+ }
+
+ loop {
+ match self.decoder.pull()? {
+ Header::Tag(..) => continue,
+ Header::Map(Some(1)) => (),
+ header @ Header::Text(..) => self.decoder.push(header),
+ header => return Err(header.expected("enum")),
+ }
+
+ return self.recurse(|me| {
+ let access = Access(me, Some(0));
+ visitor.visit_enum(access)
+ });
+ }
+ }
+
+ #[inline]
+ fn is_human_readable(&self) -> bool {
+ false
+ }
+}
+
+struct Access<'a, 'b, R: Read>(&'a mut Deserializer<'b, R>, Option<usize>);
+
+impl<'de, 'a, 'b, R: Read> de::SeqAccess<'de> for Access<'a, 'b, 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> {
+ match self.1 {
+ Some(0) => return Ok(None),
+ Some(x) => self.1 = Some(x - 1),
+ None => match self.0.decoder.pull()? {
+ Header::Break => return Ok(None),
+ header => self.0.decoder.push(header),
+ },
+ }
+
+ seed.deserialize(&mut *self.0).map(Some)
+ }
+
+ #[inline]
+ fn size_hint(&self) -> Option<usize> {
+ self.1
+ }
+}
+
+impl<'de, 'a, 'b, R: Read> de::MapAccess<'de> for Access<'a, 'b, R>
+where
+ R::Error: core::fmt::Debug,
+{
+ type Error = Error<R::Error>;
+
+ #[inline]
+ fn next_key_seed<K: de::DeserializeSeed<'de>>(
+ &mut self,
+ seed: K,
+ ) -> Result<Option<K::Value>, Self::Error> {
+ match self.1 {
+ Some(0) => return Ok(None),
+ Some(x) => self.1 = Some(x - 1),
+ None => match self.0.decoder.pull()? {
+ Header::Break => return Ok(None),
+ header => self.0.decoder.push(header),
+ },
+ }
+
+ seed.deserialize(&mut *self.0).map(Some)
+ }
+
+ #[inline]
+ fn next_value_seed<V: de::DeserializeSeed<'de>>(
+ &mut self,
+ seed: V,
+ ) -> Result<V::Value, Self::Error> {
+ seed.deserialize(&mut *self.0)
+ }
+
+ #[inline]
+ fn size_hint(&self) -> Option<usize> {
+ self.1
+ }
+}
+
+impl<'de, 'a, 'b, R: Read> de::EnumAccess<'de> for Access<'a, 'b, R>
+where
+ R::Error: core::fmt::Debug,
+{
+ type Error = Error<R::Error>;
+ type Variant = Self;
+
+ #[inline]
+ fn variant_seed<V: de::DeserializeSeed<'de>>(
+ self,
+ seed: V,
+ ) -> Result<(V::Value, Self::Variant), Self::Error> {
+ let variant = seed.deserialize(&mut *self.0)?;
+ Ok((variant, self))
+ }
+}
+
+impl<'de, 'a, 'b, R: Read> de::VariantAccess<'de> for Access<'a, 'b, R>
+where
+ R::Error: core::fmt::Debug,
+{
+ type Error = Error<R::Error>;
+
+ #[inline]
+ fn unit_variant(self) -> Result<(), Self::Error> {
+ Ok(())
+ }
+
+ #[inline]
+ fn newtype_variant_seed<U: de::DeserializeSeed<'de>>(
+ self,
+ seed: U,
+ ) -> Result<U::Value, Self::Error> {
+ seed.deserialize(&mut *self.0)
+ }
+
+ #[inline]
+ fn tuple_variant<V: de::Visitor<'de>>(
+ self,
+ _len: usize,
+ visitor: V,
+ ) -> Result<V::Value, Self::Error> {
+ self.0.deserialize_any(visitor)
+ }
+
+ #[inline]
+ fn struct_variant<V: de::Visitor<'de>>(
+ self,
+ _fields: &'static [&'static str],
+ visitor: V,
+ ) -> Result<V::Value, Self::Error> {
+ self.0.deserialize_any(visitor)
+ }
+}
+
+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>
+where
+ R::Error: core::fmt::Debug,
+{
+ type Error = Error<R::Error>;
+
+ #[inline]
+ fn deserialize_any<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ let offset = self.0.decoder.offset();
+
+ match self.0.decoder.pull()? {
+ Header::Tag(x) => visitor.visit_u64(x),
+ _ => Err(Error::semantic(offset, "expected tag")),
+ }
+ }
+
+ forward_to_deserialize_any! {
+ i8 i16 i32 i64 i128
+ u8 u16 u32 u64 u128
+ bool f32 f64
+ char str string
+ bytes byte_buf
+ seq map
+ struct tuple tuple_struct
+ identifier ignored_any
+ option unit unit_struct newtype_struct enum
+ }
+}
+
+impl<'de, 'a, 'b, R: Read> de::SeqAccess<'de> for TagAccess<'a, 'b, 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> {
+ self.1 += 1;
+
+ match self.1 {
+ 1 => seed.deserialize(self).map(Some),
+ 2 => seed.deserialize(&mut *self.0).map(Some),
+ _ => Ok(None),
+ }
+ }
+
+ #[inline]
+ fn size_hint(&self) -> Option<usize> {
+ Some(match self.1 {
+ 0 => 2,
+ 1 => 1,
+ _ => 0,
+ })
+ }
+}
+
+/// 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>>
+where
+ R::Error: core::fmt::Debug,
+{
+ let mut scratch = [0; 4096];
+
+ let mut reader = Deserializer {
+ decoder: reader.into(),
+ scratch: &mut scratch,
+ recurse: 256,
+ };
+
+ T::deserialize(&mut reader)
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..78c4bbd
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,212 @@
+// SPDX-License-Identifier: Apache-2.0
+
+//! Welcome to Ciborium!
+//!
+//! Ciborium contains CBOR serialization and deserialization implementations for serde.
+//!
+//! # Quick Start
+//!
+//! You're probably looking for [`de::from_reader()`](crate::de::from_reader)
+//! and [`ser::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).
+//!
+//! # Design Decisions
+//!
+//! ## Always Serialize Numeric Values to the Smallest Size
+//!
+//! Although the CBOR specification has differing numeric widths, this is only
+//! a form of compression on the wire and is not intended to directly
+//! represent an "integer width" or "float width." Therefore, ciborium always
+//! serializes numbers to the smallest possible lossless encoding. For example,
+//! we serialize `1u128` as a single byte (`01`). Likewise, we will also freely
+//! decode that single byte into a `u128`.
+//!
+//! While there is some minor performance cost for this, there are several
+//! reasons for this choice. First, the specification seems to imply it by
+//! using a separate bit for the sign. Second, the specification requires
+//! that implementations handle leading zeroes; a liberal reading of which
+//! implies a requirement for lossless coercion. Third, dynamic languages like
+//! Python have no notion of "integer width," making this is a practical
+//! choice for maximizing wire compatibility with those languages.
+//!
+//! This coercion is **always** lossless. For floats, this implies that we
+//! only coerce to a smaller size if coercion back to the original size has
+//! the same raw bits as the original.
+//!
+//! ## Compatibility with Other Implementations
+//!
+//! The ciborium project follows the [Robustness Principle](https://en.wikipedia.org/wiki/Robustness_principle).
+//! Therefore, we aim to be liberal in what we accept. This implies that we
+//! aim to be wire-compatible with other implementations in decoding, but
+//! not necessarily encoding.
+//!
+//! One notable example of this is that `serde_cbor` uses fixed-width encoding
+//! of numbers and doesn't losslessly coerce. This implies that `ciborium` will
+//! successfully decode `serde_cbor` encodings, but the opposite may not be the
+//! case.
+//!
+//! ## Representing Map as a Sequence of Values
+//!
+//! Other serde parsers have generally taken the route of using `BTreeMap` or
+//! `HashMap` to implement their encoding's underlying `Map` type. This crate
+//! chooses to represent the `Map` type using `Vec<(Value, Value)>` instead.
+//!
+//! This decision was made because this type preserves the order of the pairs
+//! on the wire. Further, for those that need the properties of `BTreeMap` or
+//! `HashMap`, you can simply `collect()` the values into the respective type.
+//! This provides maximum flexibility.
+//!
+//! ## Low-level Library
+//!
+//! The ciborium crate has the beginnings of a low-level library in the
+//! (private) `basic` module. We may extend this to be more robust and expose
+//! it for application consumption once we have it in a good state. If you'd
+//! like to collaborate with us on that, please contact us. Alternatively,
+//! we might fork this code into a separate crate with no serde dependency.
+//!
+//! ## Internal Types
+//!
+//! The ciborium crate contains a number of internal types that implement
+//! useful serde traits. While these are not currently exposed, we might
+//! choose to expose them in the future if there is demand. Generally, this
+//! crate takes a conservative approach to exposing APIs to avoid breakage.
+//!
+//! ## Packed Encoding?
+//!
+//! Packed encoding uses numerical offsets to represent structure field names
+//! and enum variant names. This can save significant space on the wire.
+//!
+//! While the authors of this crate like packed encoding, it should generally
+//! be avoided because it can be fragile as it exposes invariants of your Rust
+//! code to remote actors. We might consider adding this in the future. If you
+//! are interested in this, please contact us.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+#![deny(missing_docs)]
+#![deny(clippy::all)]
+#![deny(clippy::cargo)]
+#![allow(clippy::unit_arg)]
+
+extern crate alloc;
+
+pub mod de;
+pub mod ser;
+pub mod tag;
+pub mod value;
+
+/// Build a `Value` conveniently.
+///
+/// The syntax should be intuitive if you are familiar with JSON. You can also
+/// inline simple Rust expressions, including custom values that implement
+/// `serde::Serialize`. Note that this macro returns `Result<Value, Error>`,
+/// so you should handle the error appropriately.
+///
+/// ```
+/// use ciborium::cbor;
+///
+/// let value = cbor!({
+/// "code" => 415,
+/// "message" => null,
+/// "continue" => false,
+/// "extra" => { "numbers" => [8.2341e+4, 0.251425] },
+/// }).unwrap();
+/// ```
+#[macro_export]
+macro_rules! cbor {
+ (@map {$($key:expr => $val:expr),*} $(,)?) => {{
+ $crate::value::Value::Map(vec![
+ $(
+ (cbor!( $key )?, cbor!( $val )?)
+ ),*
+ ])
+ }};
+
+ (@map {$($key:expr => $val:expr),*} { $($nkey:tt)* } => $($next:tt)*) => {
+ cbor!(
+ @map
+ { $($key => $val),* }
+ cbor!({ $($nkey)* })? =>
+ $($next)*
+ )
+ };
+
+ (@map {$($key:expr => $val:expr),*} [ $($nkey:tt)* ] => $($next:tt)*) => {
+ cbor!(
+ @map
+ { $($key => $val),* }
+ cbor!([ $($nkey)* ])? =>
+ $($next)*
+ )
+ };
+
+ (@map {$($key:expr => $val:expr),*} $nkey:expr => { $($nval:tt)* }, $($next:tt)*) => {
+ cbor!(
+ @map
+ { $($key => $val,)* $nkey => cbor!({ $($nval)* })? }
+ $($next)*
+ )
+ };
+
+ (@map {$($key:expr => $val:expr),*} $nkey:expr => [ $($nval:tt)* ], $($next:tt)*) => {
+ cbor!(
+ @map
+ { $($key => $val,)* $nkey => cbor!([ $($nval)* ])? }
+ $($next)*
+ )
+ };
+
+ (@map {$($key:expr => $val:expr),*} $nkey:expr => $nval:expr, $($next:tt)*) => {
+ cbor!(
+ @map
+ { $($key => $val,)* $nkey => cbor!($nval)? }
+ $($next)*
+ )
+ };
+
+ (@seq [$($val:expr),*] $(,)?) => {
+ $crate::value::Value::Array(
+ vec![$( cbor!($val)? ),*]
+ )
+ };
+
+ (@seq [$($val:expr),*] { $($item:tt)* }, $($next:tt)*) => {
+ cbor!(
+ @seq
+ [ $($val,)* cbor!({ $($item)* })? ]
+ $($next)*
+ )
+ };
+
+ (@seq [$($val:expr),*] [ $($item:tt)* ], $($next:tt)*) => {
+ cbor!(
+ @seq
+ [ $($val,)* cbor!([ $($item)* ])? ]
+ $($next)*
+ )
+ };
+
+ (@seq [$($val:expr),*] $item:expr, $($next:tt)*) => {
+ cbor!(
+ @seq
+ [ $($val,)* $item ]
+ $($next)*
+ )
+ };
+
+ ({ $($next:tt)* }) => {(||{
+ ::core::result::Result::<_, $crate::value::Error>::from(Ok(cbor!(@map {} $($next)* ,)))
+ })()};
+
+ ([ $($next:tt)* ]) => {(||{
+ ::core::result::Result::<_, $crate::value::Error>::from(Ok(cbor!(@seq [] $($next)* ,)))
+ })()};
+
+ ($val:expr) => {{
+ #[allow(unused_imports)]
+ use $crate::value::Value::Null as null;
+ $crate::value::Value::serialized(&$val)
+ }};
+}
diff --git a/src/ser/error.rs b/src/ser/error.rs
new file mode 100644
index 0000000..e908628
--- /dev/null
+++ b/src/ser/error.rs
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use alloc::string::{String, ToString};
+use core::fmt::{Debug, Display, Formatter, Result};
+
+use serde::ser::{Error as SerError, StdError};
+
+/// An error occurred during serialization
+#[derive(Debug)]
+pub enum Error<T> {
+ /// An error occurred while writing bytes
+ ///
+ /// Contains the underlying error reaturned while writing.
+ Io(T),
+
+ /// An error indicating a value that cannot be serialized
+ ///
+ /// Contains a description of the problem.
+ Value(String),
+}
+
+impl<T> From<T> for Error<T> {
+ #[inline]
+ fn from(value: T) -> Self {
+ Error::Io(value)
+ }
+}
+
+impl<T: Debug> Display for Error<T> {
+ #[inline]
+ fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+ write!(f, "{:?}", self)
+ }
+}
+
+impl<T: Debug> StdError for Error<T> {}
+
+impl<T: Debug> SerError for Error<T> {
+ fn custom<U: Display>(msg: U) -> Self {
+ Error::Value(msg.to_string())
+ }
+}
diff --git a/src/ser/mod.rs b/src/ser/mod.rs
new file mode 100644
index 0000000..0488d72
--- /dev/null
+++ b/src/ser/mod.rs
@@ -0,0 +1,500 @@
+// SPDX-License-Identifier: Apache-2.0
+
+//! Serde serialization support for CBOR
+
+mod error;
+
+pub use error::Error;
+
+use alloc::string::ToString;
+
+use ciborium_io::Write;
+use ciborium_ll::*;
+use serde::{ser, Serialize as _};
+
+struct Serializer<W: Write>(Encoder<W>);
+
+impl<W: Write> From<W> for Serializer<W> {
+ #[inline]
+ fn from(writer: W) -> Self {
+ Self(writer.into())
+ }
+}
+
+impl<W: Write> From<Encoder<W>> for Serializer<W> {
+ #[inline]
+ fn from(writer: Encoder<W>) -> Self {
+ Self(writer)
+ }
+}
+
+impl<'a, W: Write> ser::Serializer for &'a mut Serializer<W>
+where
+ W::Error: core::fmt::Debug,
+{
+ type Ok = ();
+ type Error = Error<W::Error>;
+
+ type SerializeSeq = CollectionSerializer<'a, W>;
+ type SerializeTuple = CollectionSerializer<'a, W>;
+ type SerializeTupleStruct = CollectionSerializer<'a, W>;
+ type SerializeTupleVariant = CollectionSerializer<'a, W>;
+ type SerializeMap = CollectionSerializer<'a, W>;
+ type SerializeStruct = CollectionSerializer<'a, W>;
+ type SerializeStructVariant = CollectionSerializer<'a, W>;
+
+ #[inline]
+ fn serialize_bool(self, v: bool) -> Result<(), Self::Error> {
+ Ok(self.0.push(match v {
+ false => Header::Simple(simple::FALSE),
+ true => Header::Simple(simple::TRUE),
+ })?)
+ }
+
+ #[inline]
+ fn serialize_i8(self, v: i8) -> Result<(), Self::Error> {
+ self.serialize_i64(v.into())
+ }
+
+ #[inline]
+ fn serialize_i16(self, v: i16) -> Result<(), Self::Error> {
+ self.serialize_i64(v.into())
+ }
+
+ #[inline]
+ fn serialize_i32(self, v: i32) -> Result<(), Self::Error> {
+ self.serialize_i64(v.into())
+ }
+
+ #[inline]
+ fn serialize_i64(self, v: i64) -> Result<(), Self::Error> {
+ Ok(self.0.push(match v.is_negative() {
+ false => Header::Positive(v as u64),
+ true => Header::Negative(v as u64 ^ !0),
+ })?)
+ }
+
+ #[inline]
+ fn serialize_i128(self, v: i128) -> Result<(), Self::Error> {
+ let (tag, raw) = match v.is_negative() {
+ false => (tag::BIGPOS, v as u128),
+ true => (tag::BIGNEG, v as u128 ^ !0),
+ };
+
+ match (tag, u64::try_from(raw)) {
+ (tag::BIGPOS, Ok(x)) => return Ok(self.0.push(Header::Positive(x))?),
+ (tag::BIGNEG, Ok(x)) => return Ok(self.0.push(Header::Negative(x))?),
+ _ => {}
+ }
+
+ let bytes = raw.to_be_bytes();
+
+ // Skip leading zeros.
+ let mut slice = &bytes[..];
+ while !slice.is_empty() && slice[0] == 0 {
+ slice = &slice[1..];
+ }
+
+ self.0.push(Header::Tag(tag))?;
+ self.0.push(Header::Bytes(Some(slice.len())))?;
+ Ok(self.0.write_all(slice)?)
+ }
+
+ #[inline]
+ fn serialize_u8(self, v: u8) -> Result<(), Self::Error> {
+ self.serialize_u64(v.into())
+ }
+
+ #[inline]
+ fn serialize_u16(self, v: u16) -> Result<(), Self::Error> {
+ self.serialize_u64(v.into())
+ }
+
+ #[inline]
+ fn serialize_u32(self, v: u32) -> Result<(), Self::Error> {
+ self.serialize_u64(v.into())
+ }
+
+ #[inline]
+ fn serialize_u64(self, v: u64) -> Result<(), Self::Error> {
+ Ok(self.0.push(Header::Positive(v))?)
+ }
+
+ #[inline]
+ fn serialize_u128(self, v: u128) -> Result<(), Self::Error> {
+ if let Ok(x) = u64::try_from(v) {
+ return self.serialize_u64(x);
+ }
+
+ let bytes = v.to_be_bytes();
+
+ // Skip leading zeros.
+ let mut slice = &bytes[..];
+ while !slice.is_empty() && slice[0] == 0 {
+ slice = &slice[1..];
+ }
+
+ self.0.push(Header::Tag(tag::BIGPOS))?;
+ self.0.push(Header::Bytes(Some(slice.len())))?;
+ Ok(self.0.write_all(slice)?)
+ }
+
+ #[inline]
+ fn serialize_f32(self, v: f32) -> Result<(), Self::Error> {
+ self.serialize_f64(v.into())
+ }
+
+ #[inline]
+ fn serialize_f64(self, v: f64) -> Result<(), Self::Error> {
+ Ok(self.0.push(Header::Float(v))?)
+ }
+
+ #[inline]
+ fn serialize_char(self, v: char) -> Result<(), Self::Error> {
+ self.serialize_str(&v.to_string())
+ }
+
+ #[inline]
+ fn serialize_str(self, v: &str) -> Result<(), Self::Error> {
+ let bytes = v.as_bytes();
+ self.0.push(Header::Text(bytes.len().into()))?;
+ Ok(self.0.write_all(bytes)?)
+ }
+
+ #[inline]
+ fn serialize_bytes(self, v: &[u8]) -> Result<(), Self::Error> {
+ self.0.push(Header::Bytes(v.len().into()))?;
+ Ok(self.0.write_all(v)?)
+ }
+
+ #[inline]
+ fn serialize_none(self) -> Result<(), Self::Error> {
+ Ok(self.0.push(Header::Simple(simple::NULL))?)
+ }
+
+ #[inline]
+ fn serialize_some<U: ?Sized + ser::Serialize>(self, value: &U) -> Result<(), Self::Error> {
+ value.serialize(self)
+ }
+
+ #[inline]
+ fn serialize_unit(self) -> Result<(), Self::Error> {
+ self.serialize_none()
+ }
+
+ #[inline]
+ fn serialize_unit_struct(self, _name: &'static str) -> Result<(), Self::Error> {
+ self.serialize_unit()
+ }
+
+ #[inline]
+ fn serialize_unit_variant(
+ self,
+ _name: &'static str,
+ _index: u32,
+ variant: &'static str,
+ ) -> Result<(), Self::Error> {
+ self.serialize_str(variant)
+ }
+
+ #[inline]
+ fn serialize_newtype_struct<U: ?Sized + ser::Serialize>(
+ self,
+ _name: &'static str,
+ value: &U,
+ ) -> Result<(), Self::Error> {
+ value.serialize(self)
+ }
+
+ #[inline]
+ fn serialize_newtype_variant<U: ?Sized + ser::Serialize>(
+ self,
+ name: &'static str,
+ _index: u32,
+ variant: &'static str,
+ value: &U,
+ ) -> Result<(), Self::Error> {
+ if name != "@@TAG@@" || variant != "@@UNTAGGED@@" {
+ self.0.push(Header::Map(Some(1)))?;
+ self.serialize_str(variant)?;
+ }
+
+ value.serialize(self)
+ }
+
+ #[inline]
+ fn serialize_seq(self, length: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
+ self.0.push(Header::Array(length))?;
+ Ok(CollectionSerializer {
+ encoder: self,
+ ending: length.is_none(),
+ tag: false,
+ })
+ }
+
+ #[inline]
+ fn serialize_tuple(self, length: usize) -> Result<Self::SerializeTuple, Self::Error> {
+ self.serialize_seq(Some(length))
+ }
+
+ #[inline]
+ fn serialize_tuple_struct(
+ self,
+ _name: &'static str,
+ length: usize,
+ ) -> Result<Self::SerializeTupleStruct, Self::Error> {
+ self.serialize_seq(Some(length))
+ }
+
+ #[inline]
+ fn serialize_tuple_variant(
+ self,
+ name: &'static str,
+ _index: u32,
+ variant: &'static str,
+ length: usize,
+ ) -> Result<Self::SerializeTupleVariant, Self::Error> {
+ match (name, variant) {
+ ("@@TAG@@", "@@TAGGED@@") => Ok(CollectionSerializer {
+ encoder: self,
+ ending: false,
+ tag: true,
+ }),
+
+ _ => {
+ self.0.push(Header::Map(Some(1)))?;
+ self.serialize_str(variant)?;
+ self.0.push(Header::Array(Some(length)))?;
+ Ok(CollectionSerializer {
+ encoder: self,
+ ending: false,
+ tag: false,
+ })
+ }
+ }
+ }
+
+ #[inline]
+ fn serialize_map(self, length: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
+ self.0.push(Header::Map(length))?;
+ Ok(CollectionSerializer {
+ encoder: self,
+ ending: length.is_none(),
+ tag: false,
+ })
+ }
+
+ #[inline]
+ fn serialize_struct(
+ self,
+ _name: &'static str,
+ length: usize,
+ ) -> Result<Self::SerializeStruct, Self::Error> {
+ self.0.push(Header::Map(Some(length)))?;
+ Ok(CollectionSerializer {
+ encoder: self,
+ ending: false,
+ tag: false,
+ })
+ }
+
+ #[inline]
+ fn serialize_struct_variant(
+ self,
+ _name: &'static str,
+ _index: u32,
+ variant: &'static str,
+ length: usize,
+ ) -> Result<Self::SerializeStructVariant, Self::Error> {
+ self.0.push(Header::Map(Some(1)))?;
+ self.serialize_str(variant)?;
+ self.0.push(Header::Map(Some(length)))?;
+ Ok(CollectionSerializer {
+ encoder: self,
+ ending: false,
+ tag: false,
+ })
+ }
+
+ #[inline]
+ fn is_human_readable(&self) -> bool {
+ false
+ }
+}
+
+macro_rules! end {
+ () => {
+ #[inline]
+ fn end(self) -> Result<(), Self::Error> {
+ if self.ending {
+ self.encoder.0.push(Header::Break)?;
+ }
+
+ Ok(())
+ }
+ };
+}
+
+struct CollectionSerializer<'a, W: Write> {
+ encoder: &'a mut Serializer<W>,
+ ending: bool,
+ tag: bool,
+}
+
+impl<'a, W: Write> ser::SerializeSeq for CollectionSerializer<'a, W>
+where
+ W::Error: core::fmt::Debug,
+{
+ type Ok = ();
+ type Error = Error<W::Error>;
+
+ #[inline]
+ fn serialize_element<U: ?Sized + ser::Serialize>(
+ &mut self,
+ value: &U,
+ ) -> Result<(), Self::Error> {
+ value.serialize(&mut *self.encoder)
+ }
+
+ end!();
+}
+
+impl<'a, W: Write> ser::SerializeTuple for CollectionSerializer<'a, W>
+where
+ W::Error: core::fmt::Debug,
+{
+ type Ok = ();
+ type Error = Error<W::Error>;
+
+ #[inline]
+ fn serialize_element<U: ?Sized + ser::Serialize>(
+ &mut self,
+ value: &U,
+ ) -> Result<(), Self::Error> {
+ value.serialize(&mut *self.encoder)
+ }
+
+ end!();
+}
+
+impl<'a, W: Write> ser::SerializeTupleStruct for CollectionSerializer<'a, W>
+where
+ W::Error: core::fmt::Debug,
+{
+ type Ok = ();
+ type Error = Error<W::Error>;
+
+ #[inline]
+ fn serialize_field<U: ?Sized + ser::Serialize>(
+ &mut self,
+ value: &U,
+ ) -> Result<(), Self::Error> {
+ value.serialize(&mut *self.encoder)
+ }
+
+ end!();
+}
+
+impl<'a, W: Write> ser::SerializeTupleVariant for CollectionSerializer<'a, W>
+where
+ W::Error: core::fmt::Debug,
+{
+ type Ok = ();
+ type Error = Error<W::Error>;
+
+ #[inline]
+ fn serialize_field<U: ?Sized + ser::Serialize>(
+ &mut self,
+ value: &U,
+ ) -> Result<(), Self::Error> {
+ if !self.tag {
+ return value.serialize(&mut *self.encoder);
+ }
+
+ self.tag = false;
+ match value.serialize(crate::tag::Serializer) {
+ Ok(x) => Ok(self.encoder.0.push(Header::Tag(x))?),
+ _ => Err(Error::Value("expected tag".into())),
+ }
+ }
+
+ end!();
+}
+
+impl<'a, W: Write> ser::SerializeMap for CollectionSerializer<'a, W>
+where
+ W::Error: core::fmt::Debug,
+{
+ type Ok = ();
+ type Error = Error<W::Error>;
+
+ #[inline]
+ fn serialize_key<U: ?Sized + ser::Serialize>(&mut self, key: &U) -> Result<(), Self::Error> {
+ key.serialize(&mut *self.encoder)
+ }
+
+ #[inline]
+ fn serialize_value<U: ?Sized + ser::Serialize>(
+ &mut self,
+ value: &U,
+ ) -> Result<(), Self::Error> {
+ value.serialize(&mut *self.encoder)
+ }
+
+ end!();
+}
+
+impl<'a, W: Write> ser::SerializeStruct for CollectionSerializer<'a, W>
+where
+ W::Error: core::fmt::Debug,
+{
+ type Ok = ();
+ type Error = Error<W::Error>;
+
+ #[inline]
+ fn serialize_field<U: ?Sized + ser::Serialize>(
+ &mut self,
+ key: &'static str,
+ value: &U,
+ ) -> Result<(), Self::Error> {
+ key.serialize(&mut *self.encoder)?;
+ value.serialize(&mut *self.encoder)?;
+ Ok(())
+ }
+
+ end!();
+}
+
+impl<'a, W: Write> ser::SerializeStructVariant for CollectionSerializer<'a, W>
+where
+ W::Error: core::fmt::Debug,
+{
+ type Ok = ();
+ type Error = Error<W::Error>;
+
+ #[inline]
+ fn serialize_field<U: ?Sized + ser::Serialize>(
+ &mut self,
+ key: &'static str,
+ value: &U,
+ ) -> Result<(), Self::Error> {
+ key.serialize(&mut *self.encoder)?;
+ value.serialize(&mut *self.encoder)
+ }
+
+ end!();
+}
+
+/// Serializes as CBOR into a type with [`impl ciborium_io::Write`](ciborium_io::Write)
+#[inline]
+pub fn into_writer<T: ?Sized + ser::Serialize, W: Write>(
+ value: &T,
+ writer: W,
+) -> Result<(), Error<W::Error>>
+where
+ W::Error: core::fmt::Debug,
+{
+ let mut encoder = Serializer::from(writer);
+ value.serialize(&mut encoder)?;
+ Ok(encoder.0.flush()?)
+}
diff --git a/src/tag.rs b/src/tag.rs
new file mode 100644
index 0000000..bc18753
--- /dev/null
+++ b/src/tag.rs
@@ -0,0 +1,545 @@
+//! Contains helper types for dealing with CBOR tags
+
+use serde::{de, de::Error as _, forward_to_deserialize_any, ser, Deserialize, Serialize};
+
+#[derive(Deserialize, Serialize)]
+#[serde(rename = "@@TAG@@")]
+enum Internal<T> {
+ #[serde(rename = "@@UNTAGGED@@")]
+ Untagged(T),
+
+ #[serde(rename = "@@TAGGED@@")]
+ Tagged(u64, T),
+}
+
+/// An optional CBOR tag and its data item
+///
+/// No semantic evaluation of the tag is made.
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct Captured<V>(pub Option<u64>, pub V);
+
+impl<'de, V: Deserialize<'de>> Deserialize<'de> for Captured<V> {
+ #[inline]
+ fn deserialize<D: de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+ match Internal::deserialize(deserializer)? {
+ Internal::Tagged(t, v) => Ok(Captured(Some(t), v)),
+ Internal::Untagged(v) => Ok(Captured(None, v)),
+ }
+ }
+}
+
+impl<V: Serialize> Serialize for Captured<V> {
+ #[inline]
+ fn serialize<S: ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+ match self.0 {
+ Some(tag) => Internal::Tagged(tag, &self.1).serialize(serializer),
+ None => Internal::Untagged(&self.1).serialize(serializer),
+ }
+ }
+}
+
+/// A required CBOR tag
+///
+/// This data type indicates that the specified tag, and **only** that tag,
+/// is required during deserialization. If the tag is missing, deserialization
+/// will fail. The tag will always be emitted during serialization.
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct Required<V, const TAG: u64>(pub V);
+
+impl<'de, V: Deserialize<'de>, const TAG: u64> Deserialize<'de> for Required<V, TAG> {
+ #[inline]
+ fn deserialize<D: de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+ match Internal::deserialize(deserializer)? {
+ Internal::Tagged(t, v) if t == TAG => Ok(Required(v)),
+ _ => Err(de::Error::custom("required tag not found")),
+ }
+ }
+}
+
+impl<V: Serialize, const TAG: u64> Serialize for Required<V, TAG> {
+ #[inline]
+ fn serialize<S: ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+ Internal::Tagged(TAG, &self.0).serialize(serializer)
+ }
+}
+
+/// An optional CBOR tag
+///
+/// This data type indicates that the specified tag, and **only** that tag,
+/// is accepted, but not required, during deserialization. The tag will always
+/// be emitted during serialization.
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct Accepted<V, const TAG: u64>(pub V);
+
+impl<'de, V: Deserialize<'de>, const TAG: u64> Deserialize<'de> for Accepted<V, TAG> {
+ #[inline]
+ fn deserialize<D: de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+ match Internal::deserialize(deserializer)? {
+ Internal::Tagged(t, v) if t == TAG => Ok(Accepted(v)),
+ Internal::Untagged(v) => Ok(Accepted(v)),
+ _ => Err(de::Error::custom("required tag not found")),
+ }
+ }
+}
+
+impl<V: Serialize, const TAG: u64> Serialize for Accepted<V, TAG> {
+ #[inline]
+ fn serialize<S: ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+ Internal::Tagged(TAG, &self.0).serialize(serializer)
+ }
+}
+
+pub(crate) struct TagAccess<D> {
+ parent: Option<D>,
+ state: usize,
+ tag: Option<u64>,
+}
+
+impl<D> TagAccess<D> {
+ pub fn new(parent: D, tag: Option<u64>) -> Self {
+ Self {
+ parent: Some(parent),
+ state: 0,
+ tag,
+ }
+ }
+}
+
+impl<'de, D: de::Deserializer<'de>> de::Deserializer<'de> for &mut TagAccess<D> {
+ type Error = D::Error;
+
+ #[inline]
+ fn deserialize_any<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ self.state += 1;
+
+ match self.state {
+ 1 => visitor.visit_str(match self.tag {
+ Some(..) => "@@TAGGED@@",
+ None => "@@UNTAGGED@@",
+ }),
+
+ _ => visitor.visit_u64(self.tag.unwrap()),
+ }
+ }
+
+ forward_to_deserialize_any! {
+ i8 i16 i32 i64 i128
+ u8 u16 u32 u64 u128
+ bool f32 f64
+ char str string
+ bytes byte_buf
+ seq map
+ struct tuple tuple_struct
+ identifier ignored_any
+ option unit unit_struct newtype_struct enum
+ }
+}
+
+impl<'de, D: de::Deserializer<'de>> de::EnumAccess<'de> for TagAccess<D> {
+ type Error = D::Error;
+ type Variant = Self;
+
+ #[inline]
+ fn variant_seed<V: de::DeserializeSeed<'de>>(
+ mut self,
+ seed: V,
+ ) -> Result<(V::Value, Self::Variant), Self::Error> {
+ let variant = seed.deserialize(&mut self)?;
+ Ok((variant, self))
+ }
+}
+
+impl<'de, D: de::Deserializer<'de>> de::VariantAccess<'de> for TagAccess<D> {
+ type Error = D::Error;
+
+ #[inline]
+ fn unit_variant(self) -> Result<(), Self::Error> {
+ Err(Self::Error::custom("expected tag"))
+ }
+
+ #[inline]
+ fn newtype_variant_seed<U: de::DeserializeSeed<'de>>(
+ mut self,
+ seed: U,
+ ) -> Result<U::Value, Self::Error> {
+ seed.deserialize(self.parent.take().unwrap())
+ }
+
+ #[inline]
+ fn tuple_variant<V: de::Visitor<'de>>(
+ self,
+ _len: usize,
+ visitor: V,
+ ) -> Result<V::Value, Self::Error> {
+ visitor.visit_seq(self)
+ }
+
+ #[inline]
+ fn struct_variant<V: de::Visitor<'de>>(
+ self,
+ _fields: &'static [&'static str],
+ _visitor: V,
+ ) -> Result<V::Value, Self::Error> {
+ Err(Self::Error::custom("expected tag"))
+ }
+}
+
+impl<'de, D: de::Deserializer<'de>> de::SeqAccess<'de> for TagAccess<D> {
+ type Error = D::Error;
+
+ #[inline]
+ fn next_element_seed<T: de::DeserializeSeed<'de>>(
+ &mut self,
+ seed: T,
+ ) -> Result<Option<T::Value>, Self::Error> {
+ if self.state < 2 {
+ return Ok(Some(seed.deserialize(self)?));
+ }
+
+ Ok(match self.parent.take() {
+ Some(x) => Some(seed.deserialize(x)?),
+ None => None,
+ })
+ }
+}
+
+#[derive(Debug)]
+pub(crate) struct Error;
+
+impl core::fmt::Display for Error {
+ #[inline]
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ write!(f, "{:?}", self)
+ }
+}
+
+impl ser::StdError for Error {}
+
+impl ser::Error for Error {
+ fn custom<U: core::fmt::Display>(_msg: U) -> Self {
+ Error
+ }
+}
+
+pub(crate) struct Serializer;
+
+impl ser::Serializer for Serializer {
+ type Ok = u64;
+ type Error = Error;
+
+ type SerializeSeq = Self;
+ type SerializeTuple = Self;
+ type SerializeTupleStruct = Self;
+ type SerializeTupleVariant = Self;
+ type SerializeMap = Self;
+ type SerializeStruct = Self;
+ type SerializeStructVariant = Self;
+
+ #[inline]
+ fn serialize_bool(self, _: bool) -> Result<u64, Self::Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn serialize_i8(self, _: i8) -> Result<u64, Self::Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn serialize_i16(self, _: i16) -> Result<u64, Self::Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn serialize_i32(self, _: i32) -> Result<u64, Self::Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn serialize_i64(self, _: i64) -> Result<u64, Self::Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn serialize_i128(self, _: i128) -> Result<u64, Self::Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn serialize_u8(self, v: u8) -> Result<u64, Self::Error> {
+ Ok(v.into())
+ }
+
+ #[inline]
+ fn serialize_u16(self, v: u16) -> Result<u64, Self::Error> {
+ Ok(v.into())
+ }
+
+ #[inline]
+ fn serialize_u32(self, v: u32) -> Result<u64, Self::Error> {
+ Ok(v.into())
+ }
+
+ #[inline]
+ fn serialize_u64(self, v: u64) -> Result<u64, Self::Error> {
+ Ok(v)
+ }
+
+ #[inline]
+ fn serialize_u128(self, _: u128) -> Result<u64, Self::Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn serialize_f32(self, _: f32) -> Result<u64, Self::Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn serialize_f64(self, _: f64) -> Result<u64, Self::Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn serialize_char(self, _: char) -> Result<u64, Self::Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn serialize_str(self, _: &str) -> Result<u64, Self::Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn serialize_bytes(self, _: &[u8]) -> Result<u64, Self::Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn serialize_none(self) -> Result<u64, Self::Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn serialize_some<U: ?Sized + ser::Serialize>(self, _: &U) -> Result<u64, Self::Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn serialize_unit(self) -> Result<u64, Self::Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn serialize_unit_struct(self, _name: &'static str) -> Result<u64, Self::Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn serialize_unit_variant(
+ self,
+ _name: &'static str,
+ _index: u32,
+ _variant: &'static str,
+ ) -> Result<u64, Self::Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn serialize_newtype_struct<U: ?Sized + ser::Serialize>(
+ self,
+ _name: &'static str,
+ _value: &U,
+ ) -> Result<u64, Self::Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn serialize_newtype_variant<U: ?Sized + ser::Serialize>(
+ self,
+ _name: &'static str,
+ _index: u32,
+ _variant: &'static str,
+ _value: &U,
+ ) -> Result<u64, Self::Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn serialize_seq(self, _length: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn serialize_tuple(self, _length: usize) -> Result<Self::SerializeTuple, Self::Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn serialize_tuple_struct(
+ self,
+ _name: &'static str,
+ _length: usize,
+ ) -> Result<Self::SerializeTupleStruct, Self::Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn serialize_tuple_variant(
+ self,
+ _name: &'static str,
+ _index: u32,
+ _variant: &'static str,
+ _length: usize,
+ ) -> Result<Self::SerializeTupleVariant, Self::Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn serialize_map(self, _length: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn serialize_struct(
+ self,
+ _name: &'static str,
+ _length: usize,
+ ) -> Result<Self::SerializeStruct, Self::Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn serialize_struct_variant(
+ self,
+ _name: &'static str,
+ _index: u32,
+ _variant: &'static str,
+ _length: usize,
+ ) -> Result<Self::SerializeStructVariant, Self::Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn is_human_readable(&self) -> bool {
+ false
+ }
+}
+
+impl<'a> ser::SerializeSeq for Serializer {
+ type Ok = u64;
+ type Error = Error;
+
+ #[inline]
+ fn serialize_element<U: ?Sized + ser::Serialize>(&mut self, _value: &U) -> Result<(), Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn end(self) -> Result<Self::Ok, Self::Error> {
+ Err(Error)
+ }
+}
+
+impl<'a> ser::SerializeTuple for Serializer {
+ type Ok = u64;
+ type Error = Error;
+
+ #[inline]
+ fn serialize_element<U: ?Sized + ser::Serialize>(&mut self, _value: &U) -> Result<(), Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn end(self) -> Result<Self::Ok, Self::Error> {
+ Err(Error)
+ }
+}
+
+impl<'a> ser::SerializeTupleStruct for Serializer {
+ type Ok = u64;
+ type Error = Error;
+
+ #[inline]
+ fn serialize_field<U: ?Sized + ser::Serialize>(&mut self, _value: &U) -> Result<(), Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn end(self) -> Result<Self::Ok, Self::Error> {
+ Err(Error)
+ }
+}
+
+impl<'a> ser::SerializeTupleVariant for Serializer {
+ type Ok = u64;
+ type Error = Error;
+
+ #[inline]
+ fn serialize_field<U: ?Sized + ser::Serialize>(&mut self, _value: &U) -> Result<(), Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn end(self) -> Result<Self::Ok, Self::Error> {
+ Err(Error)
+ }
+}
+
+impl<'a> ser::SerializeMap for Serializer {
+ type Ok = u64;
+ type Error = Error;
+
+ #[inline]
+ fn serialize_key<U: ?Sized + ser::Serialize>(&mut self, _key: &U) -> Result<(), Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn serialize_value<U: ?Sized + ser::Serialize>(&mut self, _value: &U) -> Result<(), Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn end(self) -> Result<Self::Ok, Self::Error> {
+ Err(Error)
+ }
+}
+
+impl<'a> ser::SerializeStruct for Serializer {
+ type Ok = u64;
+ type Error = Error;
+
+ #[inline]
+ fn serialize_field<U: ?Sized + ser::Serialize>(
+ &mut self,
+ _key: &'static str,
+ _value: &U,
+ ) -> Result<(), Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn end(self) -> Result<Self::Ok, Self::Error> {
+ Err(Error)
+ }
+}
+
+impl<'a> ser::SerializeStructVariant for Serializer {
+ type Ok = u64;
+ type Error = Error;
+
+ #[inline]
+ fn serialize_field<U: ?Sized + ser::Serialize>(
+ &mut self,
+ _key: &'static str,
+ _value: &U,
+ ) -> Result<(), Self::Error> {
+ Err(Error)
+ }
+
+ #[inline]
+ fn end(self) -> Result<Self::Ok, Self::Error> {
+ Err(Error)
+ }
+}
diff --git a/src/value/de.rs b/src/value/de.rs
new file mode 100644
index 0000000..0d30a86
--- /dev/null
+++ b/src/value/de.rs
@@ -0,0 +1,616 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use super::{Error, Integer, Value};
+
+use alloc::{boxed::Box, string::String, vec::Vec};
+use core::iter::Peekable;
+
+use ciborium_ll::tag;
+use serde::de::{self, Deserializer as _};
+
+impl<'a> From<Integer> for de::Unexpected<'a> {
+ #[inline]
+ fn from(value: Integer) -> Self {
+ u64::try_from(value)
+ .map(de::Unexpected::Unsigned)
+ .unwrap_or_else(|_| {
+ i64::try_from(value)
+ .map(de::Unexpected::Signed)
+ .unwrap_or_else(|_| de::Unexpected::Other("large integer"))
+ })
+ }
+}
+
+impl<'a> From<&'a Value> for de::Unexpected<'a> {
+ #[inline]
+ fn from(value: &'a Value) -> Self {
+ match value {
+ Value::Bool(x) => Self::Bool(*x),
+ Value::Integer(x) => Self::from(*x),
+ Value::Float(x) => Self::Float(*x),
+ Value::Bytes(x) => Self::Bytes(x),
+ Value::Text(x) => Self::Str(x),
+ Value::Array(..) => Self::Seq,
+ Value::Map(..) => Self::Map,
+ Value::Null => Self::Other("null"),
+ Value::Tag(..) => Self::Other("tag"),
+ }
+ }
+}
+
+macro_rules! mkvisit {
+ ($($f:ident($v:ty)),+ $(,)?) => {
+ $(
+ #[inline]
+ fn $f<E: de::Error>(self, v: $v) -> Result<Self::Value, E> {
+ Ok(v.into())
+ }
+ )+
+ };
+}
+
+struct Visitor;
+
+impl<'de> serde::de::Visitor<'de> for Visitor {
+ type Value = Value;
+
+ fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ write!(formatter, "a valid CBOR item")
+ }
+
+ mkvisit! {
+ visit_bool(bool),
+ visit_f32(f32),
+ visit_f64(f64),
+
+ visit_i8(i8),
+ visit_i16(i16),
+ visit_i32(i32),
+ visit_i64(i64),
+ visit_i128(i128),
+
+ visit_u8(u8),
+ visit_u16(u16),
+ visit_u32(u32),
+ visit_u64(u64),
+ visit_u128(u128),
+
+ visit_char(char),
+ visit_str(&str),
+ visit_borrowed_str(&'de str),
+ visit_string(String),
+
+ visit_bytes(&[u8]),
+ visit_borrowed_bytes(&'de [u8]),
+ visit_byte_buf(Vec<u8>),
+ }
+
+ #[inline]
+ fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
+ Ok(Value::Null)
+ }
+
+ #[inline]
+ fn visit_some<D: de::Deserializer<'de>>(
+ self,
+ deserializer: D,
+ ) -> Result<Self::Value, D::Error> {
+ deserializer.deserialize_any(self)
+ }
+
+ #[inline]
+ fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
+ Ok(Value::Null)
+ }
+
+ #[inline]
+ fn visit_newtype_struct<D: de::Deserializer<'de>>(
+ self,
+ deserializer: D,
+ ) -> Result<Self::Value, D::Error> {
+ deserializer.deserialize_any(self)
+ }
+
+ #[inline]
+ fn visit_seq<A: de::SeqAccess<'de>>(self, mut acc: A) -> Result<Self::Value, A::Error> {
+ let mut seq = Vec::new();
+
+ while let Some(elem) = acc.next_element()? {
+ seq.push(elem);
+ }
+
+ Ok(Value::Array(seq))
+ }
+
+ #[inline]
+ fn visit_map<A: de::MapAccess<'de>>(self, mut acc: A) -> Result<Self::Value, A::Error> {
+ let mut map = Vec::<(Value, Value)>::new();
+
+ while let Some(kv) = acc.next_entry()? {
+ map.push(kv);
+ }
+
+ Ok(Value::Map(map))
+ }
+
+ #[inline]
+ fn visit_enum<A: de::EnumAccess<'de>>(self, acc: A) -> Result<Self::Value, A::Error> {
+ use serde::de::VariantAccess;
+
+ struct Inner;
+
+ impl<'de> serde::de::Visitor<'de> for Inner {
+ type Value = Value;
+
+ fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ write!(formatter, "a valid CBOR item")
+ }
+
+ #[inline]
+ fn visit_seq<A: de::SeqAccess<'de>>(self, mut acc: A) -> Result<Self::Value, A::Error> {
+ let tag: u64 = acc
+ .next_element()?
+ .ok_or_else(|| de::Error::custom("expected tag"))?;
+ let val = acc
+ .next_element()?
+ .ok_or_else(|| de::Error::custom("expected val"))?;
+ Ok(Value::Tag(tag, Box::new(val)))
+ }
+ }
+
+ let (name, data): (String, _) = acc.variant()?;
+ assert_eq!("@@TAGGED@@", name);
+ data.tuple_variant(2, Inner)
+ }
+}
+
+impl<'de> de::Deserialize<'de> for Value {
+ #[inline]
+ fn deserialize<D: de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+ deserializer.deserialize_any(Visitor)
+ }
+}
+
+struct Deserializer<T>(T);
+
+impl<'a, 'de> Deserializer<&'a Value> {
+ fn integer<N>(&self, kind: &'static str) -> Result<N, Error>
+ where
+ N: TryFrom<u128>,
+ N: TryFrom<i128>,
+ {
+ fn raw(value: &Value) -> Result<u128, Error> {
+ let mut buffer = 0u128.to_ne_bytes();
+ let length = buffer.len();
+
+ let bytes = match value {
+ Value::Bytes(bytes) => {
+ // Skip leading zeros...
+ let mut bytes: &[u8] = bytes.as_ref();
+ while bytes.len() > buffer.len() && bytes[0] == 0 {
+ bytes = &bytes[1..];
+ }
+
+ if bytes.len() > buffer.len() {
+ return Err(de::Error::custom("bigint too large"));
+ }
+
+ bytes
+ }
+
+ _ => return Err(de::Error::invalid_type(value.into(), &"bytes")),
+ };
+
+ buffer[length - bytes.len()..].copy_from_slice(bytes);
+ Ok(u128::from_be_bytes(buffer))
+ }
+
+ let err = || de::Error::invalid_type(self.0.into(), &kind);
+
+ Ok(match self.0 {
+ Value::Integer(x) => i128::from(*x).try_into().map_err(|_| err())?,
+ Value::Tag(t, v) if *t == tag::BIGPOS => raw(v)?.try_into().map_err(|_| err())?,
+ Value::Tag(t, v) if *t == tag::BIGNEG => i128::try_from(raw(v)?)
+ .map(|x| x ^ !0)
+ .map_err(|_| err())
+ .and_then(|x| x.try_into().map_err(|_| err()))?,
+ _ => return Err(de::Error::invalid_type(self.0.into(), &"(big)int")),
+ })
+ }
+}
+
+impl<'a, 'de> de::Deserializer<'de> for Deserializer<&'a Value> {
+ type Error = Error;
+
+ #[inline]
+ fn deserialize_any<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ match self.0 {
+ Value::Bytes(x) => visitor.visit_bytes(x),
+ Value::Text(x) => visitor.visit_str(x),
+ Value::Array(x) => visitor.visit_seq(Deserializer(x.iter())),
+ Value::Map(x) => visitor.visit_map(Deserializer(x.iter().peekable())),
+ Value::Bool(x) => visitor.visit_bool(*x),
+ Value::Null => visitor.visit_none(),
+
+ Value::Tag(t, v) => {
+ let parent: Deserializer<&Value> = Deserializer(&*v);
+ let access = crate::tag::TagAccess::new(parent, Some(*t));
+ visitor.visit_enum(access)
+ }
+
+ Value::Integer(x) => {
+ if let Ok(x) = u64::try_from(*x) {
+ visitor.visit_u64(x)
+ } else if let Ok(x) = i64::try_from(*x) {
+ visitor.visit_i64(x)
+ } else if let Ok(x) = i128::try_from(*x) {
+ visitor.visit_i128(x)
+ } else {
+ unreachable!()
+ }
+ }
+
+ Value::Float(x) => visitor.visit_f64(*x),
+ }
+ }
+
+ #[inline]
+ fn deserialize_bool<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ let mut value = self.0;
+ while let Value::Tag(.., v) = value {
+ value = v;
+ }
+
+ match value {
+ Value::Bool(x) => visitor.visit_bool(*x),
+ _ => Err(de::Error::invalid_type(value.into(), &"bool")),
+ }
+ }
+
+ #[inline]
+ fn deserialize_f32<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ self.deserialize_f64(visitor)
+ }
+
+ #[inline]
+ fn deserialize_f64<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ let mut value = self.0;
+ while let Value::Tag(.., v) = value {
+ value = v;
+ }
+
+ match value {
+ Value::Float(x) => visitor.visit_f64(*x),
+ _ => Err(de::Error::invalid_type(value.into(), &"f64")),
+ }
+ }
+
+ fn deserialize_i8<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ visitor.visit_i8(self.integer("i8")?)
+ }
+
+ fn deserialize_i16<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ visitor.visit_i16(self.integer("i16")?)
+ }
+
+ fn deserialize_i32<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ visitor.visit_i32(self.integer("i32")?)
+ }
+
+ fn deserialize_i64<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ visitor.visit_i64(self.integer("i64")?)
+ }
+
+ fn deserialize_i128<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ visitor.visit_i128(self.integer("i128")?)
+ }
+
+ fn deserialize_u8<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ visitor.visit_u8(self.integer("u8")?)
+ }
+
+ fn deserialize_u16<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ visitor.visit_u16(self.integer("u16")?)
+ }
+
+ fn deserialize_u32<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ visitor.visit_u32(self.integer("u32")?)
+ }
+
+ fn deserialize_u64<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ visitor.visit_u64(self.integer("u64")?)
+ }
+
+ fn deserialize_u128<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ visitor.visit_u128(self.integer("u128")?)
+ }
+
+ fn deserialize_char<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ let mut value = self.0;
+ while let Value::Tag(.., v) = value {
+ value = v;
+ }
+
+ match value {
+ Value::Text(x) => match x.chars().count() {
+ 1 => visitor.visit_char(x.chars().next().unwrap()),
+ _ => Err(de::Error::invalid_type(value.into(), &"char")),
+ },
+
+ _ => Err(de::Error::invalid_type(value.into(), &"char")),
+ }
+ }
+
+ fn deserialize_str<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ let mut value = self.0;
+ while let Value::Tag(.., v) = value {
+ value = v;
+ }
+
+ match value {
+ Value::Text(x) => visitor.visit_str(x),
+ _ => Err(de::Error::invalid_type(value.into(), &"str")),
+ }
+ }
+
+ fn deserialize_string<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ self.deserialize_str(visitor)
+ }
+
+ fn deserialize_bytes<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ let mut value = self.0;
+ while let Value::Tag(.., v) = value {
+ value = v;
+ }
+
+ match value {
+ Value::Bytes(x) => visitor.visit_bytes(x),
+ _ => Err(de::Error::invalid_type(value.into(), &"bytes")),
+ }
+ }
+
+ fn deserialize_byte_buf<V: de::Visitor<'de>>(
+ self,
+ visitor: V,
+ ) -> Result<V::Value, Self::Error> {
+ self.deserialize_bytes(visitor)
+ }
+
+ fn deserialize_seq<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ let mut value = self.0;
+ while let Value::Tag(.., v) = value {
+ value = v;
+ }
+
+ match value {
+ Value::Array(x) => visitor.visit_seq(Deserializer(x.iter())),
+ _ => Err(de::Error::invalid_type(value.into(), &"array")),
+ }
+ }
+
+ fn deserialize_map<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ let mut value = self.0;
+ while let Value::Tag(.., v) = value {
+ value = v;
+ }
+
+ match value {
+ Value::Map(x) => visitor.visit_map(Deserializer(x.iter().peekable())),
+ _ => Err(de::Error::invalid_type(value.into(), &"map")),
+ }
+ }
+
+ fn deserialize_struct<V: de::Visitor<'de>>(
+ self,
+ _name: &'static str,
+ _fields: &'static [&'static str],
+ visitor: V,
+ ) -> Result<V::Value, Self::Error> {
+ self.deserialize_map(visitor)
+ }
+
+ fn deserialize_tuple<V: de::Visitor<'de>>(
+ self,
+ _len: usize,
+ visitor: V,
+ ) -> Result<V::Value, Self::Error> {
+ self.deserialize_seq(visitor)
+ }
+
+ fn deserialize_tuple_struct<V: de::Visitor<'de>>(
+ self,
+ _name: &'static str,
+ _len: usize,
+ visitor: V,
+ ) -> Result<V::Value, Self::Error> {
+ self.deserialize_seq(visitor)
+ }
+
+ fn deserialize_identifier<V: de::Visitor<'de>>(
+ self,
+ visitor: V,
+ ) -> Result<V::Value, Self::Error> {
+ self.deserialize_str(visitor)
+ }
+
+ fn deserialize_ignored_any<V: de::Visitor<'de>>(
+ self,
+ visitor: V,
+ ) -> Result<V::Value, Self::Error> {
+ self.deserialize_any(visitor)
+ }
+
+ #[inline]
+ fn deserialize_option<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ match self.0 {
+ Value::Null => visitor.visit_none(),
+ x => visitor.visit_some(Self(x)),
+ }
+ }
+
+ #[inline]
+ fn deserialize_unit<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+ match self.0 {
+ Value::Null => visitor.visit_unit(),
+ _ => Err(de::Error::invalid_type(self.0.into(), &"null")),
+ }
+ }
+
+ #[inline]
+ fn deserialize_unit_struct<V: de::Visitor<'de>>(
+ self,
+ _name: &'static str,
+ visitor: V,
+ ) -> Result<V::Value, Self::Error> {
+ self.deserialize_unit(visitor)
+ }
+
+ #[inline]
+ fn deserialize_newtype_struct<V: de::Visitor<'de>>(
+ self,
+ _name: &'static str,
+ visitor: V,
+ ) -> Result<V::Value, Self::Error> {
+ visitor.visit_newtype_struct(self)
+ }
+
+ #[inline]
+ fn deserialize_enum<V: de::Visitor<'de>>(
+ self,
+ name: &'static str,
+ variants: &'static [&'static str],
+ visitor: V,
+ ) -> Result<V::Value, Self::Error> {
+ if name == "@@TAG@@" {
+ let (tag, val) = match self.0 {
+ Value::Tag(t, v) => (Some(*t), v.as_ref()),
+ v => (None, v),
+ };
+
+ let parent: Deserializer<&Value> = Deserializer(&*val);
+ let access = crate::tag::TagAccess::new(parent, tag);
+ return visitor.visit_enum(access);
+ }
+
+ match self.0 {
+ Value::Tag(.., v) => Deserializer(v.as_ref()).deserialize_enum(name, variants, visitor),
+ Value::Map(x) if x.len() == 1 => visitor.visit_enum(Deserializer(&x[0])),
+ x @ Value::Text(..) => visitor.visit_enum(Deserializer(x)),
+ _ => Err(de::Error::invalid_type(self.0.into(), &"map")),
+ }
+ }
+}
+
+impl<'a, 'de, T: Iterator<Item = &'a Value>> de::SeqAccess<'de> for Deserializer<T> {
+ type Error = Error;
+
+ #[inline]
+ fn next_element_seed<U: de::DeserializeSeed<'de>>(
+ &mut self,
+ seed: U,
+ ) -> Result<Option<U::Value>, Self::Error> {
+ match self.0.next() {
+ None => Ok(None),
+ Some(v) => seed.deserialize(Deserializer(v)).map(Some),
+ }
+ }
+}
+
+impl<'a, 'de, T: Iterator<Item = &'a (Value, Value)>> de::MapAccess<'de>
+ for Deserializer<Peekable<T>>
+{
+ type Error = Error;
+
+ #[inline]
+ fn next_key_seed<K: de::DeserializeSeed<'de>>(
+ &mut self,
+ seed: K,
+ ) -> Result<Option<K::Value>, Self::Error> {
+ match self.0.peek() {
+ None => Ok(None),
+ Some(x) => Ok(Some(seed.deserialize(Deserializer(&x.0))?)),
+ }
+ }
+
+ #[inline]
+ fn next_value_seed<V: de::DeserializeSeed<'de>>(
+ &mut self,
+ seed: V,
+ ) -> Result<V::Value, Self::Error> {
+ seed.deserialize(Deserializer(&self.0.next().unwrap().1))
+ }
+}
+
+impl<'a, 'de> de::EnumAccess<'de> for Deserializer<&'a (Value, Value)> {
+ type Error = Error;
+ type Variant = Deserializer<&'a Value>;
+
+ #[inline]
+ fn variant_seed<V: de::DeserializeSeed<'de>>(
+ self,
+ seed: V,
+ ) -> Result<(V::Value, Self::Variant), Self::Error> {
+ let k = seed.deserialize(Deserializer(&self.0 .0))?;
+ Ok((k, Deserializer(&self.0 .1)))
+ }
+}
+
+impl<'a, 'de> de::EnumAccess<'de> for Deserializer<&'a Value> {
+ type Error = Error;
+ type Variant = Deserializer<&'a Value>;
+
+ #[inline]
+ fn variant_seed<V: de::DeserializeSeed<'de>>(
+ self,
+ seed: V,
+ ) -> Result<(V::Value, Self::Variant), Self::Error> {
+ let k = seed.deserialize(self)?;
+ Ok((k, Deserializer(&Value::Null)))
+ }
+}
+
+impl<'a, 'de> de::VariantAccess<'de> for Deserializer<&'a Value> {
+ type Error = Error;
+
+ #[inline]
+ fn unit_variant(self) -> Result<(), Self::Error> {
+ match self.0 {
+ Value::Null => Ok(()),
+ _ => Err(de::Error::invalid_type(self.0.into(), &"unit")),
+ }
+ }
+
+ #[inline]
+ fn newtype_variant_seed<U: de::DeserializeSeed<'de>>(
+ self,
+ seed: U,
+ ) -> Result<U::Value, Self::Error> {
+ seed.deserialize(self)
+ }
+
+ #[inline]
+ fn tuple_variant<V: de::Visitor<'de>>(
+ self,
+ _len: usize,
+ visitor: V,
+ ) -> Result<V::Value, Self::Error> {
+ self.deserialize_seq(visitor)
+ }
+
+ #[inline]
+ fn struct_variant<V: de::Visitor<'de>>(
+ self,
+ _fields: &'static [&'static str],
+ visitor: V,
+ ) -> Result<V::Value, Self::Error> {
+ self.deserialize_map(visitor)
+ }
+}
+
+impl Value {
+ /// Deserializes the `Value` into an object
+ #[inline]
+ pub fn deserialized<'de, T: de::Deserialize<'de>>(&self) -> Result<T, Error> {
+ T::deserialize(Deserializer(self))
+ }
+}
diff --git a/src/value/error.rs b/src/value/error.rs
new file mode 100644
index 0000000..bcec128
--- /dev/null
+++ b/src/value/error.rs
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use alloc::string::{String, ToString};
+
+/// The error when serializing to/from a `Value`
+#[derive(Debug)]
+pub enum Error {
+ /// A custom error string produced by serde
+ Custom(String),
+}
+
+impl core::fmt::Display for Error {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ write!(f, "{:?}", self)
+ }
+}
+
+impl serde::de::StdError for Error {}
+
+impl serde::de::Error for Error {
+ #[inline]
+ fn custom<T: core::fmt::Display>(msg: T) -> Self {
+ Self::Custom(msg.to_string())
+ }
+}
+
+impl serde::ser::Error for Error {
+ #[inline]
+ fn custom<T: core::fmt::Display>(msg: T) -> Self {
+ Self::Custom(msg.to_string())
+ }
+}
diff --git a/src/value/integer.rs b/src/value/integer.rs
new file mode 100644
index 0000000..4dd1f3a
--- /dev/null
+++ b/src/value/integer.rs
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: Apache-2.0
+
+macro_rules! implfrom {
+ ($( $(#[$($attr:meta)+])? $t:ident)+) => {
+ $(
+ $(#[$($attr)+])?
+ impl From<$t> for Integer {
+ #[inline]
+ fn from(value: $t) -> Self {
+ Self(value as _)
+ }
+ }
+
+ impl TryFrom<Integer> for $t {
+ type Error = core::num::TryFromIntError;
+
+ #[inline]
+ fn try_from(value: Integer) -> Result<Self, Self::Error> {
+ $t::try_from(value.0)
+ }
+ }
+ )+
+ };
+}
+
+/// An abstract integer value
+///
+/// This opaque type represents an integer value which can be encoded in CBOR
+/// without resulting to big integer encoding. Larger values may be encoded
+/// using the big integer encoding as described in the CBOR RFC. See the
+/// implementations for 128-bit integer conversions on `Value` for more
+/// details.
+#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Integer(i128);
+
+implfrom! {
+ u8 u16 u32 u64
+ i8 i16 i32 i64
+
+ #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))]
+ usize
+
+ #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))]
+ isize
+}
+
+impl TryFrom<i128> for Integer {
+ type Error = core::num::TryFromIntError;
+
+ #[inline]
+ fn try_from(value: i128) -> Result<Self, Self::Error> {
+ u64::try_from(match value.is_negative() {
+ false => value,
+ true => value ^ !0,
+ })?;
+
+ Ok(Integer(value))
+ }
+}
+
+impl TryFrom<u128> for Integer {
+ type Error = core::num::TryFromIntError;
+
+ #[inline]
+ fn try_from(value: u128) -> Result<Self, Self::Error> {
+ Ok(Self(u64::try_from(value)?.into()))
+ }
+}
+
+impl From<Integer> for i128 {
+ #[inline]
+ fn from(value: Integer) -> Self {
+ value.0
+ }
+}
+
+impl TryFrom<Integer> for u128 {
+ type Error = core::num::TryFromIntError;
+
+ #[inline]
+ fn try_from(value: Integer) -> Result<Self, Self::Error> {
+ u128::try_from(value.0)
+ }
+}
diff --git a/src/value/mod.rs b/src/value/mod.rs
new file mode 100644
index 0000000..40988b0
--- /dev/null
+++ b/src/value/mod.rs
@@ -0,0 +1,522 @@
+// SPDX-License-Identifier: Apache-2.0
+
+//! A dynamic CBOR value
+
+mod integer;
+
+mod de;
+mod error;
+mod ser;
+
+pub use error::Error;
+pub use integer::Integer;
+
+use alloc::{boxed::Box, string::String, vec::Vec};
+
+/// A representation of a dynamic CBOR value that can handled dynamically
+#[non_exhaustive]
+#[derive(Clone, Debug, PartialEq, PartialOrd)]
+pub enum Value {
+ /// An integer
+ Integer(Integer),
+
+ /// Bytes
+ Bytes(Vec<u8>),
+
+ /// A float
+ Float(f64),
+
+ /// A string
+ Text(String),
+
+ /// A boolean
+ Bool(bool),
+
+ /// Null
+ Null,
+
+ /// Tag
+ Tag(u64, Box<Value>),
+
+ /// An array
+ Array(Vec<Value>),
+
+ /// A map
+ Map(Vec<(Value, Value)>),
+}
+
+impl Value {
+ /// Returns true if the `Value` is an `Integer`. Returns false otherwise.
+ ///
+ /// ```
+ /// # use ciborium::value::Value;
+ /// #
+ /// let value = Value::Integer(17.into());
+ ///
+ /// assert!(value.is_integer());
+ /// ```
+ pub fn is_integer(&self) -> bool {
+ self.as_integer().is_some()
+ }
+
+ /// If the `Value` is a `Integer`, returns a reference to the associated `Integer` data.
+ /// Returns None otherwise.
+ ///
+ /// ```
+ /// # use ciborium::value::Value;
+ /// #
+ /// let value = Value::Integer(17.into());
+ ///
+ /// // We can read the number
+ /// assert_eq!(17, value.as_integer().unwrap().try_into().unwrap());
+ /// ```
+ pub fn as_integer(&self) -> Option<Integer> {
+ match self {
+ Value::Integer(int) => Some(*int),
+ _ => None,
+ }
+ }
+
+ /// Returns true if the `Value` is a `Bytes`. Returns false otherwise.
+ ///
+ /// ```
+ /// # use ciborium::value::Value;
+ /// #
+ /// let value = Value::Bytes(vec![104, 101, 108, 108, 111]);
+ ///
+ /// assert!(value.is_bytes());
+ /// ```
+ pub fn is_bytes(&self) -> bool {
+ self.as_bytes().is_some()
+ }
+
+ /// If the `Value` is a `Bytes`, returns a reference to the associated bytes vector.
+ /// Returns None otherwise.
+ ///
+ /// ```
+ /// # use ciborium::value::Value;
+ /// #
+ /// let value = Value::Bytes(vec![104, 101, 108, 108, 111]);
+ ///
+ /// assert_eq!(std::str::from_utf8(value.as_bytes().unwrap()).unwrap(), "hello");
+ /// ```
+ pub fn as_bytes(&self) -> Option<&Vec<u8>> {
+ match *self {
+ Value::Bytes(ref bytes) => Some(bytes),
+ _ => None,
+ }
+ }
+
+ /// If the `Value` is a `Bytes`, returns a mutable reference to the associated bytes vector.
+ /// Returns None otherwise.
+ ///
+ /// ```
+ /// # use ciborium::value::Value;
+ /// #
+ /// let mut value = Value::Bytes(vec![104, 101, 108, 108, 111]);
+ /// value.as_bytes_mut().unwrap().clear();
+ ///
+ /// assert_eq!(value, Value::Bytes(vec![]));
+ /// ```
+ pub fn as_bytes_mut(&mut self) -> Option<&mut Vec<u8>> {
+ match *self {
+ Value::Bytes(ref mut bytes) => Some(bytes),
+ _ => None,
+ }
+ }
+
+ /// Returns true if the `Value` is a `Float`. Returns false otherwise.
+ ///
+ /// ```
+ /// # use ciborium::value::Value;
+ /// #
+ /// let value = Value::Float(17.0.into());
+ ///
+ /// assert!(value.is_float());
+ /// ```
+ pub fn is_float(&self) -> bool {
+ self.as_float().is_some()
+ }
+
+ /// If the `Value` is a `Float`, returns a reference to the associated float data.
+ /// Returns None otherwise.
+ ///
+ /// ```
+ /// # use ciborium::value::Value;
+ /// #
+ /// let value = Value::Float(17.0.into());
+ ///
+ /// // We can read the float number
+ /// assert_eq!(value.as_float().unwrap(), 17.0_f64);
+ /// ```
+ pub fn as_float(&self) -> Option<f64> {
+ match *self {
+ Value::Float(f) => Some(f),
+ _ => None,
+ }
+ }
+
+ /// Returns true if the `Value` is a `Text`. Returns false otherwise.
+ ///
+ /// ```
+ /// # use ciborium::value::Value;
+ /// #
+ /// let value = Value::Text(String::from("hello"));
+ ///
+ /// assert!(value.is_text());
+ /// ```
+ pub fn is_text(&self) -> bool {
+ self.as_text().is_some()
+ }
+
+ /// If the `Value` is a `Text`, returns a reference to the associated `String` data.
+ /// Returns None otherwise.
+ ///
+ /// ```
+ /// # use ciborium::value::Value;
+ /// #
+ /// let value = Value::Text(String::from("hello"));
+ ///
+ /// // We can read the String
+ /// assert_eq!(value.as_text().unwrap(), "hello");
+ /// ```
+ pub fn as_text(&self) -> Option<&str> {
+ match *self {
+ Value::Text(ref s) => Some(s),
+ _ => None,
+ }
+ }
+
+ /// If the `Value` is a `Text`, returns a mutable reference to the associated `String` data.
+ /// Returns None otherwise.
+ ///
+ /// ```
+ /// # use ciborium::value::Value;
+ /// #
+ /// let mut value = Value::Text(String::from("hello"));
+ /// value.as_text_mut().unwrap().clear();
+ ///
+ /// assert_eq!(value.as_text().unwrap(), &String::from(""));
+ /// ```
+ pub fn as_text_mut(&mut self) -> Option<&mut String> {
+ match *self {
+ Value::Text(ref mut s) => Some(s),
+ _ => None,
+ }
+ }
+
+ /// Returns true if the `Value` is a `Bool`. Returns false otherwise.
+ ///
+ /// ```
+ /// # use ciborium::value::Value;
+ /// #
+ /// let value = Value::Bool(false);
+ ///
+ /// assert!(value.is_bool());
+ /// ```
+ pub fn is_bool(&self) -> bool {
+ self.as_bool().is_some()
+ }
+
+ /// If the `Value` is a `Bool`, returns a copy of the associated boolean value. Returns None
+ /// otherwise.
+ ///
+ /// ```
+ /// # use ciborium::value::Value;
+ /// #
+ /// let value = Value::Bool(false);
+ ///
+ /// assert_eq!(value.as_bool().unwrap(), false);
+ /// ```
+ pub fn as_bool(&self) -> Option<bool> {
+ match *self {
+ Value::Bool(b) => Some(b),
+ _ => None,
+ }
+ }
+
+ /// Returns true if the `Value` is a `Null`. Returns false otherwise.
+ ///
+ /// ```
+ /// # use ciborium::value::Value;
+ /// #
+ /// let value = Value::Null;
+ ///
+ /// assert!(value.is_null());
+ /// ```
+ pub fn is_null(&self) -> bool {
+ matches!(self, Value::Null)
+ }
+
+ /// Returns true if the `Value` is a `Tag`. Returns false otherwise.
+ ///
+ /// ```
+ /// # use ciborium::value::Value;
+ /// #
+ /// let value = Value::Tag(61, Box::from(Value::Null));
+ ///
+ /// assert!(value.is_tag());
+ /// ```
+ pub fn is_tag(&self) -> bool {
+ self.as_tag().is_some()
+ }
+
+ /// If the `Value` is a `Tag`, returns the associated tag value and a reference to the tag `Value`.
+ /// Returns None otherwise.
+ ///
+ /// ```
+ /// # use ciborium::value::Value;
+ /// #
+ /// let value = Value::Tag(61, Box::from(Value::Bytes(vec![104, 101, 108, 108, 111])));
+ ///
+ /// let (tag, data) = value.as_tag().unwrap();
+ /// assert_eq!(tag, 61);
+ /// assert_eq!(data, &Value::Bytes(vec![104, 101, 108, 108, 111]));
+ /// ```
+ pub fn as_tag(&self) -> Option<(u64, &Value)> {
+ match self {
+ Value::Tag(tag, data) => Some((*tag, data)),
+ _ => None,
+ }
+ }
+
+ /// If the `Value` is a `Tag`, returns the associated tag value and a mutable reference
+ /// to the tag `Value`. Returns None otherwise.
+ ///
+ /// ```
+ /// # use ciborium::value::Value;
+ /// #
+ /// let mut value = Value::Tag(61, Box::from(Value::Bytes(vec![104, 101, 108, 108, 111])));
+ ///
+ /// let (tag, mut data) = value.as_tag_mut().unwrap();
+ /// data.as_bytes_mut().unwrap().clear();
+ /// assert_eq!(tag, &61);
+ /// assert_eq!(data, &Value::Bytes(vec![]));
+ /// ```
+ pub fn as_tag_mut(&mut self) -> Option<(&mut u64, &mut Value)> {
+ match self {
+ Value::Tag(tag, data) => Some((tag, data.as_mut())),
+ _ => None,
+ }
+ }
+
+ /// Returns true if the `Value` is an Array. Returns false otherwise.
+ ///
+ /// ```
+ /// # use ciborium::value::Value;
+ /// #
+ /// let value = Value::Array(
+ /// vec![
+ /// Value::Text(String::from("foo")),
+ /// Value::Text(String::from("bar"))
+ /// ]
+ /// );
+ ///
+ /// assert!(value.is_array());
+ /// ```
+ pub fn is_array(&self) -> bool {
+ self.as_array().is_some()
+ }
+
+ /// If the `Value` is an Array, returns a reference to the associated vector. Returns None
+ /// otherwise.
+ ///
+ /// ```
+ /// # use ciborium::value::Value;
+ /// #
+ /// let value = Value::Array(
+ /// vec![
+ /// Value::Text(String::from("foo")),
+ /// Value::Text(String::from("bar"))
+ /// ]
+ /// );
+ ///
+ /// // The length of `value` is 2 elements.
+ /// assert_eq!(value.as_array().unwrap().len(), 2);
+ /// ```
+ pub fn as_array(&self) -> Option<&Vec<Value>> {
+ match *self {
+ Value::Array(ref array) => Some(&*array),
+ _ => None,
+ }
+ }
+
+ /// If the `Value` is an Array, returns a mutable reference to the associated vector.
+ /// Returns None otherwise.
+ ///
+ /// ```
+ /// # use ciborium::value::Value;
+ /// #
+ /// let mut value = Value::Array(
+ /// vec![
+ /// Value::Text(String::from("foo")),
+ /// Value::Text(String::from("bar"))
+ /// ]
+ /// );
+ ///
+ /// value.as_array_mut().unwrap().clear();
+ /// assert_eq!(value, Value::Array(vec![]));
+ /// ```
+ pub fn as_array_mut(&mut self) -> Option<&mut Vec<Value>> {
+ match *self {
+ Value::Array(ref mut list) => Some(list),
+ _ => None,
+ }
+ }
+
+ /// Returns true if the `Value` is a Map. Returns false otherwise.
+ ///
+ /// ```
+ /// # use ciborium::value::Value;
+ /// #
+ /// let value = Value::Map(
+ /// vec![
+ /// (Value::Text(String::from("foo")), Value::Text(String::from("bar")))
+ /// ]
+ /// );
+ ///
+ /// assert!(value.is_map());
+ /// ```
+ pub fn is_map(&self) -> bool {
+ self.as_map().is_some()
+ }
+
+ /// If the `Value` is a Map, returns a reference to the associated Map data. Returns None
+ /// otherwise.
+ ///
+ /// ```
+ /// # use ciborium::value::Value;
+ /// #
+ /// let value = Value::Map(
+ /// vec![
+ /// (Value::Text(String::from("foo")), Value::Text(String::from("bar")))
+ /// ]
+ /// );
+ ///
+ /// // The length of data is 1 entry (1 key/value pair).
+ /// assert_eq!(value.as_map().unwrap().len(), 1);
+ ///
+ /// // The content of the first element is what we expect
+ /// assert_eq!(
+ /// value.as_map().unwrap().get(0).unwrap(),
+ /// &(Value::Text(String::from("foo")), Value::Text(String::from("bar")))
+ /// );
+ /// ```
+ pub fn as_map(&self) -> Option<&Vec<(Value, Value)>> {
+ match *self {
+ Value::Map(ref map) => Some(map),
+ _ => None,
+ }
+ }
+
+ /// If the `Value` is a Map, returns a mutable reference to the associated Map Data.
+ /// Returns None otherwise.
+ ///
+ /// ```
+ /// # use ciborium::value::Value;
+ /// #
+ /// let mut value = Value::Map(
+ /// vec![
+ /// (Value::Text(String::from("foo")), Value::Text(String::from("bar")))
+ /// ]
+ /// );
+ ///
+ /// value.as_map_mut().unwrap().clear();
+ /// assert_eq!(value, Value::Map(vec![]));
+ /// assert_eq!(value.as_map().unwrap().len(), 0);
+ /// ```
+ pub fn as_map_mut(&mut self) -> Option<&mut Vec<(Value, Value)>> {
+ match *self {
+ Value::Map(ref mut map) => Some(map),
+ _ => None,
+ }
+ }
+}
+
+macro_rules! implfrom {
+ ($($v:ident($t:ty)),+ $(,)?) => {
+ $(
+ impl From<$t> for Value {
+ #[inline]
+ fn from(value: $t) -> Self {
+ Self::$v(value.into())
+ }
+ }
+ )+
+ };
+}
+
+implfrom! {
+ Integer(Integer),
+ Integer(u64),
+ Integer(i64),
+ Integer(u32),
+ Integer(i32),
+ Integer(u16),
+ Integer(i16),
+ Integer(u8),
+ Integer(i8),
+
+ Bytes(Vec<u8>),
+ Bytes(&[u8]),
+
+ Float(f64),
+ Float(f32),
+
+ Text(String),
+ Text(&str),
+
+ Bool(bool),
+
+ Array(&[Value]),
+ Array(Vec<Value>),
+
+ Map(&[(Value, Value)]),
+ Map(Vec<(Value, Value)>),
+}
+
+impl From<u128> for Value {
+ #[inline]
+ fn from(value: u128) -> Self {
+ if let Ok(x) = Integer::try_from(value) {
+ return Value::Integer(x);
+ }
+
+ let mut bytes = &value.to_be_bytes()[..];
+ while let Some(0) = bytes.get(0) {
+ bytes = &bytes[1..];
+ }
+
+ Value::Tag(ciborium_ll::tag::BIGPOS, Value::Bytes(bytes.into()).into())
+ }
+}
+
+impl From<i128> for Value {
+ #[inline]
+ fn from(value: i128) -> Self {
+ if let Ok(x) = Integer::try_from(value) {
+ return Value::Integer(x);
+ }
+
+ let (tag, raw) = match value.is_negative() {
+ true => (ciborium_ll::tag::BIGNEG, value as u128 ^ !0),
+ false => (ciborium_ll::tag::BIGPOS, value as u128),
+ };
+
+ let mut bytes = &raw.to_be_bytes()[..];
+ while let Some(0) = bytes.get(0) {
+ bytes = &bytes[1..];
+ }
+
+ Value::Tag(tag, Value::Bytes(bytes.into()).into())
+ }
+}
+
+impl From<char> for Value {
+ #[inline]
+ fn from(value: char) -> Self {
+ let mut v = String::with_capacity(1);
+ v.push(value);
+ Value::Text(v)
+ }
+}
diff --git a/src/value/ser.rs b/src/value/ser.rs
new file mode 100644
index 0000000..6c739d3
--- /dev/null
+++ b/src/value/ser.rs
@@ -0,0 +1,434 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use super::{Error, Value};
+
+use alloc::{vec, vec::Vec};
+
+use ::serde::ser::{self, SerializeMap as _, SerializeSeq as _, SerializeTupleVariant as _};
+
+impl ser::Serialize for Value {
+ #[inline]
+ fn serialize<S: ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+ match self {
+ Value::Bytes(x) => serializer.serialize_bytes(x),
+ Value::Bool(x) => serializer.serialize_bool(*x),
+ Value::Text(x) => serializer.serialize_str(x),
+ Value::Null => serializer.serialize_unit(),
+
+ Value::Tag(t, v) => {
+ let mut acc = serializer.serialize_tuple_variant("@@TAG@@", 0, "@@TAGGED@@", 2)?;
+ acc.serialize_field(t)?;
+ acc.serialize_field(v)?;
+ acc.end()
+ }
+
+ Value::Float(x) => {
+ let y = *x as f32;
+ if (y as f64).to_bits() == x.to_bits() {
+ serializer.serialize_f32(y)
+ } else {
+ serializer.serialize_f64(*x)
+ }
+ }
+
+ Value::Integer(x) => {
+ if let Ok(x) = u8::try_from(*x) {
+ serializer.serialize_u8(x)
+ } else if let Ok(x) = i8::try_from(*x) {
+ serializer.serialize_i8(x)
+ } else if let Ok(x) = u16::try_from(*x) {
+ serializer.serialize_u16(x)
+ } else if let Ok(x) = i16::try_from(*x) {
+ serializer.serialize_i16(x)
+ } else if let Ok(x) = u32::try_from(*x) {
+ serializer.serialize_u32(x)
+ } else if let Ok(x) = i32::try_from(*x) {
+ serializer.serialize_i32(x)
+ } else if let Ok(x) = u64::try_from(*x) {
+ serializer.serialize_u64(x)
+ } else if let Ok(x) = i64::try_from(*x) {
+ serializer.serialize_i64(x)
+ } else if let Ok(x) = u128::try_from(*x) {
+ serializer.serialize_u128(x)
+ } else if let Ok(x) = i128::try_from(*x) {
+ serializer.serialize_i128(x)
+ } else {
+ unreachable!()
+ }
+ }
+
+ Value::Array(x) => {
+ let mut map = serializer.serialize_seq(Some(x.len()))?;
+
+ for v in x {
+ map.serialize_element(v)?;
+ }
+
+ map.end()
+ }
+
+ Value::Map(x) => {
+ let mut map = serializer.serialize_map(Some(x.len()))?;
+
+ for (k, v) in x {
+ map.serialize_entry(k, v)?;
+ }
+
+ map.end()
+ }
+ }
+ }
+}
+
+macro_rules! mkserialize {
+ ($($f:ident($v:ty)),+ $(,)?) => {
+ $(
+ #[inline]
+ fn $f(self, v: $v) -> Result<Self::Ok, Self::Error> {
+ Ok(v.into())
+ }
+ )+
+ };
+}
+
+struct Tagged {
+ tag: Option<u64>,
+ val: Option<Value>,
+}
+
+struct Named<T> {
+ name: &'static str,
+ data: T,
+ tag: Option<Tagged>,
+}
+
+struct Map {
+ data: Vec<(Value, Value)>,
+ temp: Option<Value>,
+}
+
+struct Serializer<T>(T);
+
+impl ser::Serializer for Serializer<()> {
+ type Ok = Value;
+ type Error = Error;
+
+ type SerializeSeq = Serializer<Vec<Value>>;
+ type SerializeTuple = Serializer<Vec<Value>>;
+ type SerializeTupleStruct = Serializer<Vec<Value>>;
+ type SerializeTupleVariant = Serializer<Named<Vec<Value>>>;
+ type SerializeMap = Serializer<Map>;
+ type SerializeStruct = Serializer<Vec<(Value, Value)>>;
+ type SerializeStructVariant = Serializer<Named<Vec<(Value, Value)>>>;
+
+ mkserialize! {
+ serialize_bool(bool),
+
+ serialize_f32(f32),
+ serialize_f64(f64),
+
+ serialize_i8(i8),
+ serialize_i16(i16),
+ serialize_i32(i32),
+ serialize_i64(i64),
+ serialize_i128(i128),
+ serialize_u8(u8),
+ serialize_u16(u16),
+ serialize_u32(u32),
+ serialize_u64(u64),
+ serialize_u128(u128),
+
+ serialize_char(char),
+ serialize_str(&str),
+ serialize_bytes(&[u8]),
+ }
+
+ #[inline]
+ fn serialize_none(self) -> Result<Value, Error> {
+ Ok(Value::Null)
+ }
+
+ #[inline]
+ fn serialize_some<U: ?Sized + ser::Serialize>(self, value: &U) -> Result<Value, Error> {
+ value.serialize(self)
+ }
+
+ #[inline]
+ fn serialize_unit(self) -> Result<Value, Error> {
+ self.serialize_none()
+ }
+
+ #[inline]
+ fn serialize_unit_struct(self, _name: &'static str) -> Result<Value, Error> {
+ self.serialize_unit()
+ }
+
+ #[inline]
+ fn serialize_unit_variant(
+ self,
+ _name: &'static str,
+ _index: u32,
+ variant: &'static str,
+ ) -> Result<Value, Error> {
+ Ok(variant.into())
+ }
+
+ #[inline]
+ fn serialize_newtype_struct<U: ?Sized + ser::Serialize>(
+ self,
+ _name: &'static str,
+ value: &U,
+ ) -> Result<Value, Error> {
+ value.serialize(self)
+ }
+
+ #[inline]
+ fn serialize_newtype_variant<U: ?Sized + ser::Serialize>(
+ self,
+ name: &'static str,
+ _index: u32,
+ variant: &'static str,
+ value: &U,
+ ) -> Result<Value, Error> {
+ Ok(match (name, variant) {
+ ("@@TAG@@", "@@UNTAGGED@@") => Value::serialized(value)?,
+ _ => vec![(variant.into(), Value::serialized(value)?)].into(),
+ })
+ }
+
+ #[inline]
+ fn serialize_seq(self, length: Option<usize>) -> Result<Self::SerializeSeq, Error> {
+ Ok(Serializer(Vec::with_capacity(length.unwrap_or(0))))
+ }
+
+ #[inline]
+ fn serialize_tuple(self, length: usize) -> Result<Self::SerializeTuple, Error> {
+ self.serialize_seq(Some(length))
+ }
+
+ #[inline]
+ fn serialize_tuple_struct(
+ self,
+ _name: &'static str,
+ length: usize,
+ ) -> Result<Self::SerializeTupleStruct, Error> {
+ self.serialize_seq(Some(length))
+ }
+
+ #[inline]
+ fn serialize_tuple_variant(
+ self,
+ name: &'static str,
+ _index: u32,
+ variant: &'static str,
+ length: usize,
+ ) -> Result<Self::SerializeTupleVariant, Error> {
+ Ok(Serializer(Named {
+ name: variant,
+ data: Vec::with_capacity(length),
+ tag: match (name, variant) {
+ ("@@TAG@@", "@@TAGGED@@") => Some(Tagged {
+ tag: None,
+ val: None,
+ }),
+
+ _ => None,
+ },
+ }))
+ }
+
+ #[inline]
+ fn serialize_map(self, length: Option<usize>) -> Result<Self::SerializeMap, Error> {
+ Ok(Serializer(Map {
+ data: Vec::with_capacity(length.unwrap_or(0)),
+ temp: None,
+ }))
+ }
+
+ #[inline]
+ fn serialize_struct(
+ self,
+ _name: &'static str,
+ length: usize,
+ ) -> Result<Self::SerializeStruct, Error> {
+ Ok(Serializer(Vec::with_capacity(length)))
+ }
+
+ #[inline]
+ fn serialize_struct_variant(
+ self,
+ _name: &'static str,
+ _index: u32,
+ variant: &'static str,
+ length: usize,
+ ) -> Result<Self::SerializeStructVariant, Error> {
+ Ok(Serializer(Named {
+ name: variant,
+ data: Vec::with_capacity(length),
+ tag: None,
+ }))
+ }
+}
+
+impl<'a> ser::SerializeSeq for Serializer<Vec<Value>> {
+ type Ok = Value;
+ type Error = Error;
+
+ #[inline]
+ fn serialize_element<U: ?Sized + ser::Serialize>(&mut self, value: &U) -> Result<(), Error> {
+ self.0.push(Value::serialized(&value)?);
+ Ok(())
+ }
+
+ #[inline]
+ fn end(self) -> Result<Self::Ok, Self::Error> {
+ Ok(self.0.into())
+ }
+}
+
+impl<'a> ser::SerializeTuple for Serializer<Vec<Value>> {
+ type Ok = Value;
+ type Error = Error;
+
+ #[inline]
+ fn serialize_element<U: ?Sized + ser::Serialize>(&mut self, value: &U) -> Result<(), Error> {
+ self.0.push(Value::serialized(&value)?);
+ Ok(())
+ }
+
+ #[inline]
+ fn end(self) -> Result<Self::Ok, Self::Error> {
+ Ok(self.0.into())
+ }
+}
+
+impl<'a> ser::SerializeTupleStruct for Serializer<Vec<Value>> {
+ type Ok = Value;
+ type Error = Error;
+
+ #[inline]
+ fn serialize_field<U: ?Sized + ser::Serialize>(&mut self, value: &U) -> Result<(), Error> {
+ self.0.push(Value::serialized(&value)?);
+ Ok(())
+ }
+
+ #[inline]
+ fn end(self) -> Result<Self::Ok, Self::Error> {
+ Ok(self.0.into())
+ }
+}
+
+impl<'a> ser::SerializeTupleVariant for Serializer<Named<Vec<Value>>> {
+ type Ok = Value;
+ type Error = Error;
+
+ #[inline]
+ fn serialize_field<U: ?Sized + ser::Serialize>(&mut self, value: &U) -> Result<(), Error> {
+ match self.0.tag.as_mut() {
+ Some(tag) => match tag.tag {
+ None => match value.serialize(crate::tag::Serializer) {
+ Ok(t) => tag.tag = Some(t),
+ Err(..) => return Err(ser::Error::custom("expected tag")),
+ },
+
+ Some(..) => tag.val = Some(Value::serialized(value)?),
+ },
+
+ None => self.0.data.push(Value::serialized(value)?),
+ }
+
+ Ok(())
+ }
+
+ #[inline]
+ fn end(self) -> Result<Self::Ok, Self::Error> {
+ Ok(match self.0.tag {
+ Some(tag) => match tag {
+ Tagged {
+ tag: Some(t),
+ val: Some(v),
+ } => Value::Tag(t, v.into()),
+ _ => return Err(ser::Error::custom("invalid tag input")),
+ },
+
+ None => vec![(self.0.name.into(), self.0.data.into())].into(),
+ })
+ }
+}
+
+impl<'a> ser::SerializeMap for Serializer<Map> {
+ type Ok = Value;
+ type Error = Error;
+
+ #[inline]
+ fn serialize_key<U: ?Sized + ser::Serialize>(&mut self, key: &U) -> Result<(), Error> {
+ self.0.temp = Some(Value::serialized(key)?);
+ Ok(())
+ }
+
+ #[inline]
+ fn serialize_value<U: ?Sized + ser::Serialize>(&mut self, value: &U) -> Result<(), Error> {
+ let key = self.0.temp.take().unwrap();
+ let val = Value::serialized(&value)?;
+
+ self.0.data.push((key, val));
+ Ok(())
+ }
+
+ #[inline]
+ fn end(self) -> Result<Self::Ok, Self::Error> {
+ Ok(self.0.data.into())
+ }
+}
+
+impl<'a> ser::SerializeStruct for Serializer<Vec<(Value, Value)>> {
+ type Ok = Value;
+ type Error = Error;
+
+ #[inline]
+ fn serialize_field<U: ?Sized + ser::Serialize>(
+ &mut self,
+ key: &'static str,
+ value: &U,
+ ) -> Result<(), Error> {
+ let k = Value::serialized(&key)?;
+ let v = Value::serialized(&value)?;
+ self.0.push((k, v));
+ Ok(())
+ }
+
+ #[inline]
+ fn end(self) -> Result<Self::Ok, Self::Error> {
+ Ok(self.0.into())
+ }
+}
+
+impl<'a> ser::SerializeStructVariant for Serializer<Named<Vec<(Value, Value)>>> {
+ type Ok = Value;
+ type Error = Error;
+
+ #[inline]
+ fn serialize_field<U: ?Sized + ser::Serialize>(
+ &mut self,
+ key: &'static str,
+ value: &U,
+ ) -> Result<(), Self::Error> {
+ let k = Value::serialized(&key)?;
+ let v = Value::serialized(&value)?;
+ self.0.data.push((k, v));
+ Ok(())
+ }
+
+ #[inline]
+ fn end(self) -> Result<Self::Ok, Self::Error> {
+ Ok(vec![(self.0.name.into(), self.0.data.into())].into())
+ }
+}
+
+impl Value {
+ /// Serializes an object into a `Value`
+ #[inline]
+ pub fn serialized<T: ?Sized + ser::Serialize>(value: &T) -> Result<Self, Error> {
+ value.serialize(Serializer(()))
+ }
+}
diff --git a/tests/codec.rs b/tests/codec.rs
new file mode 100644
index 0000000..d1f6026
--- /dev/null
+++ b/tests/codec.rs
@@ -0,0 +1,399 @@
+// SPDX-License-Identifier: Apache-2.0
+
+extern crate std;
+
+use std::collections::{BTreeMap, HashMap};
+use std::convert::TryFrom;
+use std::fmt::Debug;
+
+use ciborium::value::Value;
+use ciborium::{cbor, de::from_reader, ser::into_writer};
+
+use rstest::rstest;
+use serde::{Deserialize, Serialize};
+
+macro_rules! val {
+ ($x:expr) => {
+ Value::try_from($x).unwrap()
+ };
+}
+
+macro_rules! hex {
+ ($x:expr) => {
+ serde_bytes::ByteBuf::from(hex::decode($x).unwrap())
+ };
+}
+
+macro_rules! map {
+ ($($k:expr => $v:expr),*) => {{
+ let mut map = BTreeMap::new();
+ $(
+ map.insert($k, $v);
+ )*
+ map
+ }}
+}
+
+// Keep the first "case" aligned to a line number ending in 1 for ease in finding tests.
+#[allow(clippy::excessive_precision)]
+#[rstest(input, value, bytes, alternate, equality,
+
+ case(0u8, val!(0u8), "00", false, same),
+ case(0u16, val!(0u16), "00", false, same),
+ case(0u32, val!(0u32), "00", false, same),
+ case(0u64, val!(0u64), "00", false, same),
+ case(0u128, val!(0u128), "00", false, same),
+ case(0i8, val!(0i8), "00", false, same),
+ case(0i16, val!(0i16), "00", false, same),
+ case(0i32, val!(0i32), "00", false, same),
+ case(0i64, val!(0i64), "00", false, same),
+ case(0i128, val!(0i128), "00", false, same),
+ case(1u8, val!(1u8), "01", false, same),
+ case(1u16, val!(1u16), "01", false, same),
+ case(1u32, val!(1u32), "01", false, same),
+ case(1u64, val!(1u64), "01", false, same),
+ case(1u128, val!(1u128), "01", false, same),
+ case(1i8, val!(1i8), "01", false, same),
+ case(1i16, val!(1i16), "01", false, same),
+ case(1i32, val!(1i32), "01", false, same),
+ case(1i64, val!(1i64), "01", false, same),
+ case(1i128, val!(1i128), "01", false, same),
+ case(1u8, val!(1u8), "1b0000000000000001", true, same),
+ case(1u16, val!(1u16), "1b0000000000000001", true, same),
+ case(1u32, val!(1u32), "1b0000000000000001", true, same),
+ case(1u64, val!(1u64), "1b0000000000000001", true, same),
+ case(1u128, val!(1u128), "1b0000000000000001", true, same),
+ case(1i8, val!(1i8), "1b0000000000000001", true, same),
+ case(1i16, val!(1i16), "1b0000000000000001", true, same),
+ case(1i32, val!(1i32), "1b0000000000000001", true, same),
+ case(1i64, val!(1i64), "1b0000000000000001", true, same),
+ case(1i128, val!(1i128), "1b0000000000000001", true, same),
+ case(1u8, bigint(), "c2540000000000000000000000000000000000000001", true, same), // Not In RFC
+ case(1u16, bigint(), "c2540000000000000000000000000000000000000001", true, same), // Not In RFC
+ case(1u32, bigint(), "c2540000000000000000000000000000000000000001", true, same), // Not In RFC
+ case(1u64, bigint(), "c2540000000000000000000000000000000000000001", true, same), // Not In RFC
+ case(1u128, bigint(), "c2540000000000000000000000000000000000000001", true, same), // Not In RFC
+ case(1i8, bigint(), "c2540000000000000000000000000000000000000001", true, same), // Not In RFC
+ case(1i16, bigint(), "c2540000000000000000000000000000000000000001", true, same), // Not In RFC
+ case(1i32, bigint(), "c2540000000000000000000000000000000000000001", true, same), // Not In RFC
+ case(1i64, bigint(), "c2540000000000000000000000000000000000000001", true, same), // Not In RFC
+ case(1i128, bigint(), "c2540000000000000000000000000000000000000001", true, same), // Not In RFC
+ case(10u8, val!(10u8), "0a", false, same),
+ case(10u16, val!(10u16), "0a", false, same),
+ case(10u32, val!(10u32), "0a", false, same),
+ case(10u64, val!(10u64), "0a", false, same),
+ case(10u128, val!(10u128), "0a", false, same),
+ case(10i8, val!(10i8), "0a", false, same),
+ case(10i16, val!(10i16), "0a", false, same),
+ case(10i32, val!(10i32), "0a", false, same),
+ case(10i64, val!(10i64), "0a", false, same),
+ case(10i128, val!(10i128), "0a", false, same),
+ case(23u8, val!(23u8), "17", false, same),
+ case(23u16, val!(23u16), "17", false, same),
+ case(23u32, val!(23u32), "17", false, same),
+ case(23u64, val!(23u64), "17", false, same),
+ case(23u128, val!(23u128), "17", false, same),
+ case(23i8, val!(23i8), "17", false, same),
+ case(23i16, val!(23i16), "17", false, same),
+ case(23i32, val!(23i32), "17", false, same),
+ case(23i64, val!(23i64), "17", false, same),
+ case(23i128, val!(23i128), "17", false, same),
+ case(24u8, val!(24u8), "1818", false, same),
+ case(24u16, val!(24u16), "1818", false, same),
+ case(24u32, val!(24u32), "1818", false, same),
+ case(24u64, val!(24u64), "1818", false, same),
+ case(24u128, val!(24u128), "1818", false, same),
+ case(24i8, val!(24i8), "1818", false, same),
+ case(24i16, val!(24i16), "1818", false, same),
+ case(24i32, val!(24i32), "1818", false, same),
+ case(24i64, val!(24i64), "1818", false, same),
+ case(24i128, val!(24i128), "1818", false, same),
+ case(25u8, val!(25u8), "1819", false, same),
+ case(25u16, val!(25u16), "1819", false, same),
+ case(25u32, val!(25u32), "1819", false, same),
+ case(25u64, val!(25u64), "1819", false, same),
+ case(25u128, val!(25u128), "1819", false, same),
+ case(25i8, val!(25i8), "1819", false, same),
+ case(25i16, val!(25i16), "1819", false, same),
+ case(25i32, val!(25i32), "1819", false, same),
+ case(25i64, val!(25i64), "1819", false, same),
+ case(25i128, val!(25i128), "1819", false, same),
+ case(100u8, val!(100u8), "1864", false, same),
+ case(100u16, val!(100u16), "1864", false, same),
+ case(100u32, val!(100u32), "1864", false, same),
+ case(100u64, val!(100u64), "1864", false, same),
+ case(100u128, val!(100u128), "1864", false, same),
+ case(100i8, val!(100i8), "1864", false, same),
+ case(100i16, val!(100i16), "1864", false, same),
+ case(100i32, val!(100i32), "1864", false, same),
+ case(100i64, val!(100i64), "1864", false, same),
+ case(100i128, val!(100i128), "1864", false, same),
+ case(1000u16, val!(1000u16), "1903e8", false, same),
+ case(1000u32, val!(1000u32), "1903e8", false, same),
+ case(1000u64, val!(1000u64), "1903e8", false, same),
+ case(1000u128, val!(1000u128), "1903e8", false, same),
+ case(1000i16, val!(1000i16), "1903e8", false, same),
+ case(1000i32, val!(1000i32), "1903e8", false, same),
+ case(1000i64, val!(1000i64), "1903e8", false, same),
+ case(1000i128, val!(1000i128), "1903e8", false, same),
+ case(1000000u32, val!(1000000u32), "1a000f4240", false, same),
+ case(1000000u64, val!(1000000u64), "1a000f4240", false, same),
+ case(1000000u128, val!(1000000u128), "1a000f4240", false, same),
+ case(1000000i32, val!(1000000i32), "1a000f4240", false, same),
+ case(1000000i64, val!(1000000i64), "1a000f4240", false, same),
+ case(1000000i128, val!(1000000i128), "1a000f4240", false, same),
+ case(1000000000000u64, val!(1000000000000u64), "1b000000e8d4a51000", false, same),
+ case(1000000000000u128, val!(1000000000000u128), "1b000000e8d4a51000", false, same),
+ case(1000000000000i64, val!(1000000000000i64), "1b000000e8d4a51000", false, same),
+ case(1000000000000i128, val!(1000000000000i128), "1b000000e8d4a51000", false, same),
+ case(18446744073709551615u64, val!(18446744073709551615u64), "1bffffffffffffffff", false, same),
+ case(18446744073709551615u128, val!(18446744073709551615u128), "1bffffffffffffffff", false, same),
+ case(18446744073709551615i128, val!(18446744073709551615i128), "1bffffffffffffffff", false, same),
+ case(18446744073709551616u128, val!(18446744073709551616u128), "c249010000000000000000", false, same),
+ case(18446744073709551616i128, val!(18446744073709551616i128), "c249010000000000000000", false, same),
+ case(-18446744073709551617i128, val!(-18446744073709551617i128), "c349010000000000000000", false, same),
+ case(-18446744073709551616i128, val!(-18446744073709551616i128), "3bffffffffffffffff", false, same),
+ case(-1000i16, val!(-1000i16), "3903e7", false, same),
+ case(-1000i32, val!(-1000i32), "3903e7", false, same),
+ case(-1000i64, val!(-1000i64), "3903e7", false, same),
+ case(-1000i128, val!(-1000i128), "3903e7", false, same),
+ case(-100i8, val!(-100i8), "3863", false, same),
+ case(-100i16, val!(-100i16), "3863", false, same),
+ case(-100i32, val!(-100i32), "3863", false, same),
+ case(-100i64, val!(-100i64), "3863", false, same),
+ case(-100i128, val!(-100i128), "3863", false, same),
+ case(-10i8, val!(-10i8), "29", false, same),
+ case(-10i16, val!(-10i16), "29", false, same),
+ case(-10i32, val!(-10i32), "29", false, same),
+ case(-10i64, val!(-10i64), "29", false, same),
+ case(-10i128, val!(-10i128), "29", false, same),
+ case(-1i8, val!(-1i8), "20", false, same),
+ case(-1i16, val!(-1i16), "20", false, same),
+ case(-1i32, val!(-1i32), "20", false, same),
+ case(-1i64, val!(-1i64), "20", false, same),
+ case(-1i128, val!(-1i128), "20", false, same),
+ case(-1i8, val!(-1i8), "3b0000000000000000", true, same),
+ case(-1i16, val!(-1i16), "3b0000000000000000", true, same),
+ case(-1i32, val!(-1i32), "3b0000000000000000", true, same),
+ case(-1i64, val!(-1i64), "3b0000000000000000", true, same),
+ case(-1i128, val!(-1i128), "3b0000000000000000", true, same),
+ case(0.0f32, val!(0.0f32), "f90000", false, Float),
+ case(0.0f64, val!(0.0f64), "f90000", false, Float),
+ case(-0.0f32, val!(-0.0f32), "f98000", false, Float),
+ case(-0.0f64, val!(-0.0f64), "f98000", false, Float),
+ case(1.0f32, val!(1.0f32), "f93c00", false, Float),
+ case(1.0f64, val!(1.0f64), "f93c00", false, Float),
+ case(1.1f32, val!(1.1f32), "fa3f8ccccd", false, Float), // Not In RFC
+ case(1.1f64, val!(1.1f64), "fb3ff199999999999a", false, Float),
+ case(1.5f32, val!(1.5f32), "f93e00", false, Float),
+ case(1.5f64, val!(1.5f64), "f93e00", false, Float),
+ case(65504.0f32, val!(65504.0f32), "f97bff", false, Float),
+ case(65504.0f64, val!(65504.0f64), "f97bff", false, Float),
+ case(100000.0f32, val!(100000.0f32), "fa47c35000", false, Float),
+ case(100000.0f64, val!(100000.0f64), "fa47c35000", false, Float),
+ case(3.4028234663852886e+38f32, val!(3.4028234663852886e+38f32), "fa7f7fffff", false, Float),
+ case(3.4028234663852886e+38f64, val!(3.4028234663852886e+38f64), "fa7f7fffff", false, Float),
+ case(1.0e+300f64, val!(1.0e+300f64), "fb7e37e43c8800759c", false, Float),
+ case(5.960464477539063e-8f32, val!(5.960464477539063e-8f32), "f90001", false, Float),
+ case(5.960464477539063e-8f64, val!(5.960464477539063e-8f64), "f90001", false, Float),
+ case(0.00006103515625f32, val!(0.00006103515625f32), "f90400", false, Float),
+ case(0.00006103515625f64, val!(0.00006103515625f64), "f90400", false, Float),
+ case(-4.0f32, val!(-4.0f32), "f9c400", false, Float),
+ case(-4.0f64, val!(-4.0f64), "f9c400", false, Float),
+ case(-4.1f32, val!(-4.1f32), "fac0833333", false, Float), // Not In RFC
+ case(-4.1f64, val!(-4.1f64), "fbc010666666666666", false, Float),
+ case(core::f32::INFINITY, val!(core::f32::INFINITY), "f97c00", false, Float),
+ case(core::f64::INFINITY, val!(core::f64::INFINITY), "f97c00", false, Float),
+ case(core::f32::INFINITY, val!(core::f32::INFINITY), "fa7f800000", true, Float),
+ case(core::f64::INFINITY, val!(core::f64::INFINITY), "fa7f800000", true, Float),
+ case(core::f32::INFINITY, val!(core::f32::INFINITY), "fb7ff0000000000000", true, Float),
+ case(core::f64::INFINITY, val!(core::f64::INFINITY), "fb7ff0000000000000", true, Float),
+ case(-core::f32::INFINITY, val!(-core::f32::INFINITY), "f9fc00", false, Float),
+ case(-core::f64::INFINITY, val!(-core::f64::INFINITY), "f9fc00", false, Float),
+ case(-core::f32::INFINITY, val!(-core::f32::INFINITY), "faff800000", true, Float),
+ case(-core::f64::INFINITY, val!(-core::f64::INFINITY), "faff800000", true, Float),
+ case(-core::f32::INFINITY, val!(-core::f32::INFINITY), "fbfff0000000000000", true, Float),
+ case(-core::f64::INFINITY, val!(-core::f64::INFINITY), "fbfff0000000000000", true, Float),
+ case(core::f32::NAN, val!(core::f32::NAN), "f97e00", false, Float),
+ case(core::f64::NAN, val!(core::f64::NAN), "f97e00", false, Float),
+ case(core::f32::NAN, val!(core::f32::NAN), "fa7fc00000", true, Float),
+ case(core::f64::NAN, val!(core::f64::NAN), "fa7fc00000", true, Float),
+ case(core::f32::NAN, val!(core::f32::NAN), "fb7ff8000000000000", true, Float),
+ case(core::f64::NAN, val!(core::f64::NAN), "fb7ff8000000000000", true, Float),
+ case(-core::f32::NAN, val!(-core::f32::NAN), "f9fe00", false, Float), // Not In RFC
+ case(-core::f64::NAN, val!(-core::f64::NAN), "f9fe00", false, Float), // Not In RFC
+ case(-core::f32::NAN, val!(-core::f32::NAN), "faffc00000", true, Float), // Not In RFC
+ case(-core::f64::NAN, val!(-core::f64::NAN), "faffc00000", true, Float), // Not In RFC
+ case(-core::f32::NAN, val!(-core::f32::NAN), "fbfff8000000000000", true, Float), // Not In RFC
+ case(-core::f64::NAN, val!(-core::f64::NAN), "fbfff8000000000000", true, Float), // Not In RFC
+ case(false, val!(false), "f4", false, same),
+ case(true, val!(true), "f5", false, same),
+ case(Value::Null, Value::Null, "f6", false, same),
+ case(hex!(""), val!(&b""[..]), "40", false, same),
+ case(hex!("01020304"), val!(&b"\x01\x02\x03\x04"[..]), "4401020304", false, same),
+ case(hex!("0102030405"), val!(&b"\x01\x02\x03\x04\x05"[..]), "5f42010243030405ff", true, same),
+ case("", val!(""), "60", false, ToOwned::to_owned),
+ case("a", val!("a"), "6161", false, ToOwned::to_owned),
+ case('a', val!('a'), "6161", false, same),
+ case("IETF", val!("IETF"), "6449455446", false, ToOwned::to_owned),
+ case("\"\\", val!("\"\\"), "62225c", false, ToOwned::to_owned),
+ case("ü", val!("ü"), "62c3bc", false, ToOwned::to_owned),
+ case('ü', val!('ü'), "62c3bc", false, same),
+ case("水", val!("水"), "63e6b0b4", false, ToOwned::to_owned),
+ case('水', val!('水'), "63e6b0b4", false, same),
+ case("𐅑", val!("𐅑"), "64f0908591", false, ToOwned::to_owned),
+ case('𐅑', val!('𐅑'), "64f0908591", false, same),
+ case("streaming", val!("streaming"), "7f657374726561646d696e67ff", true, ToOwned::to_owned),
+ case(cbor!([]).unwrap(), Vec::<Value>::new().into(), "80", false, same),
+ case(cbor!([]).unwrap(), Vec::<Value>::new().into(), "9fff", true, same),
+ case(cbor!([1, 2, 3]).unwrap(), cbor!([1, 2, 3]).unwrap(), "83010203", false, same),
+ case(cbor!([1, [2, 3], [4, 5]]).unwrap(), cbor!([1, [2, 3], [4, 5]]).unwrap(), "8301820203820405", false, same),
+ case(cbor!([1, [2, 3], [4, 5]]).unwrap(), cbor!([1, [2, 3], [4, 5]]).unwrap(), "9f018202039f0405ffff", true, same),
+ case(cbor!([1, [2, 3], [4, 5]]).unwrap(), cbor!([1, [2, 3], [4, 5]]).unwrap(), "9f01820203820405ff", true, same),
+ case(cbor!([1, [2, 3], [4, 5]]).unwrap(), cbor!([1, [2, 3], [4, 5]]).unwrap(), "83018202039f0405ff", true, same),
+ case(cbor!([1, [2, 3], [4, 5]]).unwrap(), cbor!([1, [2, 3], [4, 5]]).unwrap(), "83019f0203ff820405", true, same),
+ case((1..=25).collect::<Vec<u8>>(), (1..=25).map(|x| x.into()).collect::<Vec<Value>>().into(), "98190102030405060708090a0b0c0d0e0f101112131415161718181819", false, same),
+ case((1..=25).collect::<Vec<u8>>(), (1..=25).map(|x| x.into()).collect::<Vec<Value>>().into(), "9f0102030405060708090a0b0c0d0e0f101112131415161718181819ff", true, same),
+ case(HashMap::<u8, u8>::new(), Value::Map(vec![]), "a0", false, same),
+ case(BTreeMap::<u8, u8>::new(), Value::Map(vec![]), "a0", false, same),
+ case(map!{1 => 2, 3 => 4}, cbor!({1 => 2, 3 => 4}).unwrap(), "a201020304", false, same),
+ case(cbor!({"a" => 1, "b" => [2, 3]}).unwrap(), cbor!({"a" => 1, "b" => [2, 3]}).unwrap(), "a26161016162820203", false, same),
+ case(cbor!({"a" => 1, "b" => [2, 3]}).unwrap(), cbor!({"a" => 1, "b" => [2, 3]}).unwrap(), "bf61610161629f0203ffff", true, same),
+ case(cbor!(["a", {"b" => "c"}]).unwrap(), cbor!(["a", {"b" => "c"}]).unwrap(), "826161a161626163", false, same),
+ case(cbor!(["a", {"b" => "c"}]).unwrap(), cbor!(["a", {"b" => "c"}]).unwrap(), "826161bf61626163ff", true, same),
+ case(cbor!({"Fun" => true, "Amt" => -2}).unwrap(), cbor!({"Fun" => true, "Amt" => -2}).unwrap(), "bf6346756ef563416d7421ff", true, same),
+ case(map_big(), vmap_big(), "a56161614161626142616361436164614461656145", false, same),
+ case(Option::<u8>::None, Value::Null, "f6", false, same), // Not In RFC
+ case(Option::Some(7u8), val!(7u8), "07", false, same), // Not In RFC
+ case((), Value::Null, "f6", false, same), // Not In RFC
+ case(UnitStruct, Value::Null, "f6", false, same), // Not In RFC
+ case(Newtype(123), val!(123u8), "187b", false, same), // Not In RFC
+ case((22u8, 23u16), cbor!([22, 23]).unwrap(), "821617", false, same), // Not In RFC
+ case(TupleStruct(33, 34), cbor!([33, 34]).unwrap(), "8218211822", false, same), // Not In RFC
+ case(Enum::Unit, cbor!("Unit").unwrap(), "64556e6974", false, same), // Not In RFC
+ case(Enum::Newtype(45), cbor!({"Newtype" => 45}).unwrap(), "a1674e657774797065182d", false, same), // Not In RFC
+ 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>(
+ input: T,
+ value: Value,
+ bytes: &str,
+ alternate: bool,
+ equality: F,
+) {
+ let bytes = hex::decode(bytes).unwrap();
+
+ if !alternate {
+ let mut encoded = Vec::new();
+ into_writer(&input, &mut encoded).unwrap();
+ eprintln!("{:x?} == {:x?}", bytes, encoded);
+ assert_eq!(bytes, encoded);
+
+ let mut encoded = Vec::new();
+ into_writer(&value, &mut encoded).unwrap();
+ eprintln!("{:x?} == {:x?}", bytes, encoded);
+ assert_eq!(bytes, encoded);
+
+ let encoded = Value::serialized(&input).unwrap();
+ eprintln!("{:x?} == {:x?}", &value, &encoded);
+ assert!(veq(&value, &encoded));
+ }
+
+ let decoded: V = from_reader(&bytes[..]).unwrap();
+ let answer = equality(input.clone());
+ eprintln!("{:x?} == {:x?}", answer, decoded);
+ assert_eq!(answer, decoded);
+
+ let decoded: Value = from_reader(&bytes[..]).unwrap();
+ eprintln!("{:x?} == {:x?}", &value, &decoded);
+ assert!(veq(&value, &decoded));
+
+ let decoded: V = value.deserialized().unwrap();
+ let answer = equality(input);
+ eprintln!("{:x?} == {:x?}", answer, decoded);
+ assert_eq!(answer, decoded);
+}
+
+#[inline]
+fn veq(lhs: &Value, rhs: &Value) -> bool {
+ if let Value::Float(l) = lhs {
+ if let Value::Float(r) = rhs {
+ return Float(*l) == Float(*r);
+ }
+ }
+
+ lhs == rhs
+}
+
+#[inline]
+fn same<T>(x: T) -> T {
+ x
+}
+
+#[derive(Debug, Deserialize)]
+struct Float<T>(T);
+
+impl PartialEq for Float<f32> {
+ fn eq(&self, other: &Float<f32>) -> bool {
+ if self.0.is_nan() && other.0.is_nan() {
+ return true;
+ }
+
+ self.0 == other.0
+ }
+}
+
+impl PartialEq for Float<f64> {
+ fn eq(&self, other: &Float<f64>) -> bool {
+ if self.0.is_nan() && other.0.is_nan() {
+ return true;
+ }
+
+ self.0 == other.0
+ }
+}
+
+#[inline]
+fn map_big() -> BTreeMap<String, String> {
+ let mut map = BTreeMap::new();
+ map.insert("a".into(), "A".into());
+ map.insert("b".into(), "B".into());
+ map.insert("c".into(), "C".into());
+ map.insert("d".into(), "D".into());
+ map.insert("e".into(), "E".into());
+ map
+}
+
+#[inline]
+fn vmap_big() -> Value {
+ Value::Map(
+ map_big()
+ .into_iter()
+ .map(|x| (x.0.into(), x.1.into()))
+ .collect(),
+ )
+}
+
+#[inline]
+fn bigint() -> Value {
+ let bytes = hex::decode("0000000000000000000000000000000000000001").unwrap();
+ Value::Tag(2, Value::Bytes(bytes).into())
+}
+
+#[derive(Deserialize, Serialize, Copy, Clone, Debug, PartialEq, Eq)]
+struct UnitStruct;
+
+#[derive(Deserialize, Serialize, Copy, Clone, Debug, PartialEq, Eq)]
+struct TupleStruct(u8, u16);
+
+#[derive(Deserialize, Serialize, Copy, Clone, Debug, PartialEq, Eq)]
+struct Newtype(u8);
+
+#[derive(Deserialize, Serialize, Copy, Clone, Debug, PartialEq, Eq)]
+enum Enum {
+ Unit,
+ Newtype(u8),
+ Tuple(u8, u16),
+ Struct { first: u8, second: u16 },
+}
diff --git a/tests/error.rs b/tests/error.rs
new file mode 100644
index 0000000..eb1711f
--- /dev/null
+++ b/tests/error.rs
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use ciborium::{de::from_reader, de::Error, value::Value};
+use rstest::rstest;
+
+#[rstest(bytes, error,
+ // Invalid value
+ case("1e", Error::Syntax(0)),
+
+ // Indeterminate integers are invalid
+ case("1f", Error::Syntax(0)),
+
+ // Indeterminate integer in an array
+ case("83011f03", Error::Syntax(2)),
+
+ // Integer in a string continuation
+ case("7F616101FF", Error::Syntax(3)),
+
+ // Bytes in a string continuation
+ case("7F61614101FF", Error::Syntax(3)),
+
+ // Invalid UTF-8
+ case("62C328", Error::Syntax(0)),
+
+ // Invalid UTF-8 in a string continuation
+ case("7F62C328FF", Error::Syntax(1)),
+)]
+fn test(bytes: &str, error: Error<std::io::Error>) {
+ let bytes = hex::decode(bytes).unwrap();
+
+ let correct = match error {
+ Error::Io(..) => panic!(),
+ Error::Syntax(x) => ("syntax", Some(x), None),
+ Error::Semantic(x, y) => ("semantic", x, Some(y)),
+ Error::RecursionLimitExceeded => panic!(),
+ };
+
+ let result: Result<Value, _> = from_reader(&bytes[..]);
+ let actual = match result.unwrap_err() {
+ Error::Io(..) => panic!(),
+ Error::Syntax(x) => ("syntax", Some(x), None),
+ Error::Semantic(x, y) => ("semantic", x, Some(y)),
+ Error::RecursionLimitExceeded => panic!(),
+ };
+
+ assert_eq!(correct, actual);
+}
diff --git a/tests/fuzz.rs b/tests/fuzz.rs
new file mode 100644
index 0000000..2b9d0ec
--- /dev/null
+++ b/tests/fuzz.rs
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use std::fs::File;
+use std::io::Read;
+use std::io::Write;
+use std::os::raw::c_int;
+use std::os::unix::io::{FromRawFd, RawFd};
+
+use ciborium::{de::from_reader, value::Value};
+use rand::Rng;
+
+const ITERATIONS: usize = 128 * 1024;
+
+#[allow(non_camel_case_types)]
+type pid_t = i32;
+
+extern "C" {
+ fn close(fd: RawFd) -> c_int;
+ fn fork() -> pid_t;
+ fn pipe(pipefd: &mut [RawFd; 2]) -> c_int;
+ fn waitpid(pid: pid_t, wstatus: *mut c_int, options: c_int) -> pid_t;
+}
+
+#[test]
+fn fuzz() {
+ let mut fds: [RawFd; 2] = [0; 2];
+ assert_eq!(unsafe { pipe(&mut fds) }, 0);
+
+ let pid = unsafe { fork() };
+ assert!(pid >= 0);
+
+ match pid {
+ 0 => {
+ let mut child = unsafe { File::from_raw_fd(fds[1]) };
+ unsafe { close(fds[0]) };
+
+ let mut rng = rand::thread_rng();
+ let mut buffer = [0u8; 32];
+
+ for _ in 0..ITERATIONS {
+ let len = rng.gen_range(0..buffer.len());
+ rng.fill(&mut buffer[..len]);
+
+ writeln!(child, "{}", hex::encode(&buffer[..len])).unwrap();
+ writeln!(child, "{:?}", from_reader::<Value, _>(&buffer[..len])).unwrap();
+ }
+ }
+
+ pid => {
+ let mut parent = unsafe { File::from_raw_fd(fds[0]) };
+ unsafe { close(fds[1]) };
+
+ let mut string = String::new();
+ parent.read_to_string(&mut string).unwrap();
+ eprint!("{}", string);
+
+ let mut status = 0;
+ assert_eq!(pid, unsafe { waitpid(pid, &mut status, 0) });
+
+ eprintln!("exit status: {:?}", status);
+ assert_eq!(0, status);
+ }
+ }
+}
diff --git a/tests/macro.rs b/tests/macro.rs
new file mode 100644
index 0000000..a73fc84
--- /dev/null
+++ b/tests/macro.rs
@@ -0,0 +1,358 @@
+// SPDX-License-Identifier: Apache-2.0
+
+extern crate alloc;
+
+use ciborium::{
+ cbor,
+ value::{Integer, Value, Value::Null},
+};
+
+use rstest::rstest;
+use serde_bytes::Bytes;
+
+macro_rules! map {
+ ($($key:expr => $val:expr),* $(,)*) => {
+ Value::Map(vec![$(
+ (
+ Value::serialized(&$key).unwrap(),
+ Value::serialized(&$val).unwrap()
+ )
+ ),*])
+ };
+}
+
+macro_rules! arr {
+ ($($val:expr),*) => {
+ Value::Array(vec![$(
+ Value::serialized(&$val).unwrap()
+ ),*])
+ };
+}
+
+#[rstest(answer, question,
+ // Non-numeric simple types
+ case(Value::Null, cbor!(null).unwrap()),
+ case(Value::Bool(true), cbor!(true).unwrap()),
+ case(Value::Bool(false), cbor!(false).unwrap()),
+ case(Value::Text("foo".into()), cbor!("foo").unwrap()),
+ case(Value::Bytes(vec![0, 1, 2]), cbor!(Bytes::new(b"\x00\x01\x02")).unwrap()),
+
+ // Numeric simple types
+ case(Value::Integer(Integer::from(123)), cbor!(123).unwrap()),
+ case(Value::Integer(Integer::from(-123)), cbor!(-123).unwrap()),
+ case(Value::Float(1.23), cbor!(1.23).unwrap()),
+ case(Value::Float(-1.23), cbor!(-1.23).unwrap()),
+ case(Value::Float(2.5e+1), cbor!(2.5e+1).unwrap()),
+ case(Value::Float(-2.5e+1), cbor!(-2.5e+1).unwrap()),
+
+ // Simple array formulations
+ case(arr![], cbor!([]).unwrap()),
+ case(arr![Null], cbor!([null]).unwrap()),
+ case(arr![true], cbor!([true]).unwrap()),
+ case(arr![false], cbor!([false]).unwrap()),
+ case(arr!["foo"], cbor!(["foo"]).unwrap()),
+ case(arr![123], cbor!([123]).unwrap()),
+ case(arr![-123], cbor!([-123]).unwrap()),
+ case(arr![1.23], cbor!([1.23]).unwrap()),
+ case(arr![-1.23], cbor!([-1.23]).unwrap()),
+ case(arr![2.5e+1], cbor!([2.5e+1]).unwrap()),
+ case(arr![2.5e+1], cbor!([2.5e+1]).unwrap()),
+ case(arr![[1, 2]], cbor!([[1, 2]]).unwrap()),
+ case(arr![map! {1=>2,3=>4}], cbor!([{1=>2,3=>4}]).unwrap()),
+
+ // Two-item array formluations
+ case(arr![Null, Null], cbor!([null, null]).unwrap()),
+ case(arr![Null, true], cbor!([null, true]).unwrap()),
+ case(arr![Null, false], cbor!([null, false]).unwrap()),
+ case(arr![Null, "foo"], cbor!([null, "foo"]).unwrap()),
+ case(arr![Null, 123], cbor!([null, 123]).unwrap()),
+ case(arr![Null, -123], cbor!([null, -123]).unwrap()),
+ case(arr![Null, 1.23], cbor!([null, 1.23]).unwrap()),
+ case(arr![Null, -1.23], cbor!([null, -1.23]).unwrap()),
+ case(arr![Null, 2.5e+1], cbor!([null, 2.5e+1]).unwrap()),
+ case(arr![Null, 2.5e+1], cbor!([null, 2.5e+1]).unwrap()),
+ case(arr![Null, [1, 2]], cbor!([null, [1, 2]]).unwrap()),
+ case(arr![Null, map! {1=>2,3=>4}], cbor!([null, {1=>2,3=>4}]).unwrap()),
+ case(arr![true, Null], cbor!([true, null]).unwrap()),
+ case(arr![true, true], cbor!([true, true]).unwrap()),
+ case(arr![true, false], cbor!([true, false]).unwrap()),
+ case(arr![true, "foo"], cbor!([true, "foo"]).unwrap()),
+ case(arr![true, 123], cbor!([true, 123]).unwrap()),
+ case(arr![true, -123], cbor!([true, -123]).unwrap()),
+ case(arr![true, 1.23], cbor!([true, 1.23]).unwrap()),
+ case(arr![true, -1.23], cbor!([true, -1.23]).unwrap()),
+ case(arr![true, 2.5e+1], cbor!([true, 2.5e+1]).unwrap()),
+ case(arr![true, 2.5e+1], cbor!([true, 2.5e+1]).unwrap()),
+ case(arr![true, [1, 2]], cbor!([true, [1, 2]]).unwrap()),
+ case(arr![true, map! {1=>2,3=>4}], cbor!([true, {1=>2,3=>4}]).unwrap()),
+ case(arr![false, Null], cbor!([false, null]).unwrap()),
+ case(arr![false, true], cbor!([false, true]).unwrap()),
+ case(arr![false, false], cbor!([false, false]).unwrap()),
+ case(arr![false, "foo"], cbor!([false, "foo"]).unwrap()),
+ case(arr![false, 123], cbor!([false, 123]).unwrap()),
+ case(arr![false, -123], cbor!([false, -123]).unwrap()),
+ case(arr![false, 1.23], cbor!([false, 1.23]).unwrap()),
+ case(arr![false, -1.23], cbor!([false, -1.23]).unwrap()),
+ case(arr![false, 2.5e+1], cbor!([false, 2.5e+1]).unwrap()),
+ case(arr![false, 2.5e+1], cbor!([false, 2.5e+1]).unwrap()),
+ case(arr![false, [1, 2]], cbor!([false, [1, 2]]).unwrap()),
+ case(arr![false, map! {1=>2,3=>4}], cbor!([false, {1=>2,3=>4}]).unwrap()),
+ case(arr!["foo", Null], cbor!(["foo", null]).unwrap()),
+ case(arr!["foo", true], cbor!(["foo", true]).unwrap()),
+ case(arr!["foo", false], cbor!(["foo", false]).unwrap()),
+ case(arr!["foo", "foo"], cbor!(["foo", "foo"]).unwrap()),
+ case(arr!["foo", 123], cbor!(["foo", 123]).unwrap()),
+ case(arr!["foo", -123], cbor!(["foo", -123]).unwrap()),
+ case(arr!["foo", 1.23], cbor!(["foo", 1.23]).unwrap()),
+ case(arr!["foo", -1.23], cbor!(["foo", -1.23]).unwrap()),
+ case(arr!["foo", 2.5e+1], cbor!(["foo", 2.5e+1]).unwrap()),
+ case(arr!["foo", 2.5e+1], cbor!(["foo", 2.5e+1]).unwrap()),
+ case(arr!["foo", [1, 2]], cbor!(["foo", [1, 2]]).unwrap()),
+ case(arr!["foo", map! {1=>2,3=>4}], cbor!(["foo", {1=>2,3=>4}]).unwrap()),
+ case(arr![123, Null], cbor!([123, null]).unwrap()),
+ case(arr![123, true], cbor!([123, true]).unwrap()),
+ case(arr![123, false], cbor!([123, false]).unwrap()),
+ case(arr![123, "foo"], cbor!([123, "foo"]).unwrap()),
+ case(arr![123, 123], cbor!([123, 123]).unwrap()),
+ case(arr![123, -123], cbor!([123, -123]).unwrap()),
+ case(arr![123, 1.23], cbor!([123, 1.23]).unwrap()),
+ case(arr![123, -1.23], cbor!([123, -1.23]).unwrap()),
+ case(arr![123, 2.5e+1], cbor!([123, 2.5e+1]).unwrap()),
+ case(arr![123, 2.5e+1], cbor!([123, 2.5e+1]).unwrap()),
+ case(arr![123, [1, 2]], cbor!([123, [1, 2]]).unwrap()),
+ case(arr![123, map! {1=>2,3=>4}], cbor!([123, {1=>2,3=>4}]).unwrap()),
+ case(arr![-123, Null], cbor!([-123, null]).unwrap()),
+ case(arr![-123, true], cbor!([-123, true]).unwrap()),
+ case(arr![-123, false], cbor!([-123, false]).unwrap()),
+ case(arr![-123, "foo"], cbor!([-123, "foo"]).unwrap()),
+ case(arr![-123, 123], cbor!([-123, 123]).unwrap()),
+ case(arr![-123, -123], cbor!([-123, -123]).unwrap()),
+ case(arr![-123, 1.23], cbor!([-123, 1.23]).unwrap()),
+ case(arr![-123, -1.23], cbor!([-123, -1.23]).unwrap()),
+ case(arr![-123, 2.5e+1], cbor!([-123, 2.5e+1]).unwrap()),
+ case(arr![-123, 2.5e+1], cbor!([-123, 2.5e+1]).unwrap()),
+ case(arr![-123, [1, 2]], cbor!([-123, [1, 2]]).unwrap()),
+ case(arr![-123, map! {1=>2,3=>4}], cbor!([-123, {1=>2,3=>4}]).unwrap()),
+ case(arr![1.23, Null], cbor!([1.23, null]).unwrap()),
+ case(arr![1.23, true], cbor!([1.23, true]).unwrap()),
+ case(arr![1.23, false], cbor!([1.23, false]).unwrap()),
+ case(arr![1.23, "foo"], cbor!([1.23, "foo"]).unwrap()),
+ case(arr![1.23, 123], cbor!([1.23, 123]).unwrap()),
+ case(arr![1.23, -123], cbor!([1.23, -123]).unwrap()),
+ case(arr![1.23, 1.23], cbor!([1.23, 1.23]).unwrap()),
+ case(arr![1.23, -1.23], cbor!([1.23, -1.23]).unwrap()),
+ case(arr![1.23, 2.5e+1], cbor!([1.23, 2.5e+1]).unwrap()),
+ case(arr![1.23, 2.5e+1], cbor!([1.23, 2.5e+1]).unwrap()),
+ case(arr![1.23, [1, 2]], cbor!([1.23, [1, 2]]).unwrap()),
+ case(arr![1.23, map! {1=>2,3=>4}], cbor!([1.23, {1=>2,3=>4}]).unwrap()),
+ case(arr![-1.23, Null], cbor!([-1.23, null]).unwrap()),
+ case(arr![-1.23, true], cbor!([-1.23, true]).unwrap()),
+ case(arr![-1.23, false], cbor!([-1.23, false]).unwrap()),
+ case(arr![-1.23, "foo"], cbor!([-1.23, "foo"]).unwrap()),
+ case(arr![-1.23, 123], cbor!([-1.23, 123]).unwrap()),
+ case(arr![-1.23, -123], cbor!([-1.23, -123]).unwrap()),
+ case(arr![-1.23, 1.23], cbor!([-1.23, 1.23]).unwrap()),
+ case(arr![-1.23, -1.23], cbor!([-1.23, -1.23]).unwrap()),
+ case(arr![-1.23, 2.5e+1], cbor!([-1.23, 2.5e+1]).unwrap()),
+ case(arr![-1.23, 2.5e+1], cbor!([-1.23, 2.5e+1]).unwrap()),
+ case(arr![-1.23, [1, 2]], cbor!([-1.23, [1, 2]]).unwrap()),
+ case(arr![-1.23, map! {1=>2,3=>4}], cbor!([-1.23, {1=>2,3=>4}]).unwrap()),
+ case(arr![2.5e+1, Null], cbor!([2.5e+1, null]).unwrap()),
+ case(arr![2.5e+1, true], cbor!([2.5e+1, true]).unwrap()),
+ case(arr![2.5e+1, false], cbor!([2.5e+1, false]).unwrap()),
+ case(arr![2.5e+1, "foo"], cbor!([2.5e+1, "foo"]).unwrap()),
+ case(arr![2.5e+1, 123], cbor!([2.5e+1, 123]).unwrap()),
+ case(arr![2.5e+1, -123], cbor!([2.5e+1, -123]).unwrap()),
+ case(arr![2.5e+1, 1.23], cbor!([2.5e+1, 1.23]).unwrap()),
+ case(arr![2.5e+1, -1.23], cbor!([2.5e+1, -1.23]).unwrap()),
+ case(arr![2.5e+1, 2.5e+1], cbor!([2.5e+1, 2.5e+1]).unwrap()),
+ case(arr![2.5e+1, 2.5e+1], cbor!([2.5e+1, 2.5e+1]).unwrap()),
+ case(arr![2.5e+1, [1, 2]], cbor!([2.5e+1, [1, 2]]).unwrap()),
+ case(arr![2.5e+1, map! {1=>2,3=>4}], cbor!([2.5e+1, {1=>2,3=>4}]).unwrap()),
+ case(arr![2.5e+1, Null], cbor!([2.5e+1, null]).unwrap()),
+ case(arr![2.5e+1, true], cbor!([2.5e+1, true]).unwrap()),
+ case(arr![2.5e+1, false], cbor!([2.5e+1, false]).unwrap()),
+ case(arr![2.5e+1, "foo"], cbor!([2.5e+1, "foo"]).unwrap()),
+ case(arr![2.5e+1, 123], cbor!([2.5e+1, 123]).unwrap()),
+ case(arr![2.5e+1, -123], cbor!([2.5e+1, -123]).unwrap()),
+ case(arr![2.5e+1, 1.23], cbor!([2.5e+1, 1.23]).unwrap()),
+ case(arr![2.5e+1, -1.23], cbor!([2.5e+1, -1.23]).unwrap()),
+ case(arr![2.5e+1, 2.5e+1], cbor!([2.5e+1, 2.5e+1]).unwrap()),
+ case(arr![2.5e+1, 2.5e+1], cbor!([2.5e+1, 2.5e+1]).unwrap()),
+ case(arr![2.5e+1, [1, 2]], cbor!([2.5e+1, [1, 2]]).unwrap()),
+ case(arr![2.5e+1, map! {1=>2,3=>4}], cbor!([2.5e+1, {1=>2,3=>4}]).unwrap()),
+ case(arr![[1, 2], Null], cbor!([[1, 2], null]).unwrap()),
+ case(arr![[1, 2], true], cbor!([[1, 2], true]).unwrap()),
+ case(arr![[1, 2], false], cbor!([[1, 2], false]).unwrap()),
+ case(arr![[1, 2], "foo"], cbor!([[1, 2], "foo"]).unwrap()),
+ case(arr![[1, 2], 123], cbor!([[1, 2], 123]).unwrap()),
+ case(arr![[1, 2], -123], cbor!([[1, 2], -123]).unwrap()),
+ case(arr![[1, 2], 1.23], cbor!([[1, 2], 1.23]).unwrap()),
+ case(arr![[1, 2], -1.23], cbor!([[1, 2], -1.23]).unwrap()),
+ case(arr![[1, 2], 2.5e+1], cbor!([[1, 2], 2.5e+1]).unwrap()),
+ case(arr![[1, 2], 2.5e+1], cbor!([[1, 2], 2.5e+1]).unwrap()),
+ case(arr![[1, 2], [1, 2]], cbor!([[1, 2], [1, 2]]).unwrap()),
+ case(arr![[1, 2], map! {1=>2,3=>4}], cbor!([[1, 2], {1=>2,3=>4}]).unwrap()),
+ case(arr![map! {1=>2,3=>4}, Null], cbor!([{1=>2,3=>4}, null]).unwrap()),
+ case(arr![map! {1=>2,3=>4}, true], cbor!([{1=>2,3=>4}, true]).unwrap()),
+ case(arr![map! {1=>2,3=>4}, false], cbor!([{1=>2,3=>4}, false]).unwrap()),
+ case(arr![map! {1=>2,3=>4}, "foo"], cbor!([{1=>2,3=>4}, "foo"]).unwrap()),
+ case(arr![map! {1=>2,3=>4}, 123], cbor!([{1=>2,3=>4}, 123]).unwrap()),
+ case(arr![map! {1=>2,3=>4}, -123], cbor!([{1=>2,3=>4}, -123]).unwrap()),
+ case(arr![map! {1=>2,3=>4}, 1.23], cbor!([{1=>2,3=>4}, 1.23]).unwrap()),
+ case(arr![map! {1=>2,3=>4}, -1.23], cbor!([{1=>2,3=>4}, -1.23]).unwrap()),
+ case(arr![map! {1=>2,3=>4}, 2.5e+1], cbor!([{1=>2,3=>4}, 2.5e+1]).unwrap()),
+ case(arr![map! {1=>2,3=>4}, 2.5e+1], cbor!([{1=>2,3=>4}, 2.5e+1]).unwrap()),
+ case(arr![map! {1=>2,3=>4}, [1, 2]], cbor!([{1=>2,3=>4}, [1, 2]]).unwrap()),
+ case(arr![map! {1=>2,3=>4}, map! {1=>2,3=>4}], cbor!([{1=>2,3=>4}, {1=>2,3=>4}]).unwrap()),
+
+ // Map formulations
+ case(map! {}, cbor!({}).unwrap()),
+ case(map! {Null => Null}, cbor!({ null => null }).unwrap()),
+ case(map! {Null => true}, cbor!({ null => true }).unwrap()),
+ case(map! {Null => false}, cbor!({ null => false }).unwrap()),
+ case(map! {Null => "foo"}, cbor!({ null => "foo" }).unwrap()),
+ case(map! {Null => 123}, cbor!({ null => 123 }).unwrap()),
+ case(map! {Null => -123}, cbor!({ null => -123 }).unwrap()),
+ case(map! {Null => 1.23}, cbor!({ null => 1.23 }).unwrap()),
+ case(map! {Null => -1.23}, cbor!({ null => -1.23 }).unwrap()),
+ case(map! {Null => 2.5e+1}, cbor!({ null => 2.5e+1 }).unwrap()),
+ case(map! {Null => 2.5e+1}, cbor!({ null => 2.5e+1 }).unwrap()),
+ case(map! {Null => [1, 2]}, cbor!({ null => [1, 2] }).unwrap()),
+ case(map! {Null => map! {1=>2,3=>4}}, cbor!({ null => {1=>2,3=>4} }).unwrap()),
+ case(map! {true => Null}, cbor!({ true => null }).unwrap()),
+ case(map! {true => true}, cbor!({ true => true }).unwrap()),
+ case(map! {true => false}, cbor!({ true => false }).unwrap()),
+ case(map! {true => "foo"}, cbor!({ true => "foo" }).unwrap()),
+ case(map! {true => 123}, cbor!({ true => 123 }).unwrap()),
+ case(map! {true => -123}, cbor!({ true => -123 }).unwrap()),
+ case(map! {true => 1.23}, cbor!({ true => 1.23 }).unwrap()),
+ case(map! {true => -1.23}, cbor!({ true => -1.23 }).unwrap()),
+ case(map! {true => 2.5e+1}, cbor!({ true => 2.5e+1 }).unwrap()),
+ case(map! {true => 2.5e+1}, cbor!({ true => 2.5e+1 }).unwrap()),
+ case(map! {true => [1, 2]}, cbor!({ true => [1, 2] }).unwrap()),
+ case(map! {true => map! {1=>2,3=>4}}, cbor!({ true => {1=>2,3=>4} }).unwrap()),
+ case(map! {false => Null}, cbor!({ false => null }).unwrap()),
+ case(map! {false => true}, cbor!({ false => true }).unwrap()),
+ case(map! {false => false}, cbor!({ false => false }).unwrap()),
+ case(map! {false => "foo"}, cbor!({ false => "foo" }).unwrap()),
+ case(map! {false => 123}, cbor!({ false => 123 }).unwrap()),
+ case(map! {false => -123}, cbor!({ false => -123 }).unwrap()),
+ case(map! {false => 1.23}, cbor!({ false => 1.23 }).unwrap()),
+ case(map! {false => -1.23}, cbor!({ false => -1.23 }).unwrap()),
+ case(map! {false => 2.5e+1}, cbor!({ false => 2.5e+1 }).unwrap()),
+ case(map! {false => 2.5e+1}, cbor!({ false => 2.5e+1 }).unwrap()),
+ case(map! {false => [1, 2]}, cbor!({ false => [1, 2] }).unwrap()),
+ case(map! {false => map! {1=>2,3=>4}}, cbor!({ false => {1=>2,3=>4} }).unwrap()),
+ case(map! {"foo" => Null}, cbor!({ "foo" => null }).unwrap()),
+ case(map! {"foo" => true}, cbor!({ "foo" => true }).unwrap()),
+ case(map! {"foo" => false}, cbor!({ "foo" => false }).unwrap()),
+ case(map! {"foo" => "foo"}, cbor!({ "foo" => "foo" }).unwrap()),
+ case(map! {"foo" => 123}, cbor!({ "foo" => 123 }).unwrap()),
+ case(map! {"foo" => -123}, cbor!({ "foo" => -123 }).unwrap()),
+ case(map! {"foo" => 1.23}, cbor!({ "foo" => 1.23 }).unwrap()),
+ case(map! {"foo" => -1.23}, cbor!({ "foo" => -1.23 }).unwrap()),
+ case(map! {"foo" => 2.5e+1}, cbor!({ "foo" => 2.5e+1 }).unwrap()),
+ case(map! {"foo" => 2.5e+1}, cbor!({ "foo" => 2.5e+1 }).unwrap()),
+ case(map! {"foo" => [1, 2]}, cbor!({ "foo" => [1, 2] }).unwrap()),
+ case(map! {"foo" => map! {1=>2,3=>4}}, cbor!({ "foo" => {1=>2,3=>4} }).unwrap()),
+ case(map! {123 => Null}, cbor!({ 123 => null }).unwrap()),
+ case(map! {123 => true}, cbor!({ 123 => true }).unwrap()),
+ case(map! {123 => false}, cbor!({ 123 => false }).unwrap()),
+ case(map! {123 => "foo"}, cbor!({ 123 => "foo" }).unwrap()),
+ case(map! {123 => 123}, cbor!({ 123 => 123 }).unwrap()),
+ case(map! {123 => -123}, cbor!({ 123 => -123 }).unwrap()),
+ case(map! {123 => 1.23}, cbor!({ 123 => 1.23 }).unwrap()),
+ case(map! {123 => -1.23}, cbor!({ 123 => -1.23 }).unwrap()),
+ case(map! {123 => 2.5e+1}, cbor!({ 123 => 2.5e+1 }).unwrap()),
+ case(map! {123 => 2.5e+1}, cbor!({ 123 => 2.5e+1 }).unwrap()),
+ case(map! {123 => [1, 2]}, cbor!({ 123 => [1, 2] }).unwrap()),
+ case(map! {123 => map! {1=>2,3=>4}}, cbor!({ 123 => {1=>2,3=>4} }).unwrap()),
+ case(map! {-123 => Null}, cbor!({ -123 => null }).unwrap()),
+ case(map! {-123 => true}, cbor!({ -123 => true }).unwrap()),
+ case(map! {-123 => false}, cbor!({ -123 => false }).unwrap()),
+ case(map! {-123 => "foo"}, cbor!({ -123 => "foo" }).unwrap()),
+ case(map! {-123 => 123}, cbor!({ -123 => 123 }).unwrap()),
+ case(map! {-123 => -123}, cbor!({ -123 => -123 }).unwrap()),
+ case(map! {-123 => 1.23}, cbor!({ -123 => 1.23 }).unwrap()),
+ case(map! {-123 => -1.23}, cbor!({ -123 => -1.23 }).unwrap()),
+ case(map! {-123 => 2.5e+1}, cbor!({ -123 => 2.5e+1 }).unwrap()),
+ case(map! {-123 => 2.5e+1}, cbor!({ -123 => 2.5e+1 }).unwrap()),
+ case(map! {-123 => [1, 2]}, cbor!({ -123 => [1, 2] }).unwrap()),
+ case(map! {-123 => map! {1=>2,3=>4}}, cbor!({ -123 => {1=>2,3=>4} }).unwrap()),
+ case(map! {1.23 => Null}, cbor!({ 1.23 => null }).unwrap()),
+ case(map! {1.23 => true}, cbor!({ 1.23 => true }).unwrap()),
+ case(map! {1.23 => false}, cbor!({ 1.23 => false }).unwrap()),
+ case(map! {1.23 => "foo"}, cbor!({ 1.23 => "foo" }).unwrap()),
+ case(map! {1.23 => 123}, cbor!({ 1.23 => 123 }).unwrap()),
+ case(map! {1.23 => -123}, cbor!({ 1.23 => -123 }).unwrap()),
+ case(map! {1.23 => 1.23}, cbor!({ 1.23 => 1.23 }).unwrap()),
+ case(map! {1.23 => -1.23}, cbor!({ 1.23 => -1.23 }).unwrap()),
+ case(map! {1.23 => 2.5e+1}, cbor!({ 1.23 => 2.5e+1 }).unwrap()),
+ case(map! {1.23 => 2.5e+1}, cbor!({ 1.23 => 2.5e+1 }).unwrap()),
+ case(map! {1.23 => [1, 2]}, cbor!({ 1.23 => [1, 2] }).unwrap()),
+ case(map! {1.23 => map! {1=>2,3=>4}}, cbor!({ 1.23 => {1=>2,3=>4} }).unwrap()),
+ case(map! {-1.23 => Null}, cbor!({ -1.23 => null }).unwrap()),
+ case(map! {-1.23 => true}, cbor!({ -1.23 => true }).unwrap()),
+ case(map! {-1.23 => false}, cbor!({ -1.23 => false }).unwrap()),
+ case(map! {-1.23 => "foo"}, cbor!({ -1.23 => "foo" }).unwrap()),
+ case(map! {-1.23 => 123}, cbor!({ -1.23 => 123 }).unwrap()),
+ case(map! {-1.23 => -123}, cbor!({ -1.23 => -123 }).unwrap()),
+ case(map! {-1.23 => 1.23}, cbor!({ -1.23 => 1.23 }).unwrap()),
+ case(map! {-1.23 => -1.23}, cbor!({ -1.23 => -1.23 }).unwrap()),
+ case(map! {-1.23 => 2.5e+1}, cbor!({ -1.23 => 2.5e+1 }).unwrap()),
+ case(map! {-1.23 => 2.5e+1}, cbor!({ -1.23 => 2.5e+1 }).unwrap()),
+ case(map! {-1.23 => [1, 2]}, cbor!({ -1.23 => [1, 2] }).unwrap()),
+ case(map! {-1.23 => map! {1=>2,3=>4}}, cbor!({ -1.23 => {1=>2,3=>4} }).unwrap()),
+ case(map! {2.5e+1 => Null}, cbor!({ 2.5e+1 => null }).unwrap()),
+ case(map! {2.5e+1 => true}, cbor!({ 2.5e+1 => true }).unwrap()),
+ case(map! {2.5e+1 => false}, cbor!({ 2.5e+1 => false }).unwrap()),
+ case(map! {2.5e+1 => "foo"}, cbor!({ 2.5e+1 => "foo" }).unwrap()),
+ case(map! {2.5e+1 => 123}, cbor!({ 2.5e+1 => 123 }).unwrap()),
+ case(map! {2.5e+1 => -123}, cbor!({ 2.5e+1 => -123 }).unwrap()),
+ case(map! {2.5e+1 => 1.23}, cbor!({ 2.5e+1 => 1.23 }).unwrap()),
+ case(map! {2.5e+1 => -1.23}, cbor!({ 2.5e+1 => -1.23 }).unwrap()),
+ case(map! {2.5e+1 => 2.5e+1}, cbor!({ 2.5e+1 => 2.5e+1 }).unwrap()),
+ case(map! {2.5e+1 => 2.5e+1}, cbor!({ 2.5e+1 => 2.5e+1 }).unwrap()),
+ case(map! {2.5e+1 => [1, 2]}, cbor!({ 2.5e+1 => [1, 2] }).unwrap()),
+ case(map! {2.5e+1 => map! {1=>2,3=>4}}, cbor!({ 2.5e+1 => {1=>2,3=>4} }).unwrap()),
+ case(map! {2.5e+1 => Null}, cbor!({ 2.5e+1 => null }).unwrap()),
+ case(map! {2.5e+1 => true}, cbor!({ 2.5e+1 => true }).unwrap()),
+ case(map! {2.5e+1 => false}, cbor!({ 2.5e+1 => false }).unwrap()),
+ case(map! {2.5e+1 => "foo"}, cbor!({ 2.5e+1 => "foo" }).unwrap()),
+ case(map! {2.5e+1 => 123}, cbor!({ 2.5e+1 => 123 }).unwrap()),
+ case(map! {2.5e+1 => -123}, cbor!({ 2.5e+1 => -123 }).unwrap()),
+ case(map! {2.5e+1 => 1.23}, cbor!({ 2.5e+1 => 1.23 }).unwrap()),
+ case(map! {2.5e+1 => -1.23}, cbor!({ 2.5e+1 => -1.23 }).unwrap()),
+ case(map! {2.5e+1 => 2.5e+1}, cbor!({ 2.5e+1 => 2.5e+1 }).unwrap()),
+ case(map! {2.5e+1 => 2.5e+1}, cbor!({ 2.5e+1 => 2.5e+1 }).unwrap()),
+ case(map! {2.5e+1 => [1, 2]}, cbor!({ 2.5e+1 => [1, 2] }).unwrap()),
+ case(map! {2.5e+1 => map! {1=>2,3=>4}}, cbor!({ 2.5e+1 => {1=>2,3=>4} }).unwrap()),
+ case(map! {[1, 2] => Null}, cbor!({ [1, 2] => null }).unwrap()),
+ case(map! {[1, 2] => true}, cbor!({ [1, 2] => true }).unwrap()),
+ case(map! {[1, 2] => false}, cbor!({ [1, 2] => false }).unwrap()),
+ case(map! {[1, 2] => "foo"}, cbor!({ [1, 2] => "foo" }).unwrap()),
+ case(map! {[1, 2] => 123}, cbor!({ [1, 2] => 123 }).unwrap()),
+ case(map! {[1, 2] => -123}, cbor!({ [1, 2] => -123 }).unwrap()),
+ case(map! {[1, 2] => 1.23}, cbor!({ [1, 2] => 1.23 }).unwrap()),
+ case(map! {[1, 2] => -1.23}, cbor!({ [1, 2] => -1.23 }).unwrap()),
+ case(map! {[1, 2] => 2.5e+1}, cbor!({ [1, 2] => 2.5e+1 }).unwrap()),
+ case(map! {[1, 2] => 2.5e+1}, cbor!({ [1, 2] => 2.5e+1 }).unwrap()),
+ case(map! {[1, 2] => [1, 2]}, cbor!({ [1, 2] => [1, 2] }).unwrap()),
+ case(map! {[1, 2] => map! {1=>2,3=>4}}, cbor!({ [1, 2] => {1=>2,3=>4} }).unwrap()),
+ case(map! {map! {1=>2,3=>4} => Null}, cbor!({ {1=>2,3=>4} => null }).unwrap()),
+ case(map! {map! {1=>2,3=>4} => true}, cbor!({ {1=>2,3=>4} => true }).unwrap()),
+ case(map! {map! {1=>2,3=>4} => false}, cbor!({ {1=>2,3=>4} => false }).unwrap()),
+ case(map! {map! {1=>2,3=>4} => "foo"}, cbor!({ {1=>2,3=>4} => "foo" }).unwrap()),
+ case(map! {map! {1=>2,3=>4} => 123}, cbor!({ {1=>2,3=>4} => 123 }).unwrap()),
+ case(map! {map! {1=>2,3=>4} => -123}, cbor!({ {1=>2,3=>4} => -123 }).unwrap()),
+ case(map! {map! {1=>2,3=>4} => 1.23}, cbor!({ {1=>2,3=>4} => 1.23 }).unwrap()),
+ case(map! {map! {1=>2,3=>4} => -1.23}, cbor!({ {1=>2,3=>4} => -1.23 }).unwrap()),
+ case(map! {map! {1=>2,3=>4} => 2.5e+1}, cbor!({ {1=>2,3=>4} => 2.5e+1 }).unwrap()),
+ case(map! {map! {1=>2,3=>4} => 2.5e+1}, cbor!({ {1=>2,3=>4} => 2.5e+1 }).unwrap()),
+ case(map! {map! {1=>2,3=>4} => [1, 2]}, cbor!({ {1=>2,3=>4} => [1, 2] }).unwrap()),
+ case(map! {map! {1=>2,3=>4} => map! {1=>2,3=>4}}, cbor!({ {1=>2,3=>4} => {1=>2,3=>4} }).unwrap()),
+)]
+fn test(answer: Value, question: Value) {
+ assert_eq!(answer, question);
+}
diff --git a/tests/no_std.rs b/tests/no_std.rs
new file mode 100644
index 0000000..6ea008a
--- /dev/null
+++ b/tests/no_std.rs
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#![cfg(all(feature = "serde", not(feature = "std")))]
+#![no_std]
+
+extern crate alloc;
+
+use alloc::vec::Vec;
+
+use ciborium::{de::from_reader, ser::into_writer};
+
+#[test]
+fn decode() {
+ assert_eq!(from_reader::<u8, &[u8]>(&[7u8][..]).unwrap(), 7);
+}
+
+#[test]
+fn eof() {
+ from_reader::<u8, &[u8]>(&[]).unwrap_err();
+}
+
+#[test]
+fn encode_slice() {
+ let mut buffer = [0u8; 1];
+ into_writer(&3u8, &mut buffer[..]).unwrap();
+ assert_eq!(buffer[0], 3);
+}
+
+#[test]
+fn encode_vec() {
+ let mut buffer = Vec::with_capacity(1);
+ into_writer(&3u8, &mut buffer).unwrap();
+ assert_eq!(buffer[0], 3);
+}
+
+#[test]
+fn oos() {
+ into_writer(&3u8, &mut [][..]).unwrap_err();
+}
diff --git a/tests/recursion.rs b/tests/recursion.rs
new file mode 100644
index 0000000..a340b2d
--- /dev/null
+++ b/tests/recursion.rs
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: Apache-2.0
+
+//! This test validates that we don't get stack overflows.
+//!
+//! If container types cause recursion, then a long list of prefixes which
+//! indicate nested container types could cause the stack to overflow. We
+//! test each of these types here to ensure there is no stack overflow.
+
+use ciborium::{
+ de::{from_reader, Error},
+ value::Value,
+};
+
+#[test]
+fn array() {
+ let bytes = [0x9f; 128 * 1024];
+ match from_reader::<Value, _>(&bytes[..]).unwrap_err() {
+ Error::RecursionLimitExceeded => (),
+ e => panic!("incorrect error: {:?}", e),
+ }
+}
+
+#[test]
+fn map() {
+ let bytes = [0xbf; 128 * 1024];
+ match from_reader::<Value, _>(&bytes[..]).unwrap_err() {
+ Error::RecursionLimitExceeded => (),
+ e => panic!("incorrect error: {:?}", e),
+ }
+}
+
+#[test]
+fn bytes() {
+ let bytes = [0x5f; 128 * 1024];
+ match from_reader::<Value, _>(&bytes[..]).unwrap_err() {
+ Error::Io(..) => (),
+ e => panic!("incorrect error: {:?}", e),
+ }
+}
+
+#[test]
+fn text() {
+ let bytes = [0x7f; 128 * 1024];
+ match from_reader::<Value, _>(&bytes[..]).unwrap_err() {
+ Error::Io(..) => (),
+ e => panic!("incorrect error: {:?}", e),
+ }
+}
diff --git a/tests/tag.rs b/tests/tag.rs
new file mode 100644
index 0000000..04b6dad
--- /dev/null
+++ b/tests/tag.rs
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: Apache-2.0
+
+extern crate alloc;
+
+use ciborium::{de::from_reader, ser::into_writer, tag::*, value::Value};
+use rstest::rstest;
+use serde::{Deserialize, Serialize};
+
+use core::fmt::Debug;
+
+#[rstest(item, bytes, value, encode, success,
+ case(Captured(Some(6), true), "c6f5", Value::Tag(6, Value::Bool(true).into()), true, true),
+ case(Captured(None, true), "f5", Value::Bool(true), true, true),
+
+ case(Required::<_, 6>(true), "c6f5", Value::Tag(6, Value::Bool(true).into()), true, true),
+ case(Required::<_, 6>(true), "c7f5", Value::Tag(7, Value::Bool(true).into()), false, false),
+ case(Required::<_, 6>(true), "f5", Value::Bool(true), false, false),
+
+ case(Accepted::<_, 6>(true), "c6f5", Value::Tag(6, Value::Bool(true).into()), true, true),
+ 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>(
+ item: T,
+ bytes: &str,
+ value: Value,
+ encode: bool,
+ success: bool,
+) {
+ let bytes = hex::decode(bytes).unwrap();
+
+ if encode {
+ // Encode into bytes
+ let mut encoded = Vec::new();
+ into_writer(&item, &mut encoded).unwrap();
+ assert_eq!(bytes, encoded);
+
+ // Encode into value
+ assert_eq!(value, Value::serialized(&item).unwrap());
+ }
+
+ // Decode from bytes
+ match from_reader(&bytes[..]) {
+ Ok(x) if success => assert_eq!(item, x),
+ Ok(..) => panic!("unexpected success"),
+ Err(e) if success => Err(e).unwrap(),
+ Err(..) => (),
+ }
+
+ // Decode from value
+ match value.deserialized() {
+ Ok(x) if success => assert_eq!(item, x),
+ Ok(..) => panic!("unexpected success"),
+ Err(e) if success => Err(e).unwrap(),
+ Err(..) => (),
+ }
+}