ciborium-ll 0.2.0

Low-level CBOR codec primitives
Documentation
// 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]);
        }
    }
}