Skip to content

Conversation

@MeherRushi
Copy link

@MeherRushi MeherRushi commented Nov 3, 2025

Add CBOR Support to libyang

Attempt to resolve #2130

Overview

This PR adds comprehensive CBOR support to libyang, enabling parsing, validating, and printing instance data in CBOR format as specified in RFC 9254. The implementation uses libcbor as the underlying CBOR library and follows libyang's existing patterns and conventions.

Key Features

  • Optional Dependency: CBOR support is controlled via the compile-time flag ENABLE_CBOR_SUPPORT, allowing builds with or without CBOR capabilities
  • Full Parsing Support: Complete CBOR parser that constructs libyang data trees and links them with schema trees for validation
  • Full Printing Support: CBOR printer with parallel structure to printer_json.c, supporting all YANG node types (leaf, leaf-list, list, container, anydata, anyxml, RPC, action, notification)
  • Consistent API: Mirrors existing JSON functionality for ease of use and maintenance

Implementation Details

New Files

  • parser_cbor.c / parser_cbor.h - High-level CBOR parser
  • printer_cbor.c / printer_cbor.h - CBOR printer implementation
  • lcbor.c / lcbor.h - Consistent wrapper over libcbor following libyang coding style

Changes

  • Added ENABLE_CBOR_SUPPORT CMake flag to conditionally enable CBOR functionality
  • Integrated libcbor dependency with CMake find_package and conditional compilation
  • Introduced LY_VALUE_CBOR format with corresponding case statements throughout the codebase
  • Added lyd_parse_data_mem_len() high-level function for parsing CBOR from memory (required because CBOR binary data may contain null bytes, making strlen() unreliable)
  • Implemented consistent function structure mirroring JSON counterparts for parsing (name parsing, prefix handling, schema node lookup, metadata, opaque nodes, etc.)
  • Added proper error handling, null handling, array processing, and metadata support

TODO / Known Limitations

  • Refactor input handling - Move lyd_parse_data_mem_len to input handler (in.c) for better architecture
  • Clean up debug statements - Remove debug print statements from production code
  • Add more comments - Improve code documentation
  • CBOR SID support - Currently only named identifiers are supported; add SID (Schema Item iDentifier) support
  • Finalize input API - Determine optimal API design for CBOR input handling
  • Handle missing switch cases - Complete remaining switch statement cases
  • RPC/anydata edge cases - Address remaining edge cases for RPC and anydata handling

Dependencies

  • libcbor - Required when ENABLE_CBOR_SUPPORT=ON

Build Instructions

cmake -DENABLE_CBOR_SUPPORT=ON ..
make

Testing :

Considered an example.json file :

{
  "ietf-interfaces:interfaces": {
    "interface": [
      {
        "name": "eth0",
        "description": "Loopback",
        "type": "iana-if-type:ethernetCsmacd",
        "enabled": true,
        "oper-status": "up",
        "statistics": {
          "discontinuity-time": "2024-01-01T00:00:00Z"
        }
      }
    ]
  }
}

Converted the same into cbor notation and output in example.cbor using standard cbor libraries in python (below code - json_to_cbor.py):

import json
import cbor2
import time

# Read the JSON file
with open("example.json", "r", encoding="utf-8") as f:
    json_data = json.load(f)

# Write CBOR-encoded output
with open("example.cbor", "wb") as f:
    cbor2.dump(json_data, f)

print("✅ example.cbor written from example.json")

time.sleep(5)

with open("example.cbor", "rb") as f:
    obj = cbor2.load(f)

print(json.dumps(obj, indent=2))

Now ran the following json_test.c on the json file with the necessary yang modules iana-if-type.yang and ietf-interfaces.yang in the directory to see functionality on the json data

#include <stdlib.h>
#include <libyang/libyang.h>

int main(void) {
    struct ly_ctx *ctx = NULL;
    struct lyd_node *data = NULL;

    // Context: search current dir only, no auto-loading
    if (ly_ctx_new(".", LY_CTX_NO_YANGLIBRARY, &ctx) != LY_SUCCESS) {
        fprintf(stderr, "Context creation failed.\n");
        return 1;
    }

    // Load only required official modules
    if (lys_parse_path(ctx, "iana-if-type.yang", LYS_IN_YANG, NULL) != LY_SUCCESS ||
        lys_parse_path(ctx, "ietf-interfaces.yang", LYS_IN_YANG, NULL) != LY_SUCCESS) {
        fprintf(stderr, "Module loading failed.\n");
        ly_ctx_destroy(ctx);
        return 1;
    }

    // Parse data
    if (lyd_parse_data_path(ctx, "example.json", LYD_JSON, LYD_PARSE_STRICT, 0, &data) != LY_SUCCESS) {
        fprintf(stderr, "JSON parsing failed.\n");
        ly_ctx_destroy(ctx);
        return 1;
    }

    // Print parsed data
    lyd_print_file(stdout, data, LYD_JSON, 0);

    // Cleanup
    lyd_free_all(data);
    ly_ctx_destroy(ctx);
    return 0;
}

