| // SPDX-License-Identifier: Apache-2.0 |
| |
| //! Low level CBOR parsing tools |
| //! |
| //! This crate contains low-level types for encoding and decoding items in |
| //! CBOR. This crate is usable in both `no_std` and `no_alloc` environments. |
| //! To understand how this crate works, first we will look at the structure |
| //! of a CBOR item on the wire. |
| //! |
| //! # Anatomy of a CBOR Item |
| //! |
| //! This is a brief anatomy of a CBOR item on the wire. |
| //! |
| //! ```text |
| //! +------------+-----------+ |
| //! | | | |
| //! | Major | Minor | |
| //! | (3bits) | (5bits) | |
| //! | | | |
| //! +------------+-----------+ |
| //! ^ ^ |
| //! | | |
| //! +-----+ +-----+ |
| //! | | |
| //! | | |
| //! +----------------------------+--------------+ |
| //! | | | | |
| //! | Prefix | Affix | Suffix | |
| //! | (1 byte) | (0-8 bytes) | (0+ bytes) | |
| //! | | | | |
| //! +------------+---------------+--------------+ |
| //! |
| //! | | | |
| //! +------------+---------------+--------------+ |
| //! | | |
| //! v v |
| //! |
| //! Header Body |
| //! ``` |
| //! |
| //! The `ciborium` crate works by providing the `Decoder` and `Encoder` types |
| //! which provide input and output for a CBOR header (see: `Header`). From |
| //! there, you can either handle the body yourself or use the provided utility |
| //! functions. |
| //! |
| //! For more information on the CBOR format, see |
| //! [RFC 7049](https://tools.ietf.org/html/rfc7049). |
| //! |
| //! # Decoding |
| //! |
| //! In order to decode CBOR, you will create a `Decoder` from a reader. The |
| //! decoder instance will allow you to `Decoder::pull()` `Header` instances |
| //! from the input. |
| //! |
| //! Most CBOR items are fully contained in their headers and therefore have no |
| //! body. These items can be evaluated directly from the `Header` instance. |
| //! |
| //! Bytes and text items have a body but do not contain child items. Since |
| //! both bytes and text values may be segmented, parsing them can be a bit |
| //! tricky. Therefore, we provide helper functions to parse these types. See |
| //! `Decoder::bytes()` and `Decoder::text()` for more details. |
| //! |
| //! Array and map items have a body which contains child items. These can be |
| //! parsed by simply doing `Decoder::pull()` to parse the child items. |
| //! |
| //! ## Example |
| //! |
| //! ```rust |
| //! use ciborium_ll::{Decoder, Header}; |
| //! use ciborium_io::Read as _; |
| //! |
| //! let input = b"\x6dHello, World!"; |
| //! let mut decoder = Decoder::from(&input[..]); |
| //! let mut chunks = 0; |
| //! |
| //! match decoder.pull().unwrap() { |
| //! Header::Text(len) => { |
| //! let mut segments = decoder.text(len); |
| //! while let Some(mut segment) = segments.pull().unwrap() { |
| //! let mut buffer = [0u8; 7]; |
| //! while let Some(chunk) = segment.pull(&mut buffer[..]).unwrap() { |
| //! match chunk { |
| //! "Hello, " if chunks == 0 => chunks = 1, |
| //! "World!" if chunks == 1 => chunks = 2, |
| //! _ => panic!("received unexpected chunk"), |
| //! } |
| //! } |
| //! } |
| //! } |
| //! |
| //! _ => panic!("received unexpected value"), |
| //! } |
| //! |
| //! assert_eq!(chunks, 2); |
| //! ``` |
| //! |
| //! # Encoding |
| //! |
| //! To encode values to CBOR, create an `Encoder` from a writer. The encoder |
| //! instance provides the `Encoder::push()` method to write a `Header` value |
| //! to the wire. CBOR item bodies can be written directly. |
| //! |
| //! For bytes and text, there are the `Encoder::bytes()` and `Encoder::text()` |
| //! utility functions, respectively, which will properly segment the output |
| //! on the wire for you. |
| //! |
| //! ## Example |
| //! |
| //! ```rust |
| //! use ciborium_ll::{Encoder, Header}; |
| //! use ciborium_io::Write as _; |
| //! |
| //! let mut buffer = [0u8; 19]; |
| //! let mut encoder = Encoder::from(&mut buffer[..]); |
| //! |
| //! // Write the structure |
| //! encoder.push(Header::Map(Some(1))).unwrap(); |
| //! encoder.push(Header::Positive(7)).unwrap(); |
| //! encoder.text("Hello, World!", 7).unwrap(); |
| //! |
| //! // Validate our output |
| //! encoder.flush().unwrap(); |
| //! assert_eq!(b"\xa1\x07\x7f\x67Hello, \x66World!\xff", &buffer[..]); |
| //! ``` |
| |
| #![cfg_attr(not(feature = "std"), no_std)] |
| #![deny(missing_docs)] |
| #![deny(clippy::all)] |
| #![deny(clippy::cargo)] |
| |
| #[cfg(feature = "alloc")] |
| extern crate alloc; |
| |
| mod dec; |
| mod enc; |
| mod hdr; |
| mod seg; |
| |
| pub use dec::*; |
| pub use enc::*; |
| pub use hdr::*; |
| pub use seg::{Segment, Segments}; |
| |
| /// Simple value constants |
| pub mod simple { |
| #![allow(missing_docs)] |
| |
| pub const FALSE: u8 = 20; |
| pub const TRUE: u8 = 21; |
| pub const NULL: u8 = 22; |
| pub const UNDEFINED: u8 = 23; |
| } |
| |
| /// Tag constants |
| pub mod tag { |
| #![allow(missing_docs)] |
| |
| pub const BIGPOS: u64 = 2; |
| pub const BIGNEG: u64 = 3; |
| } |
| |
| #[derive(Debug)] |
| struct InvalidError(()); |
| |
| #[derive(Copy, Clone, Debug, PartialEq, Eq)] |
| enum Major { |
| Positive, |
| Negative, |
| Bytes, |
| Text, |
| Array, |
| Map, |
| Tag, |
| Other, |
| } |
| |
| #[derive(Copy, Clone, Debug, PartialEq, Eq)] |
| enum Minor { |
| This(u8), |
| Next1([u8; 1]), |
| Next2([u8; 2]), |
| Next4([u8; 4]), |
| Next8([u8; 8]), |
| More, |
| } |
| |
| impl AsRef<[u8]> for Minor { |
| #[inline] |
| fn as_ref(&self) -> &[u8] { |
| match self { |
| Self::More => &[], |
| Self::This(..) => &[], |
| Self::Next1(x) => x.as_ref(), |
| Self::Next2(x) => x.as_ref(), |
| Self::Next4(x) => x.as_ref(), |
| Self::Next8(x) => x.as_ref(), |
| } |
| } |
| } |
| |
| impl AsMut<[u8]> for Minor { |
| #[inline] |
| fn as_mut(&mut self) -> &mut [u8] { |
| match self { |
| Self::More => &mut [], |
| Self::This(..) => &mut [], |
| Self::Next1(x) => x.as_mut(), |
| Self::Next2(x) => x.as_mut(), |
| Self::Next4(x) => x.as_mut(), |
| Self::Next8(x) => x.as_mut(), |
| } |
| } |
| } |
| |
| #[derive(Copy, Clone, Debug, PartialEq, Eq)] |
| struct Title(pub Major, pub Minor); |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| macro_rules! neg { |
| ($i:expr) => { |
| Header::Negative((($i as i128) ^ !0) as u64) |
| }; |
| } |
| |
| #[allow(clippy::excessive_precision)] |
| #[test] |
| fn leaf() { |
| use core::f64::{INFINITY, NAN}; |
| |
| let data = &[ |
| (Header::Positive(0), "00", true), |
| (Header::Positive(1), "01", true), |
| (Header::Positive(10), "0a", true), |
| (Header::Positive(23), "17", true), |
| (Header::Positive(24), "1818", true), |
| (Header::Positive(25), "1819", true), |
| (Header::Positive(100), "1864", true), |
| (Header::Positive(1000), "1903e8", true), |
| (Header::Positive(1000000), "1a000f4240", true), |
| (Header::Positive(1000000000000), "1b000000e8d4a51000", true), |
| ( |
| Header::Positive(18446744073709551615), |
| "1bffffffffffffffff", |
| true, |
| ), |
| (neg!(-18446744073709551616), "3bffffffffffffffff", true), |
| (neg!(-1), "20", true), |
| (neg!(-10), "29", true), |
| (neg!(-100), "3863", true), |
| (neg!(-1000), "3903e7", true), |
| (Header::Float(0.0), "f90000", true), |
| (Header::Float(-0.0), "f98000", true), |
| (Header::Float(1.0), "f93c00", true), |
| (Header::Float(1.1), "fb3ff199999999999a", true), |
| (Header::Float(1.5), "f93e00", true), |
| (Header::Float(65504.0), "f97bff", true), |
| (Header::Float(100000.0), "fa47c35000", true), |
| (Header::Float(3.4028234663852886e+38), "fa7f7fffff", true), |
| (Header::Float(1.0e+300), "fb7e37e43c8800759c", true), |
| (Header::Float(5.960464477539063e-8), "f90001", true), |
| (Header::Float(0.00006103515625), "f90400", true), |
| (Header::Float(-4.0), "f9c400", true), |
| (Header::Float(-4.1), "fbc010666666666666", true), |
| (Header::Float(INFINITY), "f97c00", true), |
| (Header::Float(NAN), "f97e00", true), |
| (Header::Float(-INFINITY), "f9fc00", true), |
| (Header::Float(INFINITY), "fa7f800000", false), |
| (Header::Float(NAN), "fa7fc00000", false), |
| (Header::Float(-INFINITY), "faff800000", false), |
| (Header::Float(INFINITY), "fb7ff0000000000000", false), |
| (Header::Float(NAN), "fb7ff8000000000000", false), |
| (Header::Float(-INFINITY), "fbfff0000000000000", false), |
| (Header::Simple(simple::FALSE), "f4", true), |
| (Header::Simple(simple::TRUE), "f5", true), |
| (Header::Simple(simple::NULL), "f6", true), |
| (Header::Simple(simple::UNDEFINED), "f7", true), |
| (Header::Simple(16), "f0", true), |
| (Header::Simple(24), "f818", true), |
| (Header::Simple(255), "f8ff", true), |
| (Header::Tag(0), "c0", true), |
| (Header::Tag(1), "c1", true), |
| (Header::Tag(23), "d7", true), |
| (Header::Tag(24), "d818", true), |
| (Header::Tag(32), "d820", true), |
| (Header::Bytes(Some(0)), "40", true), |
| (Header::Bytes(Some(4)), "44", true), |
| (Header::Text(Some(0)), "60", true), |
| (Header::Text(Some(4)), "64", true), |
| ]; |
| |
| for (header, bytes, encode) in data.iter().cloned() { |
| let bytes = hex::decode(bytes).unwrap(); |
| |
| let mut decoder = Decoder::from(&bytes[..]); |
| match (header, decoder.pull().unwrap()) { |
| // NaN equality... |
| (Header::Float(l), Header::Float(r)) if l.is_nan() && r.is_nan() => (), |
| |
| // Everything else... |
| (l, r) => assert_eq!(l, r), |
| } |
| |
| if encode { |
| let mut buffer = [0u8; 1024]; |
| let mut writer = &mut buffer[..]; |
| let mut encoder = Encoder::from(&mut writer); |
| encoder.push(header).unwrap(); |
| |
| let len = writer.len(); |
| assert_eq!(&bytes[..], &buffer[..1024 - len]); |
| } |
| } |
| } |
| |
| #[test] |
| fn node() { |
| let data: &[(&str, &[Header])] = &[ |
| ("80", &[Header::Array(Some(0))]), |
| ( |
| "83010203", |
| &[ |
| Header::Array(Some(3)), |
| Header::Positive(1), |
| Header::Positive(2), |
| Header::Positive(3), |
| ], |
| ), |
| ( |
| "98190102030405060708090a0b0c0d0e0f101112131415161718181819", |
| &[ |
| Header::Array(Some(25)), |
| Header::Positive(1), |
| Header::Positive(2), |
| Header::Positive(3), |
| Header::Positive(4), |
| Header::Positive(5), |
| Header::Positive(6), |
| Header::Positive(7), |
| Header::Positive(8), |
| Header::Positive(9), |
| Header::Positive(10), |
| Header::Positive(11), |
| Header::Positive(12), |
| Header::Positive(13), |
| Header::Positive(14), |
| Header::Positive(15), |
| Header::Positive(16), |
| Header::Positive(17), |
| Header::Positive(18), |
| Header::Positive(19), |
| Header::Positive(20), |
| Header::Positive(21), |
| Header::Positive(22), |
| Header::Positive(23), |
| Header::Positive(24), |
| Header::Positive(25), |
| ], |
| ), |
| ("a0", &[Header::Map(Some(0))]), |
| ( |
| "a201020304", |
| &[ |
| Header::Map(Some(2)), |
| Header::Positive(1), |
| Header::Positive(2), |
| Header::Positive(3), |
| Header::Positive(4), |
| ], |
| ), |
| ("9fff", &[Header::Array(None), Header::Break]), |
| ( |
| "9f018202039f0405ffff", |
| &[ |
| Header::Array(None), |
| Header::Positive(1), |
| Header::Array(Some(2)), |
| Header::Positive(2), |
| Header::Positive(3), |
| Header::Array(None), |
| Header::Positive(4), |
| Header::Positive(5), |
| Header::Break, |
| Header::Break, |
| ], |
| ), |
| ( |
| "9f01820203820405ff", |
| &[ |
| Header::Array(None), |
| Header::Positive(1), |
| Header::Array(Some(2)), |
| Header::Positive(2), |
| Header::Positive(3), |
| Header::Array(Some(2)), |
| Header::Positive(4), |
| Header::Positive(5), |
| Header::Break, |
| ], |
| ), |
| ( |
| "83018202039f0405ff", |
| &[ |
| Header::Array(Some(3)), |
| Header::Positive(1), |
| Header::Array(Some(2)), |
| Header::Positive(2), |
| Header::Positive(3), |
| Header::Array(None), |
| Header::Positive(4), |
| Header::Positive(5), |
| Header::Break, |
| ], |
| ), |
| ( |
| "83019f0203ff820405", |
| &[ |
| Header::Array(Some(3)), |
| Header::Positive(1), |
| Header::Array(None), |
| Header::Positive(2), |
| Header::Positive(3), |
| Header::Break, |
| Header::Array(Some(2)), |
| Header::Positive(4), |
| Header::Positive(5), |
| ], |
| ), |
| ( |
| "9f0102030405060708090a0b0c0d0e0f101112131415161718181819ff", |
| &[ |
| Header::Array(None), |
| Header::Positive(1), |
| Header::Positive(2), |
| Header::Positive(3), |
| Header::Positive(4), |
| Header::Positive(5), |
| Header::Positive(6), |
| Header::Positive(7), |
| Header::Positive(8), |
| Header::Positive(9), |
| Header::Positive(10), |
| Header::Positive(11), |
| Header::Positive(12), |
| Header::Positive(13), |
| Header::Positive(14), |
| Header::Positive(15), |
| Header::Positive(16), |
| Header::Positive(17), |
| Header::Positive(18), |
| Header::Positive(19), |
| Header::Positive(20), |
| Header::Positive(21), |
| Header::Positive(22), |
| Header::Positive(23), |
| Header::Positive(24), |
| Header::Positive(25), |
| Header::Break, |
| ], |
| ), |
| ]; |
| |
| for (bytes, headers) in data { |
| let bytes = hex::decode(bytes).unwrap(); |
| |
| // Test decoding |
| let mut decoder = Decoder::from(&bytes[..]); |
| for header in headers.iter().cloned() { |
| assert_eq!(header, decoder.pull().unwrap()); |
| } |
| |
| // Test encoding |
| let mut buffer = [0u8; 1024]; |
| let mut writer = &mut buffer[..]; |
| let mut encoder = Encoder::from(&mut writer); |
| |
| for header in headers.iter().cloned() { |
| encoder.push(header).unwrap(); |
| } |
| |
| let len = writer.len(); |
| assert_eq!(&bytes[..], &buffer[..1024 - len]); |
| } |
| } |
| } |