Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: simple type support #145

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions ciborium/src/de/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ pub use error::Error;

use alloc::{string::String, vec::Vec};

use crate::{simple_type::SimpleTypeAccess, tag::TagAccess};
use ciborium_io::Read;
use ciborium_ll::*;
use serde::de::{self, value::BytesDeserializer, Deserializer as _};

use crate::tag::TagAccess;

trait Expected<E: de::Error> {
fn expected(self, kind: &'static str) -> E;
}
Expand Down Expand Up @@ -213,8 +212,15 @@ where
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")),
Header::Simple(v @ simple::UNDEFINED) => {
visitor.visit_enum(SimpleTypeAccess::new(self, v))
}
// Those have to be registered via Standard Actions or are reserved so we should error whenever we
// encounter one. This crate should be updated once new entries in this range are added
// in the IANA registry
h @ Header::Simple(0..=31) => Err(h.expected("known simple value")),
// However we should support arbitrary simple types
Header::Simple(v) => visitor.visit_enum(SimpleTypeAccess::new(self, v)),

h @ Header::Break => Err(h.expected("non-break")),
}
Expand Down Expand Up @@ -604,6 +610,19 @@ where
let access = TagAccess::new(me, tag);
visitor.visit_enum(access)
});
} else if name == "@@ST@@" {
return match self.decoder.pull()? {
Header::Simple(v @ simple::UNDEFINED) => {
self.decoder.push(Header::Positive(v as u64));
visitor.visit_enum(SimpleTypeAccess::new(self, v))
}
h @ Header::Simple(0..=31) => Err(h.expected("known simple value")),
Header::Simple(v) => {
self.decoder.push(Header::Positive(v as u64));
visitor.visit_enum(SimpleTypeAccess::new(self, v))
}
h => Err(h.expected("known simple value")),
};
}

loop {
Expand Down
1 change: 1 addition & 0 deletions ciborium/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ extern crate alloc;

pub mod de;
pub mod ser;
pub mod simple_type;
pub mod tag;
pub mod value;

Expand Down
11 changes: 10 additions & 1 deletion ciborium/src/ser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,16 @@ where
variant: &'static str,
value: &U,
) -> Result<(), Self::Error> {
if name != "@@TAG@@" || variant != "@@UNTAGGED@@" {
if name == "@@ST@@" && variant == "@@SIMPLETYPE@@" {
use serde::ser::Error as _;

let v = crate::Value::serialized(value).map_err(Error::custom)?;
let v = v
.as_integer()
.ok_or_else(|| Error::custom("Internal error handling simple types"))?;
let v = u8::try_from(v).map_err(Error::custom)?;
return Ok(self.0.push(Header::Simple(v))?);
} else if name != "@@TAG@@" || variant != "@@UNTAGGED@@" {
self.0.push(Header::Map(Some(1)))?;
self.serialize_str(variant)?;
}
Expand Down
141 changes: 141 additions & 0 deletions ciborium/src/simple_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
//! Contains helper types for dealing with CBOR simple types

use serde::{de, de::Error as _, forward_to_deserialize_any, ser, Deserialize, Serialize};

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename = "@@ST@@")]
enum Internal {
/// The integer can either be 23, or (32..=255)
#[serde(rename = "@@SIMPLETYPE@@")]
SimpleType(u8),
}

/// A CBOR simple value
/// See https://datatracker.ietf.org/doc/html/rfc8949#section-3.3
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SimpleType(pub u8);

impl<'de> Deserialize<'de> for SimpleType {
#[inline]
fn deserialize<D: de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
match Internal::deserialize(deserializer)? {
Internal::SimpleType(t) => Ok(SimpleType(t)),
}
}
}

impl Serialize for SimpleType {
#[inline]
fn serialize<S: ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
Internal::SimpleType(self.0).serialize(serializer)
}
}

pub(crate) struct SimpleTypeAccess<D> {
parent: Option<D>,
state: usize,
typ: u8,
}

impl<D> SimpleTypeAccess<D> {
pub fn new(parent: D, typ: u8) -> Self {
Self {
parent: Some(parent),
state: 0,
typ,
}
}
}

impl<'de, D: de::Deserializer<'de>> de::Deserializer<'de> for &mut SimpleTypeAccess<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("@@SIMPLETYPE@@"),
_ => visitor.visit_u8(self.typ),
}
}

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 SimpleTypeAccess<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 SimpleTypeAccess<D> {
type Error = D::Error;

#[inline]
fn unit_variant(self) -> Result<(), Self::Error> {
Err(Self::Error::custom("expected simple type"))
}

#[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 simple_type"))
}
}

impl<'de, D: de::Deserializer<'de>> de::SeqAccess<'de> for SimpleTypeAccess<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,
})
}
}
24 changes: 22 additions & 2 deletions ciborium/src/value/de.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// SPDX-License-Identifier: Apache-2.0

use crate::tag::TagAccess;
use crate::{simple_type::SimpleTypeAccess, tag::TagAccess};

use super::{Error, Integer, Value};

use alloc::{boxed::Box, string::String, vec::Vec};
use core::iter::Peekable;

use ciborium_ll::tag;
use ciborium_ll::{simple, tag};
use serde::de::{self, Deserializer as _};