Repeated the same with the cbor_test.c file (in the same directory) utilizing the cbor features introduced to test the newly built library

#include <stdlib.h>
#include <libyang/libyang.h>

int main(void) {
    struct ly_ctx *ctx = NULL;
    struct lyd_node *data = NULL;

    // Context: search current dir only, no auto-loading
    if (ly_ctx_new(".", LY_CTX_NO_YANGLIBRARY, &ctx) != LY_SUCCESS) {
        fprintf(stderr, "Context creation failed.\n");
        return 1;
    }

    // Load only required official modules
    if (lys_parse_path(ctx, "iana-if-type.yang", LYS_IN_YANG, NULL) != LY_SUCCESS ||
        lys_parse_path(ctx, "ietf-interfaces.yang", LYS_IN_YANG, NULL) != LY_SUCCESS) {
        fprintf(stderr, "Module loading failed.\n");
        ly_ctx_destroy(ctx);
        return 1;
    }

    // Parse data
    if (lyd_parse_data_path(ctx, "example.cbor", LYD_CBOR, LYD_PARSE_STRICT, 0, &data) != LY_SUCCESS) {
        fprintf(stderr, "CBOR parsing failed.\n");
        ly_ctx_destroy(ctx);
        return 1;
    }

    // Print parsed to file named "output.cbor"
    // output.cbor should be a file pointer opened in binary write mode
    FILE *output_file = fopen("output.cbor", "wb");
    if (!output_file) {
        fprintf(stderr, "Failed to open output.cbor for writing.\n");
        ly_ctx_destroy(ctx);
        return 1;
    }
    lyd_print_file(output_file, data, LYD_CBOR, 0);
    fclose(output_file);
    // lyd_print_file(stdout, data, LYD_JSON, 0);


    // Cleanup
    lyd_free_all(data);
    ly_ctx_destroy(ctx);
    return 0;
}

Following output:

$ gcc cbor_test.c -lyang -o cbor
$./cbor 
DEBUG: Parsed CBOR data:
{"ietf-interfaces:interfaces": {"interface": [{"name": "eth0", "description": "Loopback", "type": "iana-if-type:ethernetCsmacd", "enabled": true, "oper-status": "up", "statistics": {"discontinuity-time": "2024-01-01T00:00:00Z"}}]}}

And we can change the input file from example.cbor to output.cbor to see the parsed data being parsed properly

$ gcc cbor_test.c -lyang -o cbor
(base) meherrushi@MeherRushi:~/Meher/learning/c/cbor/test$ ./cbor 
DEBUG: Parsed CBOR data:
{"ietf-interfaces:interfaces": {"interface": [{"name": "eth0", "description": "Loopback", "type": "iana-if-type:ethernetCsmacd", "enabled": true, "oper-status": "up", "statistics": {"discontinuity-time": "2024-01-01T00:00:00+00:00"}}]}}

note: shall upload the test files on another repo

feat: Add initial CBOR format support with libcbor integration (squahsed commit)

- Add ENABLE_CBOR_SUPPORT CMake flag to conditionally enable CBOR functionality
- Integrate libcbor dependency with CMake find_package and conditional compilation
- Add basic parser_cbor.c and parser_cbor.h files with foundational structures
- Implement initial CBOR parsing functions with libcbor as low-level parser
- Add necessary format switch statements throughout codebase for CBOR support
- Introduce lyd_parse_data_mem_len() high-level function for parsing CBOR from memory
  (required because CBOR binary data may contain null bytes, making strlen() unreliable)
- Build complete CBOR parser that constructs libyang data trees
- Link parsed CBOR data with schema trees for validation
- Fix initial memory allocation errors and remove dead code

