From 3aca5fbc74b85bb81d37aee8bf8bd412f8569c37 Mon Sep 17 00:00:00 2001 From: David Drysdale Date: Wed, 9 Jun 2021 10:12:55 +0100 Subject: [PATCH] cbor: prepare for publishing as standalone crate - Add an example of usage - Add a minimal README, including the example code - Document public items - Add more info to Cargo.toml --- libraries/cbor/Cargo.toml | 6 +++ libraries/cbor/README.md | 63 +++++++++++++++++++++++ libraries/cbor/examples/cbor.rs | 90 +++++++++++++++++++++++++++++++++ libraries/cbor/src/macros.rs | 3 ++ libraries/cbor/src/reader.rs | 5 ++ libraries/cbor/src/values.rs | 31 +++++++++++- libraries/cbor/src/writer.rs | 4 ++ 7 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 libraries/cbor/README.md create mode 100644 libraries/cbor/examples/cbor.rs diff --git a/libraries/cbor/Cargo.toml b/libraries/cbor/Cargo.toml index 5726615..8ae09b7 100644 --- a/libraries/cbor/Cargo.toml +++ b/libraries/cbor/Cargo.toml @@ -5,8 +5,14 @@ authors = [ "Fabian Kaczmarczyck ", "Guillaume Endignoux ", "Jean-Michel Picod ", + "David Drysdale ", ] license = "Apache-2.0" edition = "2018" +description = "CBOR parsing library" +homepage = "https://github.com/google/OpenSK" +repository = "https://github.com/google/OpenSK" +keywords = ["cbor", "serialization", "no_std"] +categories = ["encoding"] [dependencies] diff --git a/libraries/cbor/README.md b/libraries/cbor/README.md new file mode 100644 index 0000000..edf4957 --- /dev/null +++ b/libraries/cbor/README.md @@ -0,0 +1,63 @@ +# CBOR Parsing Library + +This crate implements Concise Binary Object Representation (CBOR) from [RFC +8949](https://datatracker.ietf.org/doc/html/rfc8949). + +## Usage + +```rust +fn main() { + // Build a CBOR object with various different types included. Note that this + // object is not built in canonical order. + let manual_object = Value::Map(vec![ + ( + Value::Unsigned(1), + Value::Array(vec![Value::Unsigned(2), Value::Unsigned(3)]), + ), + ( + Value::TextString("tstr".to_owned()), + Value::ByteString(vec![1, 2, 3]), + ), + (Value::Negative(-2), Value::Simple(SimpleValue::NullValue)), + (Value::Unsigned(3), Value::Simple(SimpleValue::TrueValue)), + ]); + + // Build the same object using the crate's convenience macros. + let macro_object = cbor_map! { + 1 => cbor_array![2, 3], + "tstr" => cbor_bytes!(vec![1, 2, 3]), + -2 => cbor_null!(), + 3 => cbor_true!(), + }; + + assert_eq!(manual_object, macro_object); + println!("Object {:?}", manual_object); + + // Serialize to bytes. + let mut manual_data = vec![]; + sk_cbor::writer::write(manual_object, &mut manual_data); + let hex_manual_data = hexify(&manual_data); + + // Serialized version is in canonical order. + println!("Serializes to {}", hex_manual_data); + assert_eq!( + hex_manual_data, + concat!( + "a4", // 4-map + "01", // int(1) => + "820203", // 2-array [2, 3], + "03", // int(3) => + "f5", // true, + "21", // nint(-2) => + "f6", // null, + "6474737472", // 4-tstr "tstr" => + "43010203" // 3-bstr + ) + ); + + // Convert back to an object. This is different than the original object, + // because the map is now in canonical order. + let recovered_object = sk_cbor::reader::read(&manual_data).unwrap(); + println!("Deserializes to {:?}", recovered_object); +} +``` diff --git a/libraries/cbor/examples/cbor.rs b/libraries/cbor/examples/cbor.rs new file mode 100644 index 0000000..2e098c2 --- /dev/null +++ b/libraries/cbor/examples/cbor.rs @@ -0,0 +1,90 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// 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. +// +//////////////////////////////////////////////////////////////////////////////// + +//! Example program demonstrating cbor usage. + +extern crate alloc; + +use sk_cbor::values::{SimpleValue, Value}; +use sk_cbor::{cbor_array, cbor_bytes, cbor_map, cbor_null, cbor_true}; + +fn hexify(data: &[u8]) -> String { + let mut s = String::new(); + for b in data { + s.push_str(&format!("{:02x}", b)); + } + s +} + +fn main() { + // Build a CBOR object with various different types included. Note that this + // object is not built in canonical order. + let manual_object = Value::Map(vec![ + ( + Value::Unsigned(1), + Value::Array(vec![Value::Unsigned(2), Value::Unsigned(3)]), + ), + ( + Value::TextString("tstr".to_owned()), + Value::ByteString(vec![1, 2, 3]), + ), + (Value::Negative(-2), Value::Simple(SimpleValue::NullValue)), + (Value::Unsigned(3), Value::Simple(SimpleValue::TrueValue)), + ]); + + // Build the same object using the crate's convenience macros. + let macro_object = cbor_map! { + 1 => cbor_array![2, 3], + "tstr" => cbor_bytes!(vec![1, 2, 3]), + -2 => cbor_null!(), + 3 => cbor_true!(), + }; + + assert_eq!(manual_object, macro_object); + println!("Object {:?}", manual_object); + + // Serialize to bytes. + let mut manual_data = vec![]; + sk_cbor::writer::write(manual_object, &mut manual_data); + let hex_manual_data = hexify(&manual_data); + let mut macro_data = vec![]; + sk_cbor::writer::write(macro_object, &mut macro_data); + let hex_macro_data = hexify(¯o_data); + + assert_eq!(hex_manual_data, hex_macro_data); + + // Serialized version is in canonical order. + println!("Serializes to {}", hex_manual_data); + assert_eq!( + hex_manual_data, + concat!( + "a4", // 4-map + "01", // int(1) => + "820203", // 2-array [2, 3], + "03", // int(3) => + "f5", // true, + "21", // nint(-2) => + "f6", // null, + "6474737472", // 4-tstr "tstr" => + "43010203" // 3-bstr + ) + ); + + // Convert back to an object. This is different than the original object, + // because the map is now in canonical order. + let recovered_object = sk_cbor::reader::read(&manual_data).unwrap(); + println!("Deserializes to {:?}", recovered_object); +} diff --git a/libraries/cbor/src/macros.rs b/libraries/cbor/src/macros.rs index d8a4726..18a1f61 100644 --- a/libraries/cbor/src/macros.rs +++ b/libraries/cbor/src/macros.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! Convenience macros for working with CBOR values. + use crate::values::Value; use alloc::vec; use core::cmp::Ordering; @@ -110,6 +112,7 @@ pub fn destructure_cbor_map_peek_value( } } +/// Assert that the keys in a vector of key-value pairs are in canonical order. #[macro_export] macro_rules! assert_sorted_keys { // Last key diff --git a/libraries/cbor/src/reader.rs b/libraries/cbor/src/reader.rs index d8f159e..c7d4e14 100644 --- a/libraries/cbor/src/reader.rs +++ b/libraries/cbor/src/reader.rs @@ -12,11 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! Functionality for deserializing CBOR data into values. + use super::values::{Constants, SimpleValue, Value}; use crate::{cbor_array_vec, cbor_bytes_lit, cbor_map_collection, cbor_text, cbor_unsigned}; use alloc::str; use alloc::vec::Vec; +/// Possible errors from a deserialization operation. #[derive(Debug, PartialEq)] pub enum DecoderError { UnsupportedMajorType, @@ -32,6 +35,8 @@ pub enum DecoderError { OutOfRangeIntegerValue, } +/// Deserialize CBOR binary data to produce a single [`Value`], expecting that there +/// is no additional data. pub fn read(encoded_cbor: &[u8]) -> Result { let mut reader = Reader::new(encoded_cbor); let value = reader.decode_complete_data_item(Reader::MAX_NESTING_DEPTH)?; diff --git a/libraries/cbor/src/values.rs b/libraries/cbor/src/values.rs index 9326fc7..72fab95 100644 --- a/libraries/cbor/src/values.rs +++ b/libraries/cbor/src/values.rs @@ -12,24 +12,34 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! Types for expressing CBOR values. + use super::writer::write; use alloc::string::{String, ToString}; use alloc::vec::Vec; use core::cmp::Ordering; +/// Possible CBOR values. #[derive(Clone, Debug)] pub enum Value { + /// Unsigned integer value (uint). Unsigned(u64), - // We only use 63 bits of information here. + /// Signed integer value (nint). Only 63 bits of information are used here. Negative(i64), + /// Byte string (bstr). ByteString(Vec), + /// Text string (tstr). TextString(String), + /// Array/tuple of values. Array(Vec), + /// Map of key-value pairs. Map(Vec<(Value, Value)>), // TAG is omitted + /// Simple value. Simple(SimpleValue), } +/// Specific simple CBOR values. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum SimpleValue { FalseValue = 20, @@ -38,20 +48,30 @@ pub enum SimpleValue { Undefined = 23, } +/// Constant values required for CBOR encoding. pub struct Constants {} impl Constants { + /// Number of bits used to shift left the major type of a CBOR type byte. pub const MAJOR_TYPE_BIT_SHIFT: u8 = 5; + /// Mask to retrieve the additional information held in a CBOR type bytes, + /// ignoring the major type. pub const ADDITIONAL_INFORMATION_MASK: u8 = 0x1F; + /// Additional information value that indicates the largest inline value. pub const ADDITIONAL_INFORMATION_MAX_INT: u8 = 23; + /// Additional information value indicating that a 1-byte length follows. pub const ADDITIONAL_INFORMATION_1_BYTE: u8 = 24; + /// Additional information value indicating that a 2-byte length follows. pub const ADDITIONAL_INFORMATION_2_BYTES: u8 = 25; + /// Additional information value indicating that a 4-byte length follows. pub const ADDITIONAL_INFORMATION_4_BYTES: u8 = 26; + /// Additional information value indicating that an 8-byte length follows. pub const ADDITIONAL_INFORMATION_8_BYTES: u8 = 27; } impl Value { - // For simplicity, this only takes i64. Construct directly for the last bit. + /// Create an appropriate CBOR integer value (uint/nint). + /// For simplicity, this only takes i64. Construct directly for the last bit. pub fn integer(int: i64) -> Value { if int >= 0 { Value::Unsigned(int as u64) @@ -60,6 +80,7 @@ impl Value { } } + /// Create a CBOR boolean simple value. pub fn bool_value(b: bool) -> Value { if b { Value::Simple(SimpleValue::TrueValue) @@ -68,6 +89,7 @@ impl Value { } } + /// Return the major type for the [`Value`]. pub fn type_label(&self) -> u8 { // TODO use enum discriminant instead when stable // https://github.com/rust-lang/rust/issues/60553 @@ -128,6 +150,7 @@ impl PartialEq for Value { } impl SimpleValue { + /// Create a simple value from its encoded value. pub fn from_integer(int: u64) -> Option { match int { 20 => Some(SimpleValue::FalseValue), @@ -193,7 +216,9 @@ impl From for Value { } } +/// Trait that indicates that a type can be converted to a CBOR [`Value`]. pub trait IntoCborValue { + /// Convert `self` into a CBOR [`Value`], consuming it along the way. fn into_cbor_value(self) -> Value; } @@ -206,7 +231,9 @@ where } } +/// Trait that indicates that a type can be converted to a CBOR [`Option`]. pub trait IntoCborValueOption { + /// Convert `self` into a CBOR [`Option`], consuming it along the way. fn into_cbor_value_option(self) -> Option; } diff --git a/libraries/cbor/src/writer.rs b/libraries/cbor/src/writer.rs index 924ce48..9383a2b 100644 --- a/libraries/cbor/src/writer.rs +++ b/libraries/cbor/src/writer.rs @@ -12,9 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! Functionality for serializing CBOR values into bytes. + use super::values::{Constants, Value}; use alloc::vec::Vec; +/// Convert a [`Value`] to serialized CBOR data, consuming it along the way and appending to the provided vector. +/// Returns a `bool` indicating whether conversion succeeded. pub fn write(value: Value, encoded_cbor: &mut Vec) -> bool { let mut writer = Writer::new(encoded_cbor); writer.encode_cbor(value, Writer::MAX_NESTING_DEPTH)