impl<'a> From<Integer> for de::Unexpected<'a> {
Expand Down Expand Up @@ -36,6 +36,7 @@ impl<'a> From<&'a Value> for de::Unexpected<'a> {
Value::Map(..) => Self::Map,
Value::Null => Self::Other("null"),
Value::Tag(..) => Self::Other("tag"),
Value::Simple(..) => Self::Other("simple"),
}
}
}
Expand Down Expand Up @@ -218,6 +219,7 @@ impl<'a> Deserializer<&'a Value> {
.map(|x| x ^ !0)
.map_err(|_| err())
.and_then(|x| x.try_into().map_err(|_| err()))?,
Value::Simple(x) => i128::from(*x).try_into().map_err(|_| err())?,
_ => return Err(de::Error::invalid_type(self.0.into(), &"(big)int")),
})
}
Expand All @@ -228,13 +230,19 @@ impl<'a, 'de> de::Deserializer<'de> for Deserializer<&'a Value> {

#[inline]
fn deserialize_any<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
use serde::ser::Error as _;
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::Simple(v @ simple::UNDEFINED) => {
visitor.visit_enum(SimpleTypeAccess::new(self, *v))
}
Value::Simple(0..=31) => Err(Self::Error::custom("Unsupported simple type")),
Value::Simple(v) => visitor.visit_enum(SimpleTypeAccess::new(self, *v)),

Value::Tag(t, v) => {
let parent: Deserializer<&Value> = Deserializer(v);
Expand Down Expand Up @@ -493,6 +501,18 @@ impl<'a, 'de> de::Deserializer<'de> for Deserializer<&'a Value> {
let parent: Deserializer<&Value> = Deserializer(val);
let access = TagAccess::new(parent, tag);
return visitor.visit_enum(access);
} else if name == "@@ST@@" {
use serde::ser::Error as _;
return match self.0 {
Value::Simple(v @ simple::UNDEFINED) => {
visitor.visit_enum(SimpleTypeAccess::new(Deserializer(self.0), *v))
}
Value::Simple(0..=31) => return Err(Error::custom("Unsupported simple type")),
Value::Simple(v) => {
visitor.visit_enum(SimpleTypeAccess::new(Deserializer(self.0), *v))
}
_ => Err(Error::custom("Implementation error for simple type")),
};
}

match self.0 {
Expand Down
3 changes: 3 additions & 0 deletions ciborium/src/value/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ pub enum Value {

/// A map
Map(Vec<(Value, Value)>),

/// A CBOR "Simple Value" other than true, false, or null
Simple(u8),
}

impl Value {
Expand Down
18 changes: 18 additions & 0 deletions ciborium/src/value/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,23 @@ use super::{Error, Value};
use alloc::{vec, vec::Vec};

use ::serde::ser::{self, SerializeMap as _, SerializeSeq as _, SerializeTupleVariant as _};
use ciborium_ll::simple;

impl ser::Serialize for Value {
#[inline]
fn serialize<S: ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
use serde::ser::Error as _;

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::Simple(x @ simple::UNDEFINED) => {
serializer.serialize_newtype_struct("@@SIMPLETYPE@@", x)
}
Value::Simple(0..=31) => Err(S::Error::custom("Unsupported simple type")),
Value::Simple(x) => serializer.serialize_newtype_struct("@@SIMPLETYPE@@", x),

Value::Tag(t, v) => {
let mut acc = serializer.serialize_tuple_variant("@@TAG@@", 0, "@@TAGGED@@", 2)?;
Expand Down Expand Up @@ -190,6 +198,16 @@ impl ser::Serializer for Serializer<()> {
) -> Result<Value, Error> {
Ok(match (name, variant) {
("@@TAG@@", "@@UNTAGGED@@") => Value::serialized(value)?,
("@@ST@@", "@@SIMPLETYPE@@") => {
use serde::ser::Error as _;

let v = Value::serialized(value)?;
let v = v
.as_integer()
.ok_or_else(|| Error::custom("Internal error handling simple types"))?;
let v = u8::try_from(v).map_err(Error::custom)?;
Value::Simple(v)
}
_ => vec![(variant.into(), Value::serialized(value)?)].into(),
})
}
Expand Down
53 changes: 53 additions & 0 deletions ciborium/tests/simple_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: Apache-2.0

extern crate alloc;

use ciborium::{de::from_reader, ser::into_writer, simple_type::SimpleType, value::Value};
use rstest::rstest;
use serde::{de::DeserializeOwned, Serialize};

use core::fmt::Debug;

#[rstest(item, bytes, value, encode, success,
case(SimpleType(0), "e0", Value::Simple(0), true, false), // Registered via Standard Actions
case(SimpleType(19), "f3", Value::Simple(19), true, false), // Registered via Standard Actions
case(SimpleType(23), "f7", Value::Simple(23), true, true), // CBOR simple value "undefined"
case(SimpleType(32), "f820", Value::Simple(32), true, true),
case(SimpleType(255), "f8ff", Value::Simple(255), true, true),
case(vec![SimpleType(255)], "81f8ff", Value::Array(vec![Value::Simple(255)]), true, true),
)]
fn test<T: Serialize + DeserializeOwned + 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 => panic!("{:?}", e),
Err(..) => (),
}

// Decode from value
match value.deserialized() {
Ok(x) if success => assert_eq!(item, x),
Ok(..) => panic!("unexpected success"),
Err(e) if success => panic!("{:?}", e),
Err(..) => (),
}
}
Loading