This commit establishes the foundation for CBOR support in libyang, allowing the
library to be built with or without CBOR capabilities based on build configuration.
```
- arranged all the high level parser code in parser_cbor.c
- added all the common high level ctx variables to parser_internal.h
- added lcbor.c and lcbor.h as a wrapper over libcbor but to keep the coding style consistent
- made small changes to printer_cbor.c
- and made small function renaming in parser_cbor
- Reorganize high-level parser code in parser_cbor.c
- Add common high-level context variables to parser_internal.h
- Create lcbor.c and lcbor.h as a consistent wrapper over libcbor
- Maintain coding style consistency with existing libyang modules
- consistent function structure: all functions now mirror their JSON counterparts
	- lydcbor_parse_name() - parses member names with @ and : support
	- lydcbor_get_node_prefix() - handles module prefixes
	- lydcbor_get_snode() - finds schema nodes
	- lydcbor_value_type_hint() - determines value type hints
	- lydcbor_data_check_opaq() - checks if data should be parsed as opaque
	- lydcbor_metadata_finish() - links forward-referenced metadata
	- lydcbor_meta_attr() - parses metadata/attributes
	- lydcbor_parse_any() - handles anydata/anyxml
	- lydcbor_parse_instance_inner() - parses inner nodes
	- lydcbor_parse_instance() - parses single node instances
	- lydcbor_subtree_r() - main recursive parsing function
- using cbor_typeof() consistently to check types instead of multiple cbor_isa_*() calls
- better error handling, null handling and array processing and meta data support and qpaque node support
- introduced LY_VALUE_CBOR format and added all necessary case statements
  - Parallel structure to printer_json.c for consistency
  - Full support for all YANG node types (leaf, leaf-list, list, container, anydata, anyxml, RPC, action, notification)
@michalvasko
Copy link
Member

Thanks a lot for this effort and I will try to provide some feedback today or tomorrow. But I can immediately tell you to change the libyang base branch to devel, PRs are never merged into master directly.

Copy link
Member

@michalvasko michalvasko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally looks fine but some improvements are possible and the coding style is somewhat different to ours.

option(ENABLE_TOOLS "Build binary tools 'yanglint' and 'yangre'" ON)
option(ENABLE_COMMON_TARGETS "Define common custom target names such as 'doc' or 'uninstall', may cause conflicts when using add_subdirectory() to build this project" ON)
option(BUILD_SHARED_LIBS "By default, shared libs are enabled. Turn off for a static build." ON)
option(ENABLE_CBOR_SUPPORT "Enable CBOR support with libcbor" ON)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, an option is probably redundant. You can just search for libcbor and if found, the support will be added, otherwise not.

endif()

if(ENABLE_CBOR_SUPPORT)
find_package(PkgConfig)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is pkg-config really required? We have used it only when necessary, the else() branch should be fine except may be better to add a new FindCBOR.cmake module file.

* https://opensource.org/licenses/BSD-3-Clause
*/

#ifdef ENABLE_CBOR_SUPPORT
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems redundant if the define is around the whole file, the file should just not be compiled at all if disabled. In all the new files.

@@ -0,0 +1,131 @@
/**
* @file lcbor.h
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just cbor.c would probably fit better, like lyb.c and lyb.h.

*/
void lycbor_ctx_free(struct lycbor_ctx *cborctx)
{
if (cborctx)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant if and maybe the whole function.

struct lylyb_ctx *lybctx; /* LYB context */
};

#ifdef ENABLE_CBOR_SUPPORT
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really needed, I think, if you just declare struct lycbor_ctx;.

struct lyd_node **first_p, struct ly_in *in, uint32_t parse_opts, uint32_t val_opts, uint32_t int_opts,
struct ly_set *parsed, ly_bool *subtree_sibling, struct lyd_ctx **lydctx_p);

#ifdef ENABLE_CBOR_SUPPORT
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again not necessary, the function can exist and fail. I would like to keep these macros to a minimum.

}

/* unknown data node */
printf("checkpoint1-json\n");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leftovers.

/* but first process children of the object in the array */
do {
LY_CHECK_GOTO(ret = lydjson_subtree_r(lydctx, *node_p, lyd_node_child_p(*node_p), NULL), cleanup);
LY_CHECK_GOTO(ret = lydjson_subtree_r(lydctx, *node_p, (*node_p), NULL), cleanup);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes how JSON opaque nodes are stored, what is the reason?

escaped when the anydata is printed in XML format. */
LYD_ANYDATA_XML, /**< Value is a string containing the serialized XML data. */
LYD_ANYDATA_JSON, /**< Value is a string containing the data modeled by YANG and encoded as I-JSON. */
LYD_ANYDATA_CBOR, /**< Value is a string containing the data modeled by YANG and encoded as CBOR. */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LYD_ANYDATA_LYB was removed in the devel branch and I think for the same reason LYD_ANYDATA_CBOR is not required. Only objects are allowed in anydata, so it should always be possible to parse them into LYD_ANYDATA_DATATREE.

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

Successfully merging this pull request may close these issues.

CBOR support

2 participants