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

Add support for .cbor #41

Closed
SebastienGllmt opened this issue Jul 20, 2022 · 1 comment
Closed

Add support for .cbor #41

SebastienGllmt opened this issue Jul 20, 2022 · 1 comment

Comments

@SebastienGllmt
Copy link
Collaborator

SebastienGllmt commented Jul 20, 2022

Currently cddl-codegen does not support .cbor or .cborseq

I would be nice to support .cbor since it's used in multiple places in both the Byron spec and also it's used in the Babbage spec

I think the logic for .cbor is fairly similar -- just generate the same code as we do now but wrap the generated code in a (de)serialization of a cbor wrapper for the data.

More information: https://datatracker.ietf.org/doc/html/rfc8610#section-3.8.4

Example

I would expect the following definition

addrdistr =
  [  bootstrapEraDistr
  // singleKeyDistr
  ]

bytes .cbor addrdistr

to generate this kind of serialization code

impl cbor_event::se::Serialize for AddrdistrEnum {
    fn serialize<'se, W: Write>(&self, serializer: &'se mut Serializer<W>) -> cbor_event::Result<&'se mut Serializer<W>> {
        // note: this wrapping in `se` is what was added for the `.cbor` wrapper
        let mut se = Serializer::new_vec();
        let inner_cbor = match self {
            AddrdistrEnum::BootstrapEraDistr(x) => x.serialize(&mut se),
            AddrdistrEnum::SingleKeyDistr(x) => x.serialize(&mut se),
        }?;
        serializer.write_bytes(&inner_cbor.finalize())
    }
}

and this kind of deserialization code

impl Deserialize for AddrdistrEnum {
    fn deserialize<R: BufRead + Seek>(raw: &mut Deserializer<R>) -> Result<Self, DeserializeError> {
        (|| -> Result<_, DeserializeError> {
            // note: this line here is what was added for the .cbor wrapper
            let mut inner = &mut Deserializer::from(std::io::Cursor::new(raw.bytes()?));

            let len = inner.array()?;
            let mut read_len = CBORReadLen::new(len);
            let initial_position = inner.as_mut_ref().seek(SeekFrom::Current(0)).unwrap();
            match (|inner: &mut Deserializer<_>| -> Result<_, DeserializeError> {
                Ok(BootstrapEraDistr::deserialize_as_embedded_group(inner, len)?)
            })(inner)
            {
                Ok(variant) => return Ok(AddrdistrEnum::BootstrapEraDistr(variant)),
                Err(_) => inner.as_mut_ref().seek(SeekFrom::Start(initial_position)).unwrap(),
            };
            match (|inner: &mut Deserializer<_>| -> Result<_, DeserializeError> {
                Ok(SingleKeyDistr::deserialize_as_embedded_group(inner, len)?)
            })(inner)
            {
                Ok(variant) => return Ok(AddrdistrEnum::SingleKeyDistr(variant)),
                Err(_) => inner.as_mut_ref().seek(SeekFrom::Start(initial_position)).unwrap(),
            };
            match len {
                cbor_event::Len::Len(_) => read_len.finish()?,
                cbor_event::Len::Indefinite => match inner.special()? {
                    CBORSpecial::Break => read_len.finish()?,
                    _ => return Err(DeserializeFailure::EndingBreakMissing.into()),
                },
            }
            Err(DeserializeError::new("AddrdistrEnum", DeserializeFailure::NoVariantMatched.into()))
        })().map_err(|e| e.annotate("AddrdistrEnum"))
    }
}

Ways to implement

I think there are three main ways to implement this:

  1. We change the struct definition to pub struct Outer(Inner);. Keeping track of this is kind of complicated when you also think about having to support structs and enums - notably because handling structs would mean we have to keep track of whether or not something is cbor-in-cbor inside the intermediate representation. We can't have it in RustField since it's possibly only once choice of a field is cbor-in-cbor. We can't put it in anything that relies solely on Type2 because the operator lives in Type1.
  2. We keep it as pub struct Outer(Vec<u8>), but in the (de)serialization logic we properly add to cbor-in-cbor logic. Additionally, we can provide From<...> implementations to convert the bytes to the inner type
  3. We create some new wrapper type in the prelude like Cbor<Inner> and then make the struct pub struct Outer(Cbor<Inner>). This way we can keep everything in Outer just working with bytes and put all the cbor-in-cbor logic inside the Cbor<..> wrapper logic and have some generic From<...> implementation. This is the simplest solution I think to cover all cases, but the UX is kind of ugly. Also, this solution doesn't work for type aliases that map to primitives (ex: Vec<u8>) as they don't implement (de)serialize traits (and there isn't a good workaround for that because of Allow newtype pattern instead of type aliasing #44). Ignoring that issue, the best place for this in the intermediate representation would probably to change the new_type function to either add extra information to the Alias case or create a new CborInCbor case.

Also, these approaches are much harder on anonymous types so it's probably best to only support foo .cbor ident instead of allowing anonymous types where ident is

Lastly, I also didn't check the relation between this feature and `#6.24

@SebastienGllmt
Copy link
Collaborator Author

This was added as part of #50

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant