dcSpark / cddl-codegen

Codegen serialization logic for CBOR automatically from a CDDL specification
MIT License
40 stars 14 forks source link

Better support for sized byte arrays #45

Open SebastienGllmt opened 2 years ago

SebastienGllmt commented 2 years ago

Given the CDDL

bytes .size 64

Expected code

bytes: [u8; 64]

Generated code

bytes: Vec<u8>

SebastienGllmt commented 2 years ago

It might be good to generate something like this on the Rust side as well (that we did manually for CML)

impl Nonce {
    pub const HASH_LEN: usize = 32;
}
SebastienGllmt commented 2 years ago

Seems there is a comment related to this in the code

// TODO: do we want to get rid of the rust struct and embed the tag / min/max size here?
// The tag is easy but the min/max size would require error types in any place that sets/modifies these in other structs.

Seems related to #41 as well

rooooooooob commented 2 years ago

Right now the behavior is to create a type like Foo(Vec<u8>) with range-checks for its creation (new/deserialization). The code that handles this is shared with types like bytes .size (10..20) which can't be done as static-sized arrays.

We could make a special case for when the size is fixed to instead either directly have that field in the newtype as [u8; N] with everything else as before, or to omit a typedef for type Foo = [u8; N]; on the rust side and use that only (and additionally to store the extra cbor encoding info wherever this type is used since with preserve-encodings=true what's generated is Foo { inner: Vec<u8>, inner_encoding: StringEncoding, })

and then on the wasm side either:

a) have the wrapper type there and have it do the range checking in its contructor e.g. pub fn set_some_hash(&mut self, foo: &Foo) with Foo having pub fn new(bytes: Vec<u8>) -> Result<Foo, JsError>

b) directly accept Vec<u8> anywhere the [u8; N] would be on the rust side, and make those functions throw. e.g. pub fn set_some_hash(&mut self, bytes: Vec<u8>) -> Result<(), JsError> on wasm for any type that has this fixed-size bytes as a field

Here is the kind of code that is currently generated in master:

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct String64 {
    inner: String,
    inner_encoding: StringEncoding,
}

impl String64 {
    pub fn get(&self) -> &String {
        &self.inner
    }

    pub fn new(inner: String) -> Result<Self, DeserializeError> {
        if inner.len() > 64 {
            return Err(DeserializeError::new("String64", DeserializeFailure::RangeCheck{ found: inner.len(), min: Some(0), max: Some(64) }));
        }
        Ok(Self {
            inner,
            inner_encoding: StringEncoding::default(),
        })
    }
}

for string64 = text .size (0..64) from cip-25 but it's basically the same as what happens here just swap > 64 for != 64 and String for Vec<u8>.