Skip to content

Developer Experience with CBOR on the wire #19

Description

@c2bo

#5 is discussing the general serialization format, but also mixes in the dev ux related discussion points.

My take-away from the discussions at IETF 126 was that people were pointing towards CDDL and defining CBOR and JSON in one CDDL. Make sure it is clear that we use CBOR over the wire, but have a clear definition of JSON representations for configuration, debugging, etc.
Copied over from the other issue:

If we use integer based maps in CBOR, we could in theory define in CDDL values that are different between CBOR and JSON representation which would allow us to define keys in the map that are different from CBOR (integer) and JSON (string). Taken from cbor-wg Wiki:

JC<"v", 2>
binary-value = JC<base64-string, bstr>

JC<"v",2> defines the value as "v" for JSON and 2 for CBOR.

RFC9741 introduces control operators that allows to express things like base64 encoding in CDDL:

signature-for-json = text .b64u signature
signature = bytes .cbor COSE_Sign1

Activity

  1. awoie commented on Jul 31, 2026

    @awoie
    Contributor

    WG discussion:

  2. awoie commented on Jul 31, 2026

    @awoie
    Contributor

    @c2bo @MasterKale I am wondering if you could provide examples for the CBOR / CDDL used in #29 and check what works best?

  3. andrewhughes3000 commented on Aug 3, 2026

    @andrewhughes3000
    Member

    Please post the examples in this Issue for discussion/review by WG.

  4. c2bo commented on Aug 3, 2026

    @c2bo
    MemberAuthor

    @c2bo @MasterKale I am wondering if you could provide examples for the CBOR / CDDL used in #29 and check what works best?

    That was my proposal in last call -> that is definitely the plan. I am on vacation this week, so i am not sure if i will make progress this week from my side.

  5. MasterKale commented on Aug 3, 2026

    @MasterKale
    Collaborator

    Here's an initial exploration at combining RFC 9165's .feature to define a helper notation to colocate type annotations between CBOR and JSON encodings, with RFC 9741 for some JSON-friendler datatypes.

    BTW, since the various structures in #29 are defined for CBOR as the initial/"default" encoding scheme, I think the "JC<>" notation for expressing cross-encoding datatypes should probably be "CJ<>" instead. Accordingly, CJ<..., ...> is used below to indicate "this datatype in a CBOR domain, this other datatype in a JSON domain":

    Examples

    Using CDDL's .feature to define a "CBOR type, JSON type"...type

    CJ<C,J> = C .feature "cbor" / J .feature "json"
    

    Values using CDDL datatype primitives get the CJ<> treatment

    label = CJ<int, int> / CJ<tstr,tstr>
    values = CJ<any, any>
    payload = CJ<bstr, b64u>
    empty_or_serialized_map = CJ<bstr .cbor header_map, TODO> / CJ<bstr .size 0, TODO>
    

    Values referring to "complex" CDDL datatypes don't get the CJ<> treatment

    reader_auth = COSE_Sign
    
    COSE_Sign = [
        Headers,
        payload : CJ<bstr, b64u> / CJ<nil, nil>,
        signatures : [+ COSE_Signature]
    ]
    
    COSE_Signature =  [
        Headers,
        signature : CJ<bstr, b64u>
    ]
    
    Headers = (
        protected : empty_or_serialized_map,
        unprotected : header_map
    )
    
    header_map = {
        Generic_Headers,
        * label => values
    }
    
    Generic_Headers = (
        ? 1 => CJ<int, int> / CJ<tstr, tstr>  ; algorithm identifier
        ? 2 => [+label],                      ; criticality
        ? 3 => CJ<tstr, tstr> / CJ<int, int>, ; content type
        ? 4 => CJ<bstr, b64u>,                ; key identifier
        ? ( 5 => CJ<bstr, b64u> //            ; IV
            6 => CJ<bstr, b64u> )             ; Partial IV
    )
    

    Experimenting with adding CJ<> notation to dchp/pull/29

    Pulling out some sections from #29 to update with this syntax

    Request Structure

    The top-level CredentialRequest contains:

    Field Key Type Presence Description
    payload 1 CJ<bytes, b64u> M CBOR-encoded RequestPayload
    reader_auth 2 COSE_Sign O Optional reader authentication signature

    The RequestPayload contains:

    Field Key Type Presence Description
    ScenarioSets 1 { + ScenarioRef => ScenarioSet } M Map of scenarios the reader is requesting, keyed by ScenarioRef
    CredentialQueries 2 { + CredentialRef => CredentialQuery } M Dictionary of all requested credential definitions
    encryption_context 3 EncryptionContext C Primary response encryption context; absent when ISO/IEC 18013-5 session encryption is used
    additional_encryption_contexts 4 { + CJ<int, int> => EncryptionContext } O Additional encryption contexts for multi-key routing, keyed by integer

    Credential Queries

    A CredentialQuery defines the exact credential requirements for a single credential type.

    Field Key Type Presence Description
    credential_format 1 CJ<tstr, tstr> M Format identifier (e.g., "mso_mdoc", "vc+sd-jwt")
    credential_type 2 CJ<tstr, tstr> M Credential type (e.g., "org.iso.18013.5.1.mDL")
    elements_dict 3 { + ElementRef => DataElementDef } M Dictionary of requested data elements
    requested_elements 4 ElementLogic M Boolean logic tree defining which elements are required
    general_extensions 5 generalExtensions O Protocol-level extensions applicable across formats
    format_extensions 6 formatExtensions O Format-specific extensions (e.g., mdocExtensions, sdjwtExtensions)
    encryption_ref 7 CJ<int, int> O Reference to an entry in additional_encryption_contexts; absent means the main encryption_context is used

    My Opinions

    • Any use of CDDL primitive datatypes (e.g. bstr, tstr, int) should get the CJ<> treatment, no matter how obvious it might seem a datatype maps to a JavaScript type
    • Custom CDDL types (e.g. COSE_Signature, Headers, Generic_Headers) can probably go without CJ<> annotations
    • CDDL Arrays and Maps have matching types in JavaScript so there's seemingly no need to give them the CJ<> treatment either

    I think after going through this exercise I've come to the opinion that the CJ<..., ...> notation style seems reasonable to me. I think I'd personally prefer to read this style over having to Cmd+F and jump between CBOR-specific and JSON-specific structs throughout an already complex document.

  6. MasterKale commented on Aug 3, 2026

    @MasterKale
    Collaborator

    And I apologize, I'm pretty sure I'm using "CBOR" and "CDDL" a bit too interchangeably above. I don't often talk about data structures using CDDL terminology so bear with me while I learn from the WG through osmosis 😅

  7. MasterKale commented on Aug 6, 2026

    @MasterKale
    Collaborator

    Make sure it is clear that we use CBOR over the wire, but have a clear definition of JSON representations for configuration, debugging, etc.

    I was mulling over my exploration above and agree it'd be necessary, around whatever such notation we ultimately land on, to emphasize that the JSON datatype definitions for any given data structure is only for representing an instance of that data structure in isolation. That is, there'd be no actual JSON-shaped values within the deeper parts of the protocol which expects everything to use CBOR encoding 🤔

  8. awoie commented on Aug 10, 2026

    @awoie
    Contributor

    WG discussion:

    • heavy annotated CDN might be an alternative; CDN is superset of JSON, e.g., can do comments.
    • for examples, no JSON notation; JSON notation is for debug/ output mostly.
    • in a lot of places types are the same in JSON and CBOR; only in a few places they differ, e.g., bytestr vs b64u. we could agree on this.
    • CDN and JC serve different purposes.
    • CDDL is well supported, e.g., Rust, which can also do JSON/CBOR conversion.
    • Need some experimenting with libraries.
    • Next step could be to apply this to the examples of the initial document PR.
    • Not a blocker for the remaining specification.
  9. added
    pending-validationRevisit after the specification matures to validate the decision before closing the issue.
    and removed
    pending-validationRevisit after the specification matures to validate the decision before closing the issue.
    on Aug 27, 2026
  10. jogu commented on Sep 7, 2026

    @jogu
    Contributor

    Discussed on today's call.

    Christian is experimenting with the proposed example format in rust, autogenerating data structures from the CDDL and it seems to be working so far, he will report back once he's experimented more. We'd need to check the socket syntax used in #57 is supported by CDDL parsers.

  11. kohex-net commented on Sep 10, 2026

    @kohex-net

    Below our two cents trying this out in Rust.

    We had to perform some text subsitutions, no brainer, so that it can be parsed :

    • An entry like payload: 1 => bstr carries two member keys, the readable name and the actual integer key, where the grammar allows only one,
      -> kept 1 => and moved the name to a comment (it should work the other way).
    • * int / tstr => any uses a type choice as a key without parentheses.
      -> wrote * (int / tstr) => any.

    With those two edits, cddl 0.10.7 reports the document conformant.
    Both cddl et cddl-cat stop at the same construct, and RFC 8610 agrees with them, so this is about how the CDDL is written rather than about the tools.

    Once it parses, the answer is conditional

    We wrote a RequestPayload by hand, encoded it with ciborium, and cddl-cat 0.7.1 validates it against the fixed Request CDDL.

    That is the part that works. Generation does not.

    No Rust crate we found generates examples.
    Across the eight crates surveyed on crates.io (2026-09-08), none of them emits a conforming instance from a schema.
    And anweiss/cddl lists it as an unchecked goal in its README.
    The only generator we located is www/src/sample-gen.js in that same repository, serving the browser playground, not reachable from the crate.
    We did not look at the Python, Java or Go ecosystems, not used in our project.

    At the end what it produced does not survive the trip back to CBOR land, for two reasons :

    • every map in the document ends with an RFU wildcard, which the generator satisfies by inserting a junk entry everywhere,
    • and a bstr comes out as a base64-like text string, which is no longer a bstr once re-encoded.

    Neither looks fixable by writing the CDDL differently.
    Dropping the RFU wildcards would address the first, at the cost of the extensibility they exist for.
    The second is a property of JSON, and no rewriting reaches it.

    We could not get a Rust validator to enforce the CJ<> notation as defined in #19 (comment)

    We tested the CJ<C,J> = C .feature "cbor" / J .feature "json" form against cddl 0.10.7 and cddl-cat 0.7.1.

    On a member key it parses and then does not validate.

    On a value, in that exact form, validation stops applying : an integer passes against CJ<bstr, tstr>.

    Two things behind this:

    • cddl-cat 0.7.1 does not implement .feature at all, so there is no second opinion in Rust.
    • .feature is not a selector in cddl : enabling or disabling a feature does not change what it accepts. In validator/cbor.rs a not enabled feature is pushed onto disabled_features and the target skipped, with no error raised.

    Sockets, as used in #57

    To @jogu's question :

    • cddl 0.10.7 accepts socket syntax in the schema, and validates instances against it correctly : it rejects a value matching no socket member, and a socket that was never extended.

    • cddl-cat 0.7.1 does not accept the syntax at all, so it never gets to validating anything. That is a feature it has not implemented, not a limit of CDDL.

    So the fixed document parses under cddl only.

    But, cddl gets also two constructs wrong, and the document is built on both :

    • a table whose value is a named map containing a wildcard,
    • a table whose key is a named type alias.
      That is why our hand-written RequestPayload fails there. We have not filed these two issues yet.

    As things stand, no Rust tool validates the Request CDDL as written and supports the sockets.

    What this leaves

    Today's Rust tooling gives you CBOR validation and type generation, both usable in CI.

    It does not give you example generation, and we could not get one CDDL to validate both encodings with the precision CJ<> is aiming for.
    A plain (1 / "1") => (bstr / tstr) choice validates both, but it is a union : it documents the two encodings rather than constraining either.
    So CJ<> would carry its meaning to readers rather than to tools, at least until an implementation catches up.

    We can produce a script to reproduce this if you need.

  12. added
    pending-validationRevisit after the specification matures to validate the decision before closing the issue.
    on Sep 19, 2026
  13. c2bo commented on Oct 5, 2026

    @c2bo
    MemberAuthor

    I've played a bit around with how the different options feel to me. Example CDDL from the current draft:

    EncryptionContext = {
      key:        1 => COSE_Key,
      nonce:      2 => bstr,
      algorithms: 3 => [ + int ],     ; COSE algorithm identifiers (e.g. HPKE ciphersuites)
      * int / tstr => any             ; RFU / application-specific extensions
    }
    

    As long as always keep naming the keys, even if we use a scalar value in CBOR, that seems to be sufficient to me and we could add an implementation consideration to use those strings directly to create a JSON representation if necessary for debugging and configuration.

    That would provide clear guidance and not make the CDDL harder to read. Given the complexity that seems to be added if we want to fully define a JSON encoding, that currently feels like the leaner and more practical approach to me instead of going the full CDDL for both formats way.

  14. MasterKale commented on Oct 5, 2026

    @MasterKale
    Collaborator

    That would provide clear guidance and not make the CDDL harder to read. Given the complexity that seems to be added if we want to fully define a JSON encoding, that currently feels like the leaner and more practical approach to me instead of going the full CDDL for both formats way.

    I could support this outcome, paired with an opinionated suggestion to use a single string encoding when bstr values need to be conveyed as a string (i.e. encode to base64url or hex.) The latter would be to encourage consistent implementations/examples/documentation/etc... for all the heterogenous implementations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

pending-validationRevisit after the specification matures to validate the decision before closing the issue.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions