From 9f43f21e90930aad782581aab08212bac346bab8 Mon Sep 17 00:00:00 2001 From: Michal Vasko Date: Thu, 30 Oct 2025 09:15:30 +0100 Subject: [PATCH 1/9] parser lyb BUGFIX parsing LYB data without context hash checking To support similar context restrictions to the previous libyang version. Refs #2444 --- src/parser_lyb.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/parser_lyb.c b/src/parser_lyb.c index 66ac68663..83b06f01b 100644 --- a/src/parser_lyb.c +++ b/src/parser_lyb.c @@ -1584,15 +1584,7 @@ lyb_parse_header(struct lylyb_ctx *lybctx) /* context hash */ lyb_read((uint8_t *)&hash, sizeof hash, lybctx); - if (!hash) { - /* fine for no data */ - lybctx->empty_hash = 1; - } else if (lybctx->ctx && (hash != ly_ctx_get_modules_hash(lybctx->ctx))) { - /* context is not set if called by lyd_lyb_data_length() */ - LOGERR(lybctx->ctx, LY_EINVAL, "Different current LYB context modules hash compared to the one stored in the " - "LYB file (0x%08x != 0x%08x).", hash, ly_ctx_get_modules_hash(lybctx->ctx)); - return LY_EINVAL; - } + /* skip hash checking to support parsing data with less strict requirements (as in the previous versions) */ return LY_SUCCESS; } From f092e241428e0f8743246bca3763ac72e1279fc4 Mon Sep 17 00:00:00 2001 From: Michal Vasko Date: Thu, 30 Oct 2025 09:21:35 +0100 Subject: [PATCH 2/9] SOVERSION bump to version 3.9.14 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7414e1f70..b976e03cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,7 +66,7 @@ set(LIBYANG_VERSION ${LIBYANG_MAJOR_VERSION}.${LIBYANG_MINOR_VERSION}.${LIBYANG_ # set version of the library set(LIBYANG_MAJOR_SOVERSION 3) set(LIBYANG_MINOR_SOVERSION 9) -set(LIBYANG_MICRO_SOVERSION 13) +set(LIBYANG_MICRO_SOVERSION 14) set(LIBYANG_SOVERSION_FULL ${LIBYANG_MAJOR_SOVERSION}.${LIBYANG_MINOR_SOVERSION}.${LIBYANG_MICRO_SOVERSION}) set(LIBYANG_SOVERSION ${LIBYANG_MAJOR_SOVERSION}) From c2ddd01b9b810a30d6a7d6749a3bc9adeb7b01fb Mon Sep 17 00:00:00 2001 From: Michal Vasko Date: Thu, 30 Oct 2025 09:21:44 +0100 Subject: [PATCH 3/9] VERSION bump to version 3.13.6 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b976e03cd..7fa2204c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,7 +60,7 @@ set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) # set version of the project set(LIBYANG_MAJOR_VERSION 3) set(LIBYANG_MINOR_VERSION 13) -set(LIBYANG_MICRO_VERSION 5) +set(LIBYANG_MICRO_VERSION 6) set(LIBYANG_VERSION ${LIBYANG_MAJOR_VERSION}.${LIBYANG_MINOR_VERSION}.${LIBYANG_MICRO_VERSION}) # set version of the library From de5666b876d66dfdcc6a75342123b38f2cce5944 Mon Sep 17 00:00:00 2001 From: MeherRushi Date: Sat, 19 Jul 2025 13:03:16 +0530 Subject: [PATCH 4/9] ``` 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. ``` --- CMakeLists.txt | 45 +- src/parser_cbor.c | 1076 ++++++++++++++++++++++++++++++++++++++ src/parser_cbor.h | 279 ++++++++++ src/parser_data.h | 17 + src/parser_json.c | 2 +- src/printer_data.c | 3 + src/tree.h | 1 + src/tree_data.c | 29 +- src/tree_data.h | 4 +- src/tree_data_internal.h | 2 + 10 files changed, 1454 insertions(+), 4 deletions(-) create mode 100644 src/parser_cbor.c create mode 100644 src/parser_cbor.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7fa2204c2..1346fdf4b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -231,6 +231,7 @@ set(format_sources src/*.h src/plugins_exts/* src/plugins_types/*) + # # options # @@ -249,6 +250,7 @@ option(ENABLE_YANGLINT_INTERACTIVE "Enable interactive CLI yanglint" ON) 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) set(YANG_MODULE_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/yang/modules/libyang" CACHE STRING "Directory where to copy the YANG modules to") if(ENABLE_INTERNAL_DOCS) @@ -316,6 +318,42 @@ if(ENABLE_COVERAGE) gen_coverage_enable(${ENABLE_TESTS}) endif() +if(ENABLE_CBOR_SUPPORT) + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + pkg_check_modules(LIBCBOR REQUIRED libcbor) + if(LIBCBOR_FOUND) + message(STATUS "libcbor found, enabling CBOR support") + add_definitions(-DENABLE_CBOR_SUPPORT) + include_directories(${LIBCBOR_INCLUDE_DIRS}) + # Add CBOR parser files to the library sources + list(APPEND libsrc src/parser_cbor.c) + list(APPEND headers src/parser_cbor.h) + # Add CBOR files to format sources + list(APPEND format_sources src/parser_cbor.c src/parser_cbor.h) + else() + message(FATAL_ERROR "libcbor not found! Please install libcbor development package or disable CBOR support with -DENABLE_CBOR_SUPPORT=OFF") + endif() + else() + # Fallback to find_path and find_library if pkg-config is not available + find_path(LIBCBOR_INCLUDE_DIR cbor.h) + find_library(LIBCBOR_LIBRARY cbor) + if(LIBCBOR_INCLUDE_DIR AND LIBCBOR_LIBRARY) + message(STATUS "libcbor found via find_path/find_library, enabling CBOR support") + add_definitions(-DENABLE_CBOR_SUPPORT) + include_directories(${LIBCBOR_INCLUDE_DIR}) + set(LIBCBOR_LIBRARIES ${LIBCBOR_LIBRARY}) + # Add CBOR parser files to the library sources + list(APPEND libsrc src/parser_cbor.c) + list(APPEND headers src/parser_cbor.h) + # Add CBOR files to format sources + list(APPEND format_sources src/parser_cbor.c src/parser_cbor.h) + else() + message(FATAL_ERROR "libcbor not found! Please install libcbor development package or disable CBOR support with -DENABLE_CBOR_SUPPORT=OFF") + endif() + endif() +endif() + if ("${BUILD_TYPE_UPPER}" STREQUAL "DEBUG") # enable before adding tests to let them detect that format checking is available - one of the tests is format checking source_format_enable(0.77) @@ -408,6 +446,11 @@ find_package(PCRE2 10.21 REQUIRED) include_directories(${PCRE2_INCLUDE_DIRS}) target_link_libraries(yang ${PCRE2_LIBRARIES}) +# link libcbor if CBOR support is enabled +if(ENABLE_CBOR_SUPPORT) + target_link_libraries(yang ${LIBCBOR_LIBRARIES}) +endif() + # XXHash include and library find_package(XXHash) if(XXHASH_FOUND) @@ -497,4 +540,4 @@ add_custom_target(cclean COMMAND make clean COMMAND find . -iname '*cmake*' -not -name CMakeLists.txt -not -path './CMakeModules*' -exec rm -rf {} + COMMAND rm -rf Makefile Doxyfile - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) \ No newline at end of file diff --git a/src/parser_cbor.c b/src/parser_cbor.c new file mode 100644 index 000000000..38104f31e --- /dev/null +++ b/src/parser_cbor.c @@ -0,0 +1,1076 @@ +/** + * @file parser_cbor.c + * @author + * @brief CBOR data parser for libyang + * + * Copyright (c) 2020 - 2023 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#define _GNU_SOURCE + +#ifdef ENABLE_CBOR_SUPPORT + +#include "parser_cbor.h" + +#include +#include +#include +#include +#include + +#include "compat.h" +#include "context.h" +#include "dict.h" +#include "in_internal.h" +#include "log.h" +#include "ly_common.h" +#include "parser_data.h" +#include "parser_internal.h" +#include "plugins_exts.h" +#include "set.h" +#include "tree.h" +#include "tree_data.h" +#include "tree_data_internal.h" +#include "tree_schema.h" +#include "validation.h" + +#include "cbor.h" + + +#include +#include + +void print_json(cbor_item_t *item); + +void print_json_string(const cbor_item_t *item) { + size_t length = cbor_string_length(item); + char *str = (char *)cbor_string_handle(item); + printf("\"%.*s\"", (int)length, str); +} + +void print_json_map(const cbor_item_t *item) { + printf("{"); + size_t size = cbor_map_size(item); + struct cbor_pair *pairs = cbor_map_handle(item); + + for (size_t i = 0; i < size; ++i) { + print_json(pairs[i].key); + printf(": "); + print_json(pairs[i].value); + if (i < size - 1) printf(", "); + } + + printf("}"); +} + +void print_json_bool(const cbor_item_t *item) { + printf(cbor_is_bool(item) && cbor_ctrl_value(item) ? "true" : "false"); +} + +void print_json(cbor_item_t *item) { + if (cbor_isa_map(item)) { + print_json_map(item); + } else if (cbor_isa_string(item)) { + print_json_string(item); + } else if (cbor_is_bool(item)) { + print_json_bool(item); + } else { + printf("null"); // fallback for unsupported types + } +} + +/** + * @brief Free the CBOR parser context + * + * @param[in] lydctx Data parser context to free. + */ +static void +lyd_cbor_ctx_free(struct lyd_ctx *lydctx) +{ + struct lyd_cbor_ctx *ctx = (struct lyd_cbor_ctx *)lydctx; + + if(lydctx){ + lyd_ctx_free(lydctx); + lycbor_ctx_free(ctx->cborctx); + free(ctx); + } +} + +/** + * @brief Create new CBOR context for parsing. + * + * @param[in] ctx libyang context. + * @param[in] in Input handler. + * @param[out] cbor_ctx_p Pointer to store the created CBOR context. + * @return LY_ERR value. + */ +LY_ERR +lycbor_ctx_new(const struct ly_ctx *ctx, struct ly_in *in, struct lycbor_ctx **cbor_ctx_p) +{ + LY_ERR ret = LY_SUCCESS; + struct lycbor_ctx *cbor_ctx; + + assert(ctx && in && cbor_ctx_p); + + /* Allocate and initialize CBOR context */ + cbor_ctx = calloc(1, sizeof *cbor_ctx); + LY_CHECK_ERR_RET(!cbor_ctx, LOGMEM(ctx), LY_EMEM); + + cbor_ctx->ctx = ctx; + cbor_ctx->in = in; + + *cbor_ctx_p = cbor_ctx; + return ret; +} + +/** + * @brief Free CBOR context. + * + * @param[in] cbor_ctx CBOR context to free. + */ +void +lycbor_ctx_free(struct lycbor_ctx *cbor_ctx) +{ + if (cbor_ctx) { + free(cbor_ctx); + } +} + +/** + * @brief Create new CBOR parser context + */ +LY_ERR +lydcbor_ctx_init(const struct ly_ctx *ctx, struct ly_in *in, + uint32_t parse_opts, uint32_t val_opts, enum lyd_cbor_format format, + struct lyd_cbor_ctx **lydctx_p) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_cbor_ctx *lydctx = NULL; + + assert(lydctx_p); + + /* Initialize context with calloc to ensure all fields are zero */ + lydctx = calloc(1, sizeof *lydctx); + LY_CHECK_ERR_RET(!lydctx, LOGMEM(ctx), LY_EMEM); + lydctx->parse_opts = parse_opts; + lydctx->val_opts = val_opts; + lydctx->free = lyd_cbor_ctx_free; + lydctx->format = format; + + lydctx->cborctx = NULL; /* Will be set below */ + + /* Create low-level CBOR context */ + LY_CHECK_GOTO(ret = lycbor_ctx_new(ctx, in, &lydctx->cborctx), cleanup); + + *lydctx_p = lydctx; + return ret; + +cleanup: + if (lydctx) { + lyd_cbor_ctx_free((struct lyd_ctx *)lydctx); + } + return ret; +} + + +/** + * @brief Convert a CBOR item to a string representation. + * + * This function handles the low-level CBOR to string conversion, + * similar to how JSON parser converts JSON values to strings. + * + * @param[in] item CBOR item to convert. + * @param[out] str_val String value (allocated, caller must free). + * @param[out] str_len String length. + * @return LY_ERR value. + */ +static LY_ERR +lydcbor_item_to_string(const cbor_item_t *item, char **str_val, size_t *str_len) +{ + LY_ERR ret = LY_SUCCESS; + + assert(item && str_val && str_len); + *str_val = NULL; + *str_len = 0; + + switch (cbor_typeof(item)) + { + case CBOR_TYPE_UINT: + { + uint64_t val = cbor_get_int(item); + int len = snprintf(NULL, 0, "%" PRIu64, val); + if (len < 0) { + return LY_ESYS; + } + *str_val = malloc(len + 1); + LY_CHECK_ERR_RET(!*str_val, LOGMEM(NULL), LY_EMEM); + sprintf(*str_val, "%" PRIu64, val); + *str_len = len; + break; + } + case CBOR_TYPE_NEGINT: + { + int64_t val = -1 - (int64_t)cbor_get_int(item); + int len = snprintf(NULL, 0, "%" PRId64, val); + if (len < 0) { + return LY_ESYS; + } + *str_val = malloc(len + 1); + LY_CHECK_ERR_RET(!*str_val, LOGMEM(NULL), LY_EMEM); + sprintf(*str_val, "%" PRId64, val); + *str_len = len; + break; + } + case CBOR_TYPE_BYTESTRING: + *str_len = cbor_bytestring_length(item); + *str_val = malloc(*str_len + 1); + LY_CHECK_ERR_RET(!*str_val, LOGMEM(NULL), LY_EMEM); + memcpy(*str_val, cbor_bytestring_handle(item), *str_len); + (*str_val)[*str_len] = '\0'; + break; + case CBOR_TYPE_STRING: + *str_len = cbor_string_length(item); + *str_val = malloc(*str_len + 1); + LY_CHECK_ERR_RET(!*str_val, LOGMEM(NULL), LY_EMEM); + memcpy(*str_val, cbor_string_handle(item), *str_len); + (*str_val)[*str_len] = '\0'; + break; + case CBOR_TYPE_FLOAT_CTRL: + if (cbor_float_ctrl_is_ctrl(item)) + { + switch (cbor_ctrl_value(item)) + { + case CBOR_CTRL_TRUE: + *str_val = strdup("true"); + *str_len = 4; + break; + case CBOR_CTRL_FALSE: + *str_val = strdup("false"); + *str_len = 5; + break; + case CBOR_CTRL_NULL: + *str_val = strdup(""); + *str_len = 0; + break; + default: + LOGVAL(NULL, LYVE_SYNTAX, "Unsupported CBOR control value"); + ret = LY_EVALID; + break; + } + LY_CHECK_ERR_RET(!*str_val, LOGMEM(NULL), LY_EMEM); + } + else + { + /* Float value */ + double val = cbor_float_get_float(item); + int len = snprintf(NULL, 0, "%g", val); + if (len < 0) { + return LY_ESYS; + } + *str_val = malloc(len + 1); + LY_CHECK_ERR_RET(!*str_val, LOGMEM(NULL), LY_EMEM); + sprintf(*str_val, "%g", val); + *str_len = len; + } + break; + default: + LOGVAL(NULL, LYVE_SYNTAX, "Unsupported CBOR data type %d", cbor_typeof(item)); + ret = LY_EVALID; + break; + } + + return ret; +} + +/** + * @brief Get string key from CBOR map item. + * + * For named identifier format, keys should be strings. + * For SID format, keys would be integers (future implementation). + */ +static LY_ERR +lydcbor_get_key_string(struct lyd_cbor_ctx *lydctx, const cbor_item_t *key_item, + char **key_str, size_t *key_len) +{ + LY_ERR ret = LY_SUCCESS; + + assert(lydctx && key_item && key_str && key_len); + + switch (lydctx->format) + { + case LYD_CBOR_NAMED: + /* Keys must be strings for named format */ + if (!cbor_isa_string(key_item)) + { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "CBOR map key must be string for named identifier format"); + return LY_EVALID; + } + ret = lydcbor_item_to_string(key_item, key_str, key_len); + break; + case LYD_CBOR_SID: + /* Future: Handle SID integer keys */ + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "CBOR SID format not yet implemented"); + ret = LY_ENOT; + break; + default: + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Unknown CBOR format"); + ret = LY_EINVAL; + break; + } + + return ret; +} + +LY_ERR +lydcbor_parse_value(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, + const void *cbor_item, struct lyd_node **node) +{ + LY_ERR ret = LY_SUCCESS; + const cbor_item_t *item = (const cbor_item_t *)cbor_item; + char *str_val = NULL; + size_t str_len = 0; + + assert(lydctx && snode && item && node); + + /* Convert CBOR value to string */ + LY_CHECK_GOTO(ret = lydcbor_item_to_string(item, &str_val, &str_len), cleanup); + + /* Create data node based on schema node type */ + switch (snode->nodetype) + { + case LYS_LEAF: + case LYS_LEAFLIST: + ret = lyd_create_term(snode, str_val, str_len, 0, 0, NULL, LY_VALUE_JSON, NULL, LYD_HINT_DATA, NULL, node); + break; + case LYS_ANYDATA: + case LYS_ANYXML: + /* For anydata/anyxml, we store the CBOR directly */ + ret = lyd_create_any(snode, cbor_item, LYD_ANYDATA_CBOR, 0, node); + break; + default: + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Invalid schema node type for CBOR value"); + ret = LY_EVALID; + break; + } + +cleanup: + free(str_val); + return ret; +} + + +/** + * @brief Get module prefix from a qualified name. + * + * @param[in] qname Qualified name (prefix:name or just name). + * @param[in] qname_len Length of the qualified name. + * @param[out] prefix Extracted prefix (points into qname, not allocated). + * @param[out] prefix_len Length of the prefix. + * @param[out] name Local name (points into qname, not allocated). + * @param[out] name_len Length of the local name. + * @return LY_SUCCESS on success. + */ +static LY_ERR +lydcbor_parse_qname(const char *qname, size_t qname_len, const char **prefix, size_t *prefix_len, + const char **name, size_t *name_len) +{ + const char *colon; + + assert(qname && name && name_len); + + *name = qname; + *name_len = qname_len; + + if (prefix) { + *prefix = NULL; + } + if (prefix_len) { + *prefix_len = 0; + } + + /* Look for module prefix separator */ + colon = ly_strnchr(qname, ':', qname_len); + if (colon) { + /* We have a module prefix */ + if (prefix) { + *prefix = qname; + *prefix_len = colon - qname; + } + + /* Local name starts after the colon */ + *name = colon + 1; + *name_len = qname_len - (colon - qname) - 1; + + /* Validate we have both prefix and name */ + if ((colon == qname) || (*name_len == 0)) { + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Get schema node from CBOR node name, following lydjson_get_snode logic. + * + * @param[in] lydctx CBOR parser context. + * @param[in] name Node name. + * @param[in] name_len Length of node name. + * @param[in] parent Data parent node. + * @param[out] snode Schema node found. + * @param[out] ext Extension instance if found. + * @return LY_ERR value. + */ +static LY_ERR +lydcbor_get_snode(struct lyd_cbor_ctx *lydctx, const char *name, size_t name_len, + struct lyd_node *parent, const struct lysc_node **snode, + const struct lysc_ext_instance **ext) +{ + LY_ERR ret = LY_SUCCESS, r; + const char *prefix = NULL, *local_name = NULL; + size_t prefix_len = 0, local_name_len = 0; + const struct lys_module *mod = NULL; + const struct lysc_node *sparent = NULL; + uint32_t getnext_opts; + + assert(lydctx && name && snode); + *snode = NULL; + if (ext) { + *ext = NULL; + } + + /* Parse qualified name */ + LY_CHECK_RET(lydcbor_parse_qname(name, name_len, &prefix, &prefix_len, &local_name, &local_name_len)); + + /* Get parent schema node */ + if (parent) { + sparent = parent->schema; + if (!sparent) { + /* Opaque parent */ + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Cannot parse \"%.*s\" node with opaque parent.", + (int)local_name_len, local_name); + ret = LY_EVALID; + goto cleanup; + } + } else { + sparent = NULL; + } + + /* Resolve module if prefix is present */ + if (prefix) { + mod = ly_ctx_get_module_implemented2(lydctx->cborctx->ctx, prefix, prefix_len); + if (!mod) { + if (lydctx->parse_opts & LYD_PARSE_STRICT) { + LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, "Unknown module \"%.*s\".", (int)prefix_len, prefix); + ret = LY_EVALID; + goto cleanup; + } + if (!(lydctx->parse_opts & LYD_PARSE_OPAQ)) { + LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, "Unknown module \"%.*s\".", (int)prefix_len, prefix); + ret = LY_EVALID; + goto cleanup; + } + } + } else if (!sparent) { + /* Top-level node without prefix - need to find module */ + /* Try to find the node in all implemented modules */ + const struct lys_module *iter_mod; + uint32_t idx = 0; + ly_bool found = 0; + + while ((iter_mod = ly_ctx_get_module_iter(lydctx->cborctx->ctx, &idx))) { + if (!iter_mod->implemented) { + continue; + } + + /* Check if node exists in this module */ + if (lys_find_child(NULL, iter_mod, local_name, local_name_len, 0, 0)) { + if (found) { + /* Ambiguous name */ + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Ambiguous node name \"%.*s\", use module prefix.", + (int)local_name_len, local_name); + ret = LY_EVALID; + goto cleanup; + } + mod = iter_mod; + found = 1; + } + } + + if (!found && !(lydctx->parse_opts & LYD_PARSE_OPAQ)) { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Unknown node \"%.*s\".", (int)local_name_len, local_name); + ret = LY_EVALID; + goto cleanup; + } + } + + /* Set getnext options */ + getnext_opts = lydctx->int_opts & LYD_INTOPT_REPLY ? LYS_GETNEXT_OUTPUT : 0; + if (parent && (parent->schema->nodetype & (LYS_RPC | LYS_ACTION))) { + if (lydctx->int_opts & LYD_INTOPT_RPC) { + getnext_opts = 0; + } else if (lydctx->int_opts & LYD_INTOPT_REPLY) { + getnext_opts = LYS_GETNEXT_OUTPUT; + } + } + + /* Find schema node */ + if (sparent) { + /* Search in parent's children */ + *snode = lys_find_child(sparent, sparent->module, local_name, local_name_len, 0, getnext_opts); + + /* Try to find extension data if regular node not found */ + if (!*snode && ext) { + r = ly_nested_ext_schema(parent, sparent, prefix, prefix_len, LY_VALUE_JSON, NULL, + local_name, local_name_len, snode, ext); + if (r != LY_ENOT) { + if (r) { + ret = r; + goto cleanup; + } + } + } + } else { + /* Top-level node */ + if (mod) { + /* Search in specific module */ + *snode = lys_find_child(NULL, mod, local_name, local_name_len, 0, getnext_opts); + } + /* Extension data for top-level not typically handled */ + } + + /* Handle missing schema node */ + if (!*snode) { + if (lydctx->parse_opts & LYD_PARSE_STRICT) { + if (prefix) { + LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, "Unknown element \"%.*s\" in module \"%.*s\".", + (int)local_name_len, local_name, (int)prefix_len, prefix); + } else { + LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, "Unknown element \"%.*s\".", + (int)local_name_len, local_name); + } + ret = LY_EVALID; + goto cleanup; + } else if (!(lydctx->parse_opts & LYD_PARSE_OPAQ)) { + /* Log error but continue if not in strict mode and opaque allowed */ + if (prefix) { + LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, "Unknown element \"%.*s\" in module \"%.*s\".", + (int)local_name_len, local_name, (int)prefix_len, prefix); + } else { + LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, "Unknown element \"%.*s\".", + (int)local_name_len, local_name); + } + ret = LY_EVALID; + goto cleanup; + } + /* If opaque parsing allowed, *snode remains NULL and caller handles it */ + } + +cleanup: + return ret; +} + +static LY_ERR +lydcbor_parse_node_value(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, + struct lyd_node **node, const cbor_item_t *cbor_value) +{ + LY_ERR ret = LY_SUCCESS; + + assert(lydctx && snode && node && cbor_value); + *node = NULL; + + switch (snode->nodetype) { + case LYS_CONTAINER: + ret = lyd_create_inner(snode, node); + break; + case LYS_LIST: + ret = lyd_create_inner(snode, node); + break; + case LYS_LEAF: + case LYS_LEAFLIST: { + char *str_val = NULL; + size_t str_len = 0; + + ret = lydcbor_item_to_string(cbor_value, &str_val, &str_len); + if (ret == LY_SUCCESS) { + ret = lyd_create_term(snode, str_val, str_len, 0, 0, NULL, LY_VALUE_JSON, NULL, LYD_HINT_DATA, NULL, node); + } + free(str_val); + break; + } + case LYS_ANYDATA: + case LYS_ANYXML: + ret = lyd_create_any(snode, cbor_value, LYD_ANYDATA_CBOR, 0, node); + break; + default: + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Invalid schema node type %d", snode->nodetype); + ret = LY_EVALID; + break; + } + + return ret; +} + +static LY_ERR +lydcbor_parse_leaflist_array(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, + const cbor_item_t *array_item, struct lyd_node **first_p, struct ly_set *parsed) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_node *node = NULL; + size_t array_size; + cbor_item_t **array_handle; + + assert(lydctx && snode && array_item && parsed); + + if (!cbor_isa_array(array_item)) { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Expected CBOR array for leaf-list"); + return LY_EVALID; + } + + if (snode->nodetype != LYS_LEAFLIST) { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Schema node must be leaf-list"); + return LY_EVALID; + } + + array_size = cbor_array_size(array_item); + array_handle = cbor_array_handle(array_item); + + if (!array_handle && array_size > 0) { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Invalid CBOR array structure"); + return LY_EVALID; + } + + for (size_t i = 0; i < array_size; ++i) { + const cbor_item_t *item = array_handle[i]; + + if (!item) { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Null array element at index %zu", i); + ret = LY_EVALID; + goto cleanup; + } + + LY_CHECK_GOTO(ret = lydcbor_parse_node_value(lydctx, snode, &node, item), cleanup); + + if (!node) { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Failed to create node for array element %zu", i); + ret = LY_EVALID; + goto cleanup; + } + + /* Insert the node */ + ret = lyd_insert_sibling(*first_p, node, first_p); + LY_CHECK_GOTO(ret, cleanup); + + /* Add to parsed set */ + LY_CHECK_GOTO(ret = ly_set_add(parsed, node, 1, NULL), cleanup); + node = NULL; /* Reset pointer after successful insertion */ + } + +cleanup: + if (ret && node) { + lyd_free_tree(node); + } + return ret; +} + +static LY_ERR +lydcbor_parse_subtree(struct lyd_cbor_ctx *lydctx, struct lyd_node *parent, + struct lyd_node **first_p, struct ly_set *parsed, const cbor_item_t *cbor_obj) +{ + LY_ERR ret = LY_SUCCESS; + + printf("Entering lydcbor_parse_subtree\n"); + printf("CBOR object:\n"); + print_json(cbor_obj); + printf("\n"); + + const struct lysc_node *snode = NULL; + char *key_str = NULL; + size_t key_len = 0; +\ + assert(lydctx && first_p && parsed && cbor_obj); + + /* assuming that the top level structure is always a map + to be modified to include anything else that it can support */ + + if (!cbor_isa_map(cbor_obj)) { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Expected CBOR map"); + return LY_EVALID; + } + + size_t map_size = cbor_map_size(cbor_obj); + struct cbor_pair *pairs = cbor_map_handle(cbor_obj); + + if (!pairs && map_size > 0) { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Invalid CBOR map structure"); + return LY_EVALID; + } + + for (size_t i = 0; i < map_size; ++i) { + const cbor_item_t *key_item = pairs[i].key; + const cbor_item_t *value_item = pairs[i].value; + + if (!key_item || !value_item) { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Null key or value at map index %zu", i); + ret = LY_EVALID; + goto cleanup; + } + + /* Get key string */ + LY_CHECK_GOTO(ret = lydcbor_get_key_string(lydctx, key_item, &key_str, &key_len), cleanup); + + /* Find schema node */ + LY_CHECK_GOTO(ret = lydcbor_get_snode(lydctx, key_str, key_len, + parent, &snode, NULL), cleanup); + + /* Handle different node types */ + if (snode->nodetype & (LYS_LEAF | LYS_LEAFLIST)) { + if (snode->nodetype == LYS_LEAFLIST && cbor_isa_array(value_item)) { + ret = lydcbor_parse_leaflist_array(lydctx, snode, value_item, first_p, parsed); + } else { + ret = lydcbor_parse_terminal(lydctx, snode, value_item, first_p, parsed); + } + } else if (snode->nodetype == LYS_CONTAINER) { + ret = lydcbor_parse_container(lydctx, snode, value_item, first_p, parsed); + } else if (snode->nodetype == LYS_LIST) { + ret = lydcbor_parse_list(lydctx, snode, value_item, first_p, parsed); + } else if (snode->nodetype & (LYS_ANYDATA | LYS_ANYXML)) { + ret = lydcbor_parse_any(lydctx, snode, value_item, first_p, parsed); + } else { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Invalid schema node type %d for \"%s\"", + snode->nodetype, snode->name); + ret = LY_EVALID; + } + + LY_CHECK_GOTO(ret, cleanup); + + free(key_str); + key_str = NULL; + } + +cleanup: + free(key_str); + return ret; +} + +// Add these new functions: + +static LY_ERR +lydcbor_parse_terminal(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, + const cbor_item_t *cbor_value, struct lyd_node **first_p, struct ly_set *parsed) +{ + LY_ERR ret; + struct lyd_node *node = NULL; + + ret = lydcbor_parse_node_value(lydctx, snode, &node, cbor_value); + LY_CHECK_RET(ret); + + if (!node) { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Failed to create terminal node for \"%s\"", snode->name); + return LY_EVALID; + } + + /* Insert into tree */ + ret = lyd_insert_sibling(*first_p, node, first_p); + if (ret) { + lyd_free_tree(node); + return ret; + } + + /* Add to parsed set */ + return ly_set_add(parsed, node, 1, NULL); +} + +static LY_ERR +lydcbor_parse_container(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, + const cbor_item_t *cbor_value, struct lyd_node **first_p, struct ly_set *parsed) +{ + LY_ERR ret; + struct lyd_node *node = NULL; + + ret = lyd_create_inner(snode, &node); + LY_CHECK_RET(ret); + + if (!node) { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Failed to create container node for \"%s\"", snode->name); + return LY_EVALID; + } + + /* Insert into tree first */ + ret = lyd_insert_sibling(*first_p, node, first_p); + if (ret) { + lyd_free_tree(node); + return ret; + } + + /* Add to parsed set */ + ret = ly_set_add(parsed, node, 1, NULL); + LY_CHECK_RET(ret); + + /* Parse container children */ + if (cbor_isa_map(cbor_value) && cbor_map_size(cbor_value) > 0) { + struct lyd_node *child_first = NULL; + ret = lydcbor_parse_subtree(lydctx, node, &child_first, parsed, cbor_value); + if (ret) { + return ret; + } + + /* Link children to container */ + if (child_first) { + lyd_insert_child(node, child_first); + } + } + + return LY_SUCCESS; +} + +// Replace lydcbor_parse_list_array with this improved version: +static LY_ERR +lydcbor_parse_list_array(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, + const cbor_item_t *array_item, struct lyd_node **first_p, struct ly_set *parsed) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_node *node = NULL; + size_t array_size; + cbor_item_t **array_handle; + + assert(lydctx && snode && array_item && parsed); + + if (!cbor_isa_array(array_item)) { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Expected CBOR array for list"); + return LY_EVALID; + } + + if (snode->nodetype != LYS_LIST) { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Schema node must be list"); + return LY_EVALID; + } + + array_size = cbor_array_size(array_item); + array_handle = cbor_array_handle(array_item); + + if (!array_handle && array_size > 0) { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Invalid CBOR array structure"); + return LY_EVALID; + } + + for (size_t i = 0; i < array_size; ++i) { + const cbor_item_t *item = array_handle[i]; + + if (!item) { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Null array element at index %zu", i); + ret = LY_EVALID; + goto cleanup; + } + + if (!cbor_isa_map(item)) { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "List entry must be a CBOR map"); + ret = LY_EVALID; + goto cleanup; + } + + ret = lyd_create_inner(snode, &node); + LY_CHECK_GOTO(ret, cleanup); + + if (!node) { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Failed to create list node"); + ret = LY_EVALID; + goto cleanup; + } + + /* Insert the list node */ + ret = lyd_insert_sibling(*first_p, node, first_p); + LY_CHECK_GOTO(ret, cleanup); + + /* Add to parsed set */ + LY_CHECK_GOTO(ret = ly_set_add(parsed, node, 1, NULL), cleanup); + + /* Parse list entry content */ + struct lyd_node *child_first = NULL; + ret = lydcbor_parse_subtree(lydctx, node, &child_first, parsed, item); + LY_CHECK_GOTO(ret, cleanup); + + /* Link children to list entry */ + if (child_first) { + lyd_insert_child(node, child_first); + } + + node = NULL; /* Reset pointer after successful processing */ + } + +cleanup: + if (ret && node) { + lyd_free_tree(node); + } + return ret; +} + +static LY_ERR +lydcbor_parse_list(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, + const cbor_item_t *cbor_value, struct lyd_node **first_p, struct ly_set *parsed) +{ + LY_ERR ret = LY_SUCCESS; + + if (cbor_isa_array(cbor_value)) { + /* Array of list entries */ + ret = lydcbor_parse_list_array(lydctx, snode, cbor_value, first_p, parsed); + } else if (cbor_isa_map(cbor_value)) { + /* Single list entry */ + struct lyd_node *node = NULL; + + ret = lyd_create_inner(snode, &node); + LY_CHECK_RET(ret); + + /* Insert into tree */ + ret = lyd_insert_sibling(*first_p, node, first_p); + if (ret) { + lyd_free_tree(node); + return ret; + } + + /* Add to parsed set */ + ret = ly_set_add(parsed, node, 1, NULL); + LY_CHECK_RET(ret); + + /* Parse list entry content */ + struct lyd_node *child_first = NULL; + ret = lydcbor_parse_subtree(lydctx, node, &child_first, parsed, cbor_value); + if (ret) { + return ret; + } + + /* Link children to list entry */ + if (child_first) { + lyd_insert_child(node, child_first); + } + } else { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "List \"%s\" value must be a CBOR map or array", snode->name); + ret = LY_EVALID; + } + + return ret; +} + +static LY_ERR +lydcbor_parse_any(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, + const cbor_item_t *cbor_value, struct lyd_node **first_p, struct ly_set *parsed) +{ + LY_ERR ret; + struct lyd_node *node = NULL; + + ret = lyd_create_any(snode, cbor_value, LYD_ANYDATA_CBOR, 0, &node); + LY_CHECK_RET(ret); + + if (!node) { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Failed to create any node for \"%s\"", snode->name); + return LY_EVALID; + } + + /* Insert into tree */ + ret = lyd_insert_sibling(*first_p, node, first_p); + if (ret) { + lyd_free_tree(node); + return ret; + } + + /* Add to parsed set */ + return ly_set_add(parsed, node, 1, NULL); +} + +LY_ERR +lydcbor_detect_format(struct ly_in *in, enum lyd_cbor_format *format) +{ + /* Simple heuristic: try to parse as CBOR and examine structure */ + /* For now, default to named format */ + (void)in; + *format = LYD_CBOR_NAMED; + return LY_SUCCESS; +} + +LY_ERR +lydcbor_parse_metadata(struct lyd_cbor_ctx *lydctx, const void *cbor_item, struct lyd_node *node) +{ + /* Future implementation for CBOR metadata parsing */ + (void)lydctx; + (void)cbor_item; + (void)node; + return LY_SUCCESS; +} + +LY_ERR +lyd_parse_cbor(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, struct lyd_node *parent, + 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) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_cbor_ctx *lydctx = NULL; + cbor_item_t *cbor_data = NULL; + struct cbor_load_result result = {0}; + enum lyd_cbor_format format; + + /* Detect CBOR format - Named or SID */ + LY_CHECK_GOTO(ret = lydcbor_detect_format(in, &format), cleanup); + + /* Initialize context */ + LY_CHECK_GOTO(ret = lydcbor_ctx_init(ctx, in, parse_opts, val_opts, format, &lydctx), cleanup); + + lydctx->int_opts = int_opts; + lydctx->ext = ext; + + /* + * Loads CBOR data from the current input buffer. + * + * Parameters: + * in->current - Pointer to the current position in the input buffer. + * in->length - Length of the data to be loaded. + * &result - Pointer to a variable where the result status will be stored. + * + * Returns: + * cbor_data - Pointer to the loaded CBOR data structure, or NULL on failure. + */ + /* need to convert in->current from const char* to cbor_data type */ + cbor_data = cbor_load(in->current, in->length, &result); + lydctx->cborctx->cbor_data = cbor_data; + + if (!cbor_data) { + LOGVAL(ctx, LYVE_SYNTAX, "Failed to parse CBOR data: no data returned from cbor_load()."); + ret = LY_EVALID; + goto cleanup; + } + if (result.error.code != CBOR_ERR_NONE) { + LOGVAL(ctx, LYVE_SYNTAX, "Failed to parse CBOR data: parsing error (code %d).", result.error.code); + ret = LY_EVALID; + goto cleanup; + } + + /* Probably need to check if the obtained data is a operational node and + then write functions to parse them accordingly. If not then continue below */ + + /* Parse the CBOR structure */ + ret = lydcbor_parse_subtree(lydctx, parent, first_p, parsed, cbor_data); + +cleanup: + if (cbor_data) + { + cbor_decref(&cbor_data); + } + + if (ret) + { + if (lydctx) { + lyd_cbor_ctx_free((struct lyd_ctx *)lydctx); + lydctx = NULL; + } + } + + *lydctx_p = (struct lyd_ctx *)lydctx; + return ret; +} + +#endif /* ENABLE_CBOR_SUPPORT */ \ No newline at end of file diff --git a/src/parser_cbor.h b/src/parser_cbor.h new file mode 100644 index 000000000..5b6b0dc94 --- /dev/null +++ b/src/parser_cbor.h @@ -0,0 +1,279 @@ +/** + * @file parser_cbor.h + * @author + * @brief CBOR data parser for libyang + * + * Copyright (c) 2020 - 2023 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#ifndef LY_PARSER_CBOR_H_ +#define LY_PARSER_CBOR_H_ + +#ifdef ENABLE_CBOR_SUPPORT + +#include +#include +#include + +#include "log.h" +#include "tree_data.h" +#include "parser_internal.h" +#include "set.h" + +struct ly_ctx; +struct ly_in; +struct ly_out; +struct lyd_ctx; +struct lyd_node; +struct ly_set; +struct lysc_ext_instance; +struct lysc_node; + +/** + * @brief CBOR format variants for different encoding schemes + */ +enum lyd_cbor_format +{ + LYD_CBOR_NAMED, /**< CBOR with named identifiers (JSON-like) */ + LYD_CBOR_SID /**< CBOR with Schema Item identifiers (future implementation) */ +}; + +struct lycbor_ctx { + const struct ly_ctx *ctx; /**< libyang context */ + struct ly_in *in; /**< input structure */ + cbor_item_t *cbor_data; /**< parsed CBOR data */ + enum lyd_cbor_format format; /**< CBOR format variant */ + uint32_t parse_opts; /**< parser options */ + uint32_t val_opts; /**< validation options */ +}; + +/** + * @brief Internal context for CBOR YANG data parser. + * + * This structure extends the basic lyd_ctx pattern used throughout libyang + * and provides CBOR-specific parsing state and configuration. + */ +struct lyd_cbor_ctx +{ + const struct lysc_ext_instance *ext; /**< extension instance possibly changing document root context, NULL if none */ + uint32_t parse_opts; /**< various @ref dataparseroptions. */ + uint32_t val_opts; /**< various @ref datavalidationoptions. */ + uint32_t int_opts; /**< internal parser options */ + uint32_t path_len; /**< used bytes in the path buffer */ + char path[LYD_PARSER_BUFSIZE]; /**< buffer for the generated path */ + struct ly_set node_when; /**< set of nodes with "when" conditions */ + struct ly_set node_types; /**< set of nodes with unresolved types */ + struct ly_set meta_types; /**< set of metadata with unresolved types */ + struct ly_set ext_node; /**< set of nodes with extension instances to validate */ + struct ly_set ext_val; /**< set of nested extension data to validate */ + struct lyd_node *op_node; /**< if an operation is being parsed, its node */ + const struct lys_module *val_getnext_ht_mod; + struct ly_ht *val_getnext_ht; + + /* callbacks */ + lyd_ctx_free_clb free; /**< destructor */ + + struct lycbor_ctx *cborctx; /**< CBOR context for low-level operations */ + + /* CBOR-specific members */ + enum lyd_cbor_format format; /**< CBOR format being parsed */ +}; + +/** + * @brief Create new CBOR context for parsing. + * + * @param[in] ctx libyang context. + * @param[in] in Input handler. + * @param[out] cbor_ctx_p Pointer to store the created CBOR context. + * @return LY_ERR value. + */ +LY_ERR +lycbor_ctx_new(const struct ly_ctx *ctx, struct ly_in *in, struct lycbor_ctx **cbor_ctx_p); + +/** + * @brief Free CBOR context. + * + * @param[in] cbor_ctx CBOR context to free. + */ +void +lycbor_ctx_free(struct lycbor_ctx *cbor_ctx); + +/** + * @brief Parse CBOR data into libyang data tree. + * + * This function mirrors the signature and behavior of lyd_parse_json() but handles + * CBOR input instead. It supports both named identifier and SID formats. + * + * @param[in] ctx libyang context. + * @param[in] ext Optional extension instance to parse data following the schema tree specified in the extension instance + * @param[in] parent Parent to connect the parsed nodes to, if any. + * @param[in,out] first_p Pointer to the first top-level parsed node, used only if @p parent is NULL. + * @param[in] in Input structure to read from. + * @param[in] parse_opts Options for parser, see @ref dataparseroptions. + * @param[in] val_opts Options for the validation phase, see @ref datavalidationoptions. + * @param[in] int_opts Internal data parser options. + * @param[out] parsed Set to add all the parsed siblings into. + * @param[out] subtree_sibling Set if ::LYD_PARSE_SUBTREE is used and another subtree is following in @p in. + * @param[out] lydctx_p Data parser context to finish validation. + * @return LY_ERR value. + */ +LY_ERR lyd_parse_cbor(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, struct lyd_node *parent, + 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); + +/** + * @brief Parse CBOR data from memory into libyang data tree. + * + * Convenience function for parsing CBOR data directly from memory buffer. + * + * @param[in] ctx libyang context. + * @param[in] data CBOR data to parse. + * @param[in] data_len Length of @p data. + * @param[in] format CBOR format variant (named or SID). + * @param[in] parse_opts Parse options, see @ref dataparseroptions. + * @param[in] val_opts Validation options, see @ref datavalidationoptions. + * @param[out] tree Parsed data tree. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_parse_cbor_data(const struct ly_ctx *ctx, const char *data, size_t data_len, + enum lyd_cbor_format format, uint32_t parse_opts, uint32_t val_opts, struct lyd_node **tree); + +/** + * @brief Print libyang data tree as CBOR. + * + * @param[in] root Root node of the data tree to print. + * @param[in] format CBOR format variant to use for output. + * @param[in] out Output structure to write to. + * @param[in] options Print options. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_print_cbor_data(const struct lyd_node *root, enum lyd_cbor_format format, + struct ly_out *out, uint32_t options); + +/* Internal functions (used by parser_data.c and other libyang components) */ + +/** + * @brief Detect CBOR format variant from input data. + * + * @param[in] in Input structure to analyze. + * @param[out] format Detected format. + * @return LY_ERR value. + */ +LY_ERR lydcbor_detect_format(struct ly_in *in, enum lyd_cbor_format *format); + +/** + * @brief Parse a single CBOR value according to schema node. + * + * @param[in] lydctx CBOR parser context. + * @param[in] snode Schema node for the value. + * @param[in] cbor_item CBOR item to parse. + * @param[out] node Created data node. + * @return LY_ERR value. + */ +LY_ERR lydcbor_parse_value(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, + const void *cbor_item, struct lyd_node **node); + +/** + * @brief Parse CBOR metadata/attributes. + * + * @param[in] lydctx CBOR parser context. + * @param[in] cbor_item CBOR item containing metadata. + * @param[in,out] node Data node to attach metadata to. + * @return LY_ERR value. + */ +LY_ERR lydcbor_parse_metadata(struct lyd_cbor_ctx *lydctx, const void *cbor_item, struct lyd_node *node); + +/** + * @brief Create a new CBOR parser context. + * + * @param[in] ctx libyang context. + * @param[in] ext Extension instance providing context for the top level element, NULL if none. + * @param[in] parse_opts Parse options, see @ref dataparseroptions. + * @param[in] val_opts Validation options, see @ref datavalidationoptions. + * @param[in] format CBOR format variant (named or SID). + * @param[out] lydctx_p Pointer to the created CBOR parser context. + * @return LY_ERR value. + */ +LY_ERR lydcbor_ctx_init(const struct ly_ctx *ctx, struct ly_in *in, + uint32_t parse_opts, uint32_t val_opts, enum lyd_cbor_format format, + struct lyd_cbor_ctx **lydctx_p); + + /* Add these declarations to parser_cbor.h or at the top of parser_cbor.c */ + +static LY_ERR +lydcbor_parse_leaflist_array(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, + const cbor_item_t *array_item, struct lyd_node **first_p, struct ly_set *parsed); + + +static LY_ERR +lydcbor_parse_list_array(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, + const cbor_item_t *array_item, struct lyd_node **first_p, struct ly_set *parsed); + +/** + * @brief Parse a CBOR container recursive + */ +static LY_ERR +lydcbor_parse_subtree(struct lyd_cbor_ctx *lydctx, struct lyd_node *parent, + struct lyd_node **first_p, struct ly_set *parsed, const cbor_item_t *cbor_obj); +static LY_ERR +lydcbor_parse_any(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, + const cbor_item_t *cbor_value, struct lyd_node **first_p, struct ly_set *parsed); + +static LY_ERR +lydcbor_parse_list(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, + const cbor_item_t *cbor_value, struct lyd_node **first_p, struct ly_set *parsed); + + +static LY_ERR +lydcbor_parse_container(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, + const cbor_item_t *cbor_value, struct lyd_node **first_p, struct ly_set *parsed); + +static LY_ERR +lydcbor_parse_terminal(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, + const cbor_item_t *cbor_value, struct lyd_node **first_p, struct ly_set *parsed); +static LY_ERR +lydcbor_parse_node_value(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, + struct lyd_node **node, const cbor_item_t *cbor_value); + + +/** + * @brief Get schema node from CBOR node name, following lydjson_get_snode logic. + * + * @param[in] lydctx CBOR parser context. + * @param[in] name Node name. + * @param[in] name_len Length of node name. + * @param[in] parent Data parent node. + * @param[out] snode Schema node found. + * @param[out] ext Extension instance if found. + * @return LY_ERR value. + */ +static LY_ERR +lydcbor_get_snode(struct lyd_cbor_ctx *lydctx, const char *name, size_t name_len, + struct lyd_node *parent, const struct lysc_node **snode, + const struct lysc_ext_instance **ext); + + +/** + * @brief Get module prefix from a qualified name. + * + * @param[in] qname Qualified name (prefix:name or just name). + * @param[in] qname_len Length of the qualified name. + * @param[out] prefix Extracted prefix (points into qname, not allocated). + * @param[out] prefix_len Length of the prefix. + * @param[out] name Local name (points into qname, not allocated). + * @param[out] name_len Length of the local name. + * @return LY_SUCCESS on success. + */ +static LY_ERR +lydcbor_parse_qname(const char *qname, size_t qname_len, const char **prefix, size_t *prefix_len, + const char **name, size_t *name_len); + +#endif /* ENABLE_CBOR_SUPPORT */ + +#endif /* LY_PARSER_CBOR_H_ */ \ No newline at end of file diff --git a/src/parser_data.h b/src/parser_data.h index 4af6102f7..a4b420faf 100644 --- a/src/parser_data.h +++ b/src/parser_data.h @@ -265,6 +265,23 @@ LIBYANG_API_DECL LY_ERR lyd_parse_data_mem(const struct ly_ctx *ctx, const char uint32_t validate_options, struct lyd_node **tree); /** + * @brief Parse data from a memory buffer with a specified length. + * + * This function parses the provided data buffer of a given length and returns the resulting data tree. + * + * @param[in] ctx libyang context for parsing. + * @param[in] data Pointer to the memory buffer containing the data to parse. + * @param[in] data_len Length of the memory buffer. + * @param[in] format Data format (e.g., XML, JSON, LYD_LYB). + * @param[in] options Parsing options, see @ref dataparseroptions. + * @param[in] ctx_node Optional context node for parsing (can be NULL). + * @param[out] tree Pointer to the resulting data tree (set on success). + * @return LY_ERR value indicating success or error reason. + */ +LIBYANG_API_DECL LY_ERR lyd_parse_data_mem_len(const struct ly_ctx *ctx, const char *data, size_t data_len, LYD_FORMAT format, + uint32_t parse_options, uint32_t validate_options, struct lyd_node **tree); + + /** * @brief Parse (and validate) input data as a YANG data tree. * * Wrapper around ::lyd_parse_data() hiding work with the input handler and some obscure options. diff --git a/src/parser_json.c b/src/parser_json.c index bda43a50b..af0ec64b6 100644 --- a/src/parser_json.c +++ b/src/parser_json.c @@ -1067,7 +1067,7 @@ lydjson_parse_opaq(struct lyd_json_ctx *lydctx, const char *name, size_t name_le /* 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); *status_inner_p = lyjson_ctx_status(lydctx->jsonctx); } while (*status_inner_p == LYJSON_OBJECT_NEXT); } else { diff --git a/src/printer_data.c b/src/printer_data.c index a0eeaea40..bdbf58717 100644 --- a/src/printer_data.c +++ b/src/printer_data.c @@ -38,6 +38,9 @@ lyd_print_(struct ly_out *out, const struct lyd_node *root, LYD_FORMAT format, u case LYD_LYB: ret = lyb_print_data(out, root, options); break; + case LYD_CBOR: + // ret = cbor_print_data(out, root, options); + break; case LYD_UNKNOWN: LOGINT(root ? LYD_CTX(root) : NULL); ret = LY_EINT; diff --git a/src/tree.h b/src/tree.h index 283cea768..9e2b7fa8d 100644 --- a/src/tree.h +++ b/src/tree.h @@ -237,6 +237,7 @@ typedef enum { LY_VALUE_SCHEMA_RESOLVED, /**< resolved YANG schema value, prefixes map to module structures directly */ LY_VALUE_XML, /**< XML data value, prefixes map to XML namespace prefixes */ LY_VALUE_JSON, /**< JSON data value, prefixes map to module names */ + LY_VALUE_CBOR, /**< CBOR data value, prefixes map to module names */ LY_VALUE_LYB, /**< LYB data binary value, prefix mapping is type-specific (but usually like JSON) */ LY_VALUE_STR_NS /**< any data format value, prefixes map to XML namespace prefixes */ } LY_VALUE_FORMAT; diff --git a/src/tree_data.c b/src/tree_data.c index 517b1694c..a397f28ef 100644 --- a/src/tree_data.c +++ b/src/tree_data.c @@ -35,6 +35,7 @@ #include "in_internal.h" #include "log.h" #include "ly_common.h" +#include "parser_cbor.h" #include "parser_data.h" #include "parser_internal.h" #include "path.h" @@ -76,6 +77,9 @@ lyd_parse_get_format(const struct ly_in *in, LYD_FORMAT format) } else if ((len >= LY_LYB_SUFFIX_LEN + 1) && !strncmp(&path[len - LY_LYB_SUFFIX_LEN], LY_LYB_SUFFIX, LY_LYB_SUFFIX_LEN)) { format = LYD_LYB; + } else if ((len >= LY_CBOR_SUFFIX_LEN + 1) && + !strncmp(&path[len - LY_CBOR_SUFFIX_LEN], LY_CBOR_SUFFIX, LY_CBOR_SUFFIX_LEN)) { + format = LYD_CBOR; } /* else still unknown */ } @@ -86,7 +90,7 @@ lyd_parse_get_format(const struct ly_in *in, LYD_FORMAT format) * @brief Parse YANG data into a data tree. * * @param[in] ctx libyang context. - * @param[in] ext Optional extenion instance to parse data following the schema tree specified in the extension instance + * @param[in] ext Optional extension instance to parse data following the schema tree specified in the extension instance * @param[in] parent Parent to connect the parsed nodes to, if any. * @param[in,out] first_p Pointer to the first parsed node. * @param[in] in Input handle to read the input from. @@ -136,6 +140,10 @@ lyd_parse(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, struct r = lyd_parse_lyb(ctx, ext, parent, first_p, in, parse_opts, val_opts, int_opts, &parsed, &subtree_sibling, &lydctx); break; + case LYD_CBOR: + r = lyd_parse_cbor(ctx, ext, parent, first_p, in, parse_opts, val_opts, int_opts, &parsed, + &subtree_sibling, &lydctx); + break; case LYD_UNKNOWN: LOGARG(ctx, format); r = LY_EINVAL; @@ -233,6 +241,22 @@ lyd_parse_data(const struct ly_ctx *ctx, struct lyd_node *parent, struct ly_in * return lyd_parse(ctx, NULL, parent, tree, in, format, parse_options, validate_options, NULL); } +LIBYANG_API_DEF LY_ERR +lyd_parse_data_mem_len(const struct ly_ctx *ctx, const char *data, size_t data_len, LYD_FORMAT format, + uint32_t parse_options, uint32_t validate_options, struct lyd_node **tree) +{ + LY_ERR ret; + struct ly_in *in; + + LY_CHECK_RET(ly_in_new_memory(data, &in)); + in->length = data_len; // Set the length for the input + + ret = lyd_parse_data(ctx, NULL, in, format, parse_options, validate_options, tree); + + ly_in_free(in, 0); + return ret; +} + LIBYANG_API_DEF LY_ERR lyd_parse_data_mem(const struct ly_ctx *ctx, const char *data, LYD_FORMAT format, uint32_t parse_options, uint32_t validate_options, struct lyd_node **tree) @@ -402,6 +426,9 @@ lyd_parse_op_(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, str case LYD_LYB: rc = lyd_parse_lyb(ctx, ext, parent, &first, in, parse_opts, val_opts, int_opts, &parsed, NULL, &lydctx); break; + case LYD_CBOR: + rc = lyd_parse_cbor(ctx, ext, parent, &first, in, parse_opts, val_opts, int_opts, &parsed, NULL, &lydctx); + break; case LYD_UNKNOWN: LOGARG(ctx, format); rc = LY_EINVAL; diff --git a/src/tree_data.h b/src/tree_data.h index a32b17618..7c48291a2 100644 --- a/src/tree_data.h +++ b/src/tree_data.h @@ -540,7 +540,8 @@ typedef enum { LYD_UNKNOWN = 0, /**< unknown data format, invalid value */ LYD_XML, /**< XML instance data format */ LYD_JSON, /**< JSON instance data format */ - LYD_LYB /**< LYB instance data format */ + LYD_LYB, /**< LYB instance data format */ + LYD_CBOR /**< CBOR instance data format */ } LYD_FORMAT; /** @@ -556,6 +557,7 @@ typedef enum { 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. */ LYD_ANYDATA_LYB /**< Value is a memory chunk with the serialized data tree in LYB format. */ } LYD_ANYDATA_VALUETYPE; diff --git a/src/tree_data_internal.h b/src/tree_data_internal.h index 03f82bca3..948d0db96 100644 --- a/src/tree_data_internal.h +++ b/src/tree_data_internal.h @@ -33,6 +33,8 @@ struct lysc_module; #define LY_JSON_SUFFIX_LEN 5 #define LY_LYB_SUFFIX ".lyb" #define LY_LYB_SUFFIX_LEN 4 +#define LY_CBOR_SUFFIX ".cbor" +#define LY_CBOR_SUFFIX_LEN 5 /** * @brief Internal item structure for remembering "used" instances of duplicate node instances. From 2498924d837731f19cf7350e980d2239cc0c8baa Mon Sep 17 00:00:00 2001 From: MeherRushi Date: Wed, 23 Jul 2025 21:11:22 +0530 Subject: [PATCH 5/9] reformat: restructured the code to match the current format - 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 --- CMakeLists.txt | 12 +- src/lcbor.c | 89 +++ src/lcbor.h | 71 +++ src/parser_cbor.c | 1255 ++++++++++++++++++++++------------------- src/parser_cbor.h | 279 --------- src/parser_internal.h | 54 ++ src/tree_data.c | 1 - 7 files changed, 887 insertions(+), 874 deletions(-) create mode 100644 src/lcbor.c create mode 100644 src/lcbor.h delete mode 100644 src/parser_cbor.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1346fdf4b..7baf3d1d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -327,10 +327,10 @@ if(ENABLE_CBOR_SUPPORT) add_definitions(-DENABLE_CBOR_SUPPORT) include_directories(${LIBCBOR_INCLUDE_DIRS}) # Add CBOR parser files to the library sources - list(APPEND libsrc src/parser_cbor.c) - list(APPEND headers src/parser_cbor.h) + list(APPEND libsrc src/parser_cbor.c src/lcbor.c) + list(APPEND headers src/lcbor.h) # Add CBOR files to format sources - list(APPEND format_sources src/parser_cbor.c src/parser_cbor.h) + list(APPEND format_sources src/parser_cbor.c src/lcbor.h src/lcbor.c) else() message(FATAL_ERROR "libcbor not found! Please install libcbor development package or disable CBOR support with -DENABLE_CBOR_SUPPORT=OFF") endif() @@ -344,10 +344,10 @@ if(ENABLE_CBOR_SUPPORT) include_directories(${LIBCBOR_INCLUDE_DIR}) set(LIBCBOR_LIBRARIES ${LIBCBOR_LIBRARY}) # Add CBOR parser files to the library sources - list(APPEND libsrc src/parser_cbor.c) - list(APPEND headers src/parser_cbor.h) + list(APPEND libsrc src/parser_cbor.c src/lcbor.c) + list(APPEND headers src/lcbor.h) # Add CBOR files to format sources - list(APPEND format_sources src/parser_cbor.c src/parser_cbor.h) + list(APPEND format_sources src/parser_cbor.c src/lcbor.h src/lcbor.c) else() message(FATAL_ERROR "libcbor not found! Please install libcbor development package or disable CBOR support with -DENABLE_CBOR_SUPPORT=OFF") endif() diff --git a/src/lcbor.c b/src/lcbor.c new file mode 100644 index 000000000..e3962fa46 --- /dev/null +++ b/src/lcbor.c @@ -0,0 +1,89 @@ +/** + * @file lcbor.h + * @author MeherRushi + * @brief CBOR data parser for libyang (abstraction over libcbor) + * + * Copyright (c) 2020 - 2023 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#ifdef ENABLE_CBOR_SUPPORT + +#include +#include +#include + +#include "in_internal.h" +#include "lcbor.h" +#include "log.h" +#include "ly_common.h" + +/** + * @brief Free CBOR context. + * + * @param[in] cbor_ctx CBOR context to free. + */ +void lycbor_ctx_free(struct lycbor_ctx *cbor_ctx) +{ + if (cbor_ctx) + { + free(cbor_ctx); + } +} + +/** + * @brief Detect CBOR format variant from input data. + * + * @param[in] in Input structure to analyze. + * @param[out] format Detected format. + * @return LY_ERR value. + */ +static LY_ERR +lydcbor_detect_format(struct ly_in *in, enum lyd_cbor_format *format) +{ + /* Simple heuristic: try to parse as CBOR and examine structure */ + /* For now, default to named format */ + (void)in; + *format = LYD_CBOR_NAMED; + return LY_SUCCESS; +} + +/** + * @brief Create new CBOR context for parsing. + * + * @param[in] ctx libyang context. + * @param[in] in Input handler. + * @param[out] cbor_ctx_p Pointer to store the created CBOR context. + * @return LY_ERR value. + */ +LY_ERR +lycbor_ctx_new(const struct ly_ctx *ctx, struct ly_in *in, struct lycbor_ctx **cbor_ctx_p) +{ + /* TODO : Need to restructure error handling here */ + LY_ERR ret = LY_SUCCESS; + struct lycbor_ctx *cbor_ctx; + enum lyd_cbor_format format; + + assert(ctx && in && cbor_ctx_p); + + /* TODO : error handling after the detect_format function call */ + ret = lydcbor_detect_format(in, &format); + + /* Allocate and initialize CBOR context */ + cbor_ctx = calloc(1, sizeof *cbor_ctx); + LY_CHECK_ERR_RET(!cbor_ctx, LOGMEM(ctx), LY_EMEM); + + cbor_ctx->ctx = ctx; + cbor_ctx->in = in; + cbor_ctx->format = format; + + *cbor_ctx_p = cbor_ctx; + return ret; +} + +#endif /* ENABLE_CBOR_SUPPORT */ \ No newline at end of file diff --git a/src/lcbor.h b/src/lcbor.h new file mode 100644 index 000000000..22d2fb486 --- /dev/null +++ b/src/lcbor.h @@ -0,0 +1,71 @@ +/** + * @file lcbor.h + * @author MeherRushi + * @brief CBOR data parser routines for libyang (abstraction over libcbor) + * + * Copyright (c) 2020 - 2023 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#ifndef LY_LCBOR_H_ +#define LY_LCBOR_H_ + +#ifdef ENABLE_CBOR_SUPPORT + +#include +#include +/* using libcbor as the low-level parser */ +#include + + +#include "log.h" +#include "set.h" + +struct ly_ctx; +struct ly_in; + +/** + * @brief CBOR format variants for different encoding schemes + */ +enum lyd_cbor_format +{ + LYD_CBOR_NAMED, /**< CBOR with named identifiers (JSON-like) */ + LYD_CBOR_SID /**< CBOR with Schema Item identifiers (future implementation) */ +}; + +struct lycbor_ctx { + const struct ly_ctx *ctx; /**< libyang context */ + struct ly_in *in; /**< input structure */ + cbor_item_t *cbor_data; /**< parsed CBOR data */ + enum lyd_cbor_format format; /**< CBOR format variant */ + uint32_t parse_opts; /**< parser options */ + uint32_t val_opts; /**< validation options */ +}; + +/** + * @brief Create new CBOR context for parsing. + * + * @param[in] ctx libyang context. + * @param[in] in Input handler. + * @param[out] cbor_ctx_p Pointer to store the created CBOR context. + * @return LY_ERR value. + */ +LY_ERR +lycbor_ctx_new(const struct ly_ctx *ctx, struct ly_in *in, struct lycbor_ctx **cbor_ctx_p); + +/** + * @brief Free CBOR context. + * + * @param[in] cbor_ctx CBOR context to free. + */ +void +lycbor_ctx_free(struct lycbor_ctx *cbor_ctx); + +#endif /* ENABLE_CBOR_SUPPORT */ + +#endif /* LY_LCBOR_H_ */ \ No newline at end of file diff --git a/src/parser_cbor.c b/src/parser_cbor.c index 38104f31e..bcc12d2aa 100644 --- a/src/parser_cbor.c +++ b/src/parser_cbor.c @@ -1,6 +1,6 @@ /** * @file parser_cbor.c - * @author + * @author Meher Rushi * @brief CBOR data parser for libyang * * Copyright (c) 2020 - 2023 CESNET, z.s.p.o. @@ -16,18 +16,16 @@ #ifdef ENABLE_CBOR_SUPPORT -#include "parser_cbor.h" - #include #include #include #include -#include #include "compat.h" #include "context.h" #include "dict.h" #include "in_internal.h" +#include "lcbor.h" #include "log.h" #include "ly_common.h" #include "parser_data.h" @@ -40,51 +38,65 @@ #include "tree_schema.h" #include "validation.h" -#include "cbor.h" - #include #include void print_json(cbor_item_t *item); -void print_json_string(const cbor_item_t *item) { +void print_json_string(const cbor_item_t *item) +{ size_t length = cbor_string_length(item); char *str = (char *)cbor_string_handle(item); printf("\"%.*s\"", (int)length, str); } -void print_json_map(const cbor_item_t *item) { +void print_json_map(const cbor_item_t *item) +{ printf("{"); size_t size = cbor_map_size(item); struct cbor_pair *pairs = cbor_map_handle(item); - for (size_t i = 0; i < size; ++i) { + for (size_t i = 0; i < size; ++i) + { print_json(pairs[i].key); printf(": "); print_json(pairs[i].value); - if (i < size - 1) printf(", "); + if (i < size - 1) + printf(", "); } printf("}"); } -void print_json_bool(const cbor_item_t *item) { +void print_json_bool(const cbor_item_t *item) +{ printf(cbor_is_bool(item) && cbor_ctrl_value(item) ? "true" : "false"); } -void print_json(cbor_item_t *item) { - if (cbor_isa_map(item)) { +void print_json(cbor_item_t *item) +{ + if (cbor_isa_map(item)) + { print_json_map(item); - } else if (cbor_isa_string(item)) { + } + else if (cbor_isa_string(item)) + { print_json_string(item); - } else if (cbor_is_bool(item)) { + } + else if (cbor_is_bool(item)) + { print_json_bool(item); - } else { + } + else + { printf("null"); // fallback for unsupported types } } +static LY_ERR lydcbor_parse_subtree(struct lyd_cbor_ctx *lydctx, struct lyd_node *parent, + struct lyd_node **first_p, struct ly_set *parsed, const cbor_item_t *cbor_obj); + /** * @brief Free the CBOR parser context * @@ -95,90 +107,14 @@ lyd_cbor_ctx_free(struct lyd_ctx *lydctx) { struct lyd_cbor_ctx *ctx = (struct lyd_cbor_ctx *)lydctx; - if(lydctx){ + if (lydctx) + { lyd_ctx_free(lydctx); lycbor_ctx_free(ctx->cborctx); free(ctx); } } -/** - * @brief Create new CBOR context for parsing. - * - * @param[in] ctx libyang context. - * @param[in] in Input handler. - * @param[out] cbor_ctx_p Pointer to store the created CBOR context. - * @return LY_ERR value. - */ -LY_ERR -lycbor_ctx_new(const struct ly_ctx *ctx, struct ly_in *in, struct lycbor_ctx **cbor_ctx_p) -{ - LY_ERR ret = LY_SUCCESS; - struct lycbor_ctx *cbor_ctx; - - assert(ctx && in && cbor_ctx_p); - - /* Allocate and initialize CBOR context */ - cbor_ctx = calloc(1, sizeof *cbor_ctx); - LY_CHECK_ERR_RET(!cbor_ctx, LOGMEM(ctx), LY_EMEM); - - cbor_ctx->ctx = ctx; - cbor_ctx->in = in; - - *cbor_ctx_p = cbor_ctx; - return ret; -} - -/** - * @brief Free CBOR context. - * - * @param[in] cbor_ctx CBOR context to free. - */ -void -lycbor_ctx_free(struct lycbor_ctx *cbor_ctx) -{ - if (cbor_ctx) { - free(cbor_ctx); - } -} - -/** - * @brief Create new CBOR parser context - */ -LY_ERR -lydcbor_ctx_init(const struct ly_ctx *ctx, struct ly_in *in, - uint32_t parse_opts, uint32_t val_opts, enum lyd_cbor_format format, - struct lyd_cbor_ctx **lydctx_p) -{ - LY_ERR ret = LY_SUCCESS; - struct lyd_cbor_ctx *lydctx = NULL; - - assert(lydctx_p); - - /* Initialize context with calloc to ensure all fields are zero */ - lydctx = calloc(1, sizeof *lydctx); - LY_CHECK_ERR_RET(!lydctx, LOGMEM(ctx), LY_EMEM); - lydctx->parse_opts = parse_opts; - lydctx->val_opts = val_opts; - lydctx->free = lyd_cbor_ctx_free; - lydctx->format = format; - - lydctx->cborctx = NULL; /* Will be set below */ - - /* Create low-level CBOR context */ - LY_CHECK_GOTO(ret = lycbor_ctx_new(ctx, in, &lydctx->cborctx), cleanup); - - *lydctx_p = lydctx; - return ret; - -cleanup: - if (lydctx) { - lyd_cbor_ctx_free((struct lyd_ctx *)lydctx); - } - return ret; -} - - /** * @brief Convert a CBOR item to a string representation. * @@ -205,7 +141,8 @@ lydcbor_item_to_string(const cbor_item_t *item, char **str_val, size_t *str_len) { uint64_t val = cbor_get_int(item); int len = snprintf(NULL, 0, "%" PRIu64, val); - if (len < 0) { + if (len < 0) + { return LY_ESYS; } *str_val = malloc(len + 1); @@ -218,7 +155,8 @@ lydcbor_item_to_string(const cbor_item_t *item, char **str_val, size_t *str_len) { int64_t val = -1 - (int64_t)cbor_get_int(item); int len = snprintf(NULL, 0, "%" PRId64, val); - if (len < 0) { + if (len < 0) + { return LY_ESYS; } *str_val = malloc(len + 1); @@ -270,7 +208,8 @@ lydcbor_item_to_string(const cbor_item_t *item, char **str_val, size_t *str_len) /* Float value */ double val = cbor_float_get_float(item); int len = snprintf(NULL, 0, "%g", val); - if (len < 0) { + if (len < 0) + { return LY_ESYS; } *str_val = malloc(len + 1); @@ -302,7 +241,7 @@ lydcbor_get_key_string(struct lyd_cbor_ctx *lydctx, const cbor_item_t *key_item, assert(lydctx && key_item && key_str && key_len); - switch (lydctx->format) + switch (lydctx->cborctx->format) { case LYD_CBOR_NAMED: /* Keys must be strings for named format */ @@ -327,7 +266,16 @@ lydcbor_get_key_string(struct lyd_cbor_ctx *lydctx, const cbor_item_t *key_item, return ret; } -LY_ERR +/** + * @brief Parse a single CBOR value according to schema node. + * + * @param[in] lydctx CBOR parser context. + * @param[in] snode Schema node for the value. + * @param[in] cbor_item CBOR item to parse. + * @param[out] node Created data node. + * @return LY_ERR value. + */ +static LY_ERR lydcbor_parse_value(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, const void *cbor_item, struct lyd_node **node) { @@ -364,228 +312,17 @@ lydcbor_parse_value(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, return ret; } - -/** - * @brief Get module prefix from a qualified name. - * - * @param[in] qname Qualified name (prefix:name or just name). - * @param[in] qname_len Length of the qualified name. - * @param[out] prefix Extracted prefix (points into qname, not allocated). - * @param[out] prefix_len Length of the prefix. - * @param[out] name Local name (points into qname, not allocated). - * @param[out] name_len Length of the local name. - * @return LY_SUCCESS on success. - */ -static LY_ERR -lydcbor_parse_qname(const char *qname, size_t qname_len, const char **prefix, size_t *prefix_len, - const char **name, size_t *name_len) -{ - const char *colon; - - assert(qname && name && name_len); - - *name = qname; - *name_len = qname_len; - - if (prefix) { - *prefix = NULL; - } - if (prefix_len) { - *prefix_len = 0; - } - - /* Look for module prefix separator */ - colon = ly_strnchr(qname, ':', qname_len); - if (colon) { - /* We have a module prefix */ - if (prefix) { - *prefix = qname; - *prefix_len = colon - qname; - } - - /* Local name starts after the colon */ - *name = colon + 1; - *name_len = qname_len - (colon - qname) - 1; - - /* Validate we have both prefix and name */ - if ((colon == qname) || (*name_len == 0)) { - return LY_EVALID; - } - } - - return LY_SUCCESS; -} - -/** - * @brief Get schema node from CBOR node name, following lydjson_get_snode logic. - * - * @param[in] lydctx CBOR parser context. - * @param[in] name Node name. - * @param[in] name_len Length of node name. - * @param[in] parent Data parent node. - * @param[out] snode Schema node found. - * @param[out] ext Extension instance if found. - * @return LY_ERR value. - */ -static LY_ERR -lydcbor_get_snode(struct lyd_cbor_ctx *lydctx, const char *name, size_t name_len, - struct lyd_node *parent, const struct lysc_node **snode, - const struct lysc_ext_instance **ext) -{ - LY_ERR ret = LY_SUCCESS, r; - const char *prefix = NULL, *local_name = NULL; - size_t prefix_len = 0, local_name_len = 0; - const struct lys_module *mod = NULL; - const struct lysc_node *sparent = NULL; - uint32_t getnext_opts; - - assert(lydctx && name && snode); - *snode = NULL; - if (ext) { - *ext = NULL; - } - - /* Parse qualified name */ - LY_CHECK_RET(lydcbor_parse_qname(name, name_len, &prefix, &prefix_len, &local_name, &local_name_len)); - - /* Get parent schema node */ - if (parent) { - sparent = parent->schema; - if (!sparent) { - /* Opaque parent */ - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Cannot parse \"%.*s\" node with opaque parent.", - (int)local_name_len, local_name); - ret = LY_EVALID; - goto cleanup; - } - } else { - sparent = NULL; - } - - /* Resolve module if prefix is present */ - if (prefix) { - mod = ly_ctx_get_module_implemented2(lydctx->cborctx->ctx, prefix, prefix_len); - if (!mod) { - if (lydctx->parse_opts & LYD_PARSE_STRICT) { - LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, "Unknown module \"%.*s\".", (int)prefix_len, prefix); - ret = LY_EVALID; - goto cleanup; - } - if (!(lydctx->parse_opts & LYD_PARSE_OPAQ)) { - LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, "Unknown module \"%.*s\".", (int)prefix_len, prefix); - ret = LY_EVALID; - goto cleanup; - } - } - } else if (!sparent) { - /* Top-level node without prefix - need to find module */ - /* Try to find the node in all implemented modules */ - const struct lys_module *iter_mod; - uint32_t idx = 0; - ly_bool found = 0; - - while ((iter_mod = ly_ctx_get_module_iter(lydctx->cborctx->ctx, &idx))) { - if (!iter_mod->implemented) { - continue; - } - - /* Check if node exists in this module */ - if (lys_find_child(NULL, iter_mod, local_name, local_name_len, 0, 0)) { - if (found) { - /* Ambiguous name */ - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Ambiguous node name \"%.*s\", use module prefix.", - (int)local_name_len, local_name); - ret = LY_EVALID; - goto cleanup; - } - mod = iter_mod; - found = 1; - } - } - - if (!found && !(lydctx->parse_opts & LYD_PARSE_OPAQ)) { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Unknown node \"%.*s\".", (int)local_name_len, local_name); - ret = LY_EVALID; - goto cleanup; - } - } - - /* Set getnext options */ - getnext_opts = lydctx->int_opts & LYD_INTOPT_REPLY ? LYS_GETNEXT_OUTPUT : 0; - if (parent && (parent->schema->nodetype & (LYS_RPC | LYS_ACTION))) { - if (lydctx->int_opts & LYD_INTOPT_RPC) { - getnext_opts = 0; - } else if (lydctx->int_opts & LYD_INTOPT_REPLY) { - getnext_opts = LYS_GETNEXT_OUTPUT; - } - } - - /* Find schema node */ - if (sparent) { - /* Search in parent's children */ - *snode = lys_find_child(sparent, sparent->module, local_name, local_name_len, 0, getnext_opts); - - /* Try to find extension data if regular node not found */ - if (!*snode && ext) { - r = ly_nested_ext_schema(parent, sparent, prefix, prefix_len, LY_VALUE_JSON, NULL, - local_name, local_name_len, snode, ext); - if (r != LY_ENOT) { - if (r) { - ret = r; - goto cleanup; - } - } - } - } else { - /* Top-level node */ - if (mod) { - /* Search in specific module */ - *snode = lys_find_child(NULL, mod, local_name, local_name_len, 0, getnext_opts); - } - /* Extension data for top-level not typically handled */ - } - - /* Handle missing schema node */ - if (!*snode) { - if (lydctx->parse_opts & LYD_PARSE_STRICT) { - if (prefix) { - LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, "Unknown element \"%.*s\" in module \"%.*s\".", - (int)local_name_len, local_name, (int)prefix_len, prefix); - } else { - LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, "Unknown element \"%.*s\".", - (int)local_name_len, local_name); - } - ret = LY_EVALID; - goto cleanup; - } else if (!(lydctx->parse_opts & LYD_PARSE_OPAQ)) { - /* Log error but continue if not in strict mode and opaque allowed */ - if (prefix) { - LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, "Unknown element \"%.*s\" in module \"%.*s\".", - (int)local_name_len, local_name, (int)prefix_len, prefix); - } else { - LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, "Unknown element \"%.*s\".", - (int)local_name_len, local_name); - } - ret = LY_EVALID; - goto cleanup; - } - /* If opaque parsing allowed, *snode remains NULL and caller handles it */ - } - -cleanup: - return ret; -} - static LY_ERR lydcbor_parse_node_value(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, - struct lyd_node **node, const cbor_item_t *cbor_value) + struct lyd_node **node, const cbor_item_t *cbor_value) { LY_ERR ret = LY_SUCCESS; - + assert(lydctx && snode && node && cbor_value); *node = NULL; - - switch (snode->nodetype) { + + switch (snode->nodetype) + { case LYS_CONTAINER: ret = lyd_create_inner(snode, node); break; @@ -593,12 +330,14 @@ lydcbor_parse_node_value(struct lyd_cbor_ctx *lydctx, const struct lysc_node *sn ret = lyd_create_inner(snode, node); break; case LYS_LEAF: - case LYS_LEAFLIST: { + case LYS_LEAFLIST: + { char *str_val = NULL; size_t str_len = 0; - + ret = lydcbor_item_to_string(cbor_value, &str_val, &str_len); - if (ret == LY_SUCCESS) { + if (ret == LY_SUCCESS) + { ret = lyd_create_term(snode, str_val, str_len, 0, 0, NULL, LY_VALUE_JSON, NULL, LYD_HINT_DATA, NULL, node); } free(str_val); @@ -613,227 +352,89 @@ lydcbor_parse_node_value(struct lyd_cbor_ctx *lydctx, const struct lysc_node *sn ret = LY_EVALID; break; } - + return ret; } static LY_ERR -lydcbor_parse_leaflist_array(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, - const cbor_item_t *array_item, struct lyd_node **first_p, struct ly_set *parsed) +lydcbor_parse_terminal(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, + const cbor_item_t *cbor_value, struct lyd_node **first_p, struct ly_set *parsed) { - LY_ERR ret = LY_SUCCESS; + LY_ERR ret; struct lyd_node *node = NULL; - size_t array_size; - cbor_item_t **array_handle; - assert(lydctx && snode && array_item && parsed); - - if (!cbor_isa_array(array_item)) { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Expected CBOR array for leaf-list"); - return LY_EVALID; - } - - if (snode->nodetype != LYS_LEAFLIST) { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Schema node must be leaf-list"); - return LY_EVALID; - } - - array_size = cbor_array_size(array_item); - array_handle = cbor_array_handle(array_item); - - if (!array_handle && array_size > 0) { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Invalid CBOR array structure"); - return LY_EVALID; - } - - for (size_t i = 0; i < array_size; ++i) { - const cbor_item_t *item = array_handle[i]; - - if (!item) { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Null array element at index %zu", i); - ret = LY_EVALID; - goto cleanup; - } - - LY_CHECK_GOTO(ret = lydcbor_parse_node_value(lydctx, snode, &node, item), cleanup); - - if (!node) { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Failed to create node for array element %zu", i); - ret = LY_EVALID; - goto cleanup; - } - - /* Insert the node */ - ret = lyd_insert_sibling(*first_p, node, first_p); - LY_CHECK_GOTO(ret, cleanup); - - /* Add to parsed set */ - LY_CHECK_GOTO(ret = ly_set_add(parsed, node, 1, NULL), cleanup); - node = NULL; /* Reset pointer after successful insertion */ - } - -cleanup: - if (ret && node) { - lyd_free_tree(node); - } - return ret; -} - -static LY_ERR -lydcbor_parse_subtree(struct lyd_cbor_ctx *lydctx, struct lyd_node *parent, - struct lyd_node **first_p, struct ly_set *parsed, const cbor_item_t *cbor_obj) -{ - LY_ERR ret = LY_SUCCESS; - - printf("Entering lydcbor_parse_subtree\n"); - printf("CBOR object:\n"); - print_json(cbor_obj); - printf("\n"); - - const struct lysc_node *snode = NULL; - char *key_str = NULL; - size_t key_len = 0; -\ - assert(lydctx && first_p && parsed && cbor_obj); - - /* assuming that the top level structure is always a map - to be modified to include anything else that it can support */ - - if (!cbor_isa_map(cbor_obj)) { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Expected CBOR map"); - return LY_EVALID; - } - - size_t map_size = cbor_map_size(cbor_obj); - struct cbor_pair *pairs = cbor_map_handle(cbor_obj); - - if (!pairs && map_size > 0) { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Invalid CBOR map structure"); - return LY_EVALID; - } - - for (size_t i = 0; i < map_size; ++i) { - const cbor_item_t *key_item = pairs[i].key; - const cbor_item_t *value_item = pairs[i].value; - - if (!key_item || !value_item) { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Null key or value at map index %zu", i); - ret = LY_EVALID; - goto cleanup; - } - - /* Get key string */ - LY_CHECK_GOTO(ret = lydcbor_get_key_string(lydctx, key_item, &key_str, &key_len), cleanup); - - /* Find schema node */ - LY_CHECK_GOTO(ret = lydcbor_get_snode(lydctx, key_str, key_len, - parent, &snode, NULL), cleanup); - - /* Handle different node types */ - if (snode->nodetype & (LYS_LEAF | LYS_LEAFLIST)) { - if (snode->nodetype == LYS_LEAFLIST && cbor_isa_array(value_item)) { - ret = lydcbor_parse_leaflist_array(lydctx, snode, value_item, first_p, parsed); - } else { - ret = lydcbor_parse_terminal(lydctx, snode, value_item, first_p, parsed); - } - } else if (snode->nodetype == LYS_CONTAINER) { - ret = lydcbor_parse_container(lydctx, snode, value_item, first_p, parsed); - } else if (snode->nodetype == LYS_LIST) { - ret = lydcbor_parse_list(lydctx, snode, value_item, first_p, parsed); - } else if (snode->nodetype & (LYS_ANYDATA | LYS_ANYXML)) { - ret = lydcbor_parse_any(lydctx, snode, value_item, first_p, parsed); - } else { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Invalid schema node type %d for \"%s\"", - snode->nodetype, snode->name); - ret = LY_EVALID; - } - - LY_CHECK_GOTO(ret, cleanup); - - free(key_str); - key_str = NULL; - } - -cleanup: - free(key_str); - return ret; -} - -// Add these new functions: - -static LY_ERR -lydcbor_parse_terminal(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, - const cbor_item_t *cbor_value, struct lyd_node **first_p, struct ly_set *parsed) -{ - LY_ERR ret; - struct lyd_node *node = NULL; - ret = lydcbor_parse_node_value(lydctx, snode, &node, cbor_value); LY_CHECK_RET(ret); - - if (!node) { + + if (!node) + { LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Failed to create terminal node for \"%s\"", snode->name); return LY_EVALID; } - + /* Insert into tree */ ret = lyd_insert_sibling(*first_p, node, first_p); - if (ret) { + if (ret) + { lyd_free_tree(node); return ret; } - + /* Add to parsed set */ return ly_set_add(parsed, node, 1, NULL); } static LY_ERR lydcbor_parse_container(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, - const cbor_item_t *cbor_value, struct lyd_node **first_p, struct ly_set *parsed) + const cbor_item_t *cbor_value, struct lyd_node **first_p, struct ly_set *parsed) { LY_ERR ret; struct lyd_node *node = NULL; - + ret = lyd_create_inner(snode, &node); LY_CHECK_RET(ret); - - if (!node) { + + if (!node) + { LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Failed to create container node for \"%s\"", snode->name); return LY_EVALID; } - + /* Insert into tree first */ ret = lyd_insert_sibling(*first_p, node, first_p); - if (ret) { + if (ret) + { lyd_free_tree(node); return ret; } - + /* Add to parsed set */ ret = ly_set_add(parsed, node, 1, NULL); LY_CHECK_RET(ret); - + /* Parse container children */ - if (cbor_isa_map(cbor_value) && cbor_map_size(cbor_value) > 0) { + if (cbor_isa_map(cbor_value) && cbor_map_size(cbor_value) > 0) + { struct lyd_node *child_first = NULL; ret = lydcbor_parse_subtree(lydctx, node, &child_first, parsed, cbor_value); - if (ret) { + if (ret) + { return ret; } - + /* Link children to container */ - if (child_first) { + if (child_first) + { lyd_insert_child(node, child_first); } } - + return LY_SUCCESS; } -// Replace lydcbor_parse_list_array with this improved version: static LY_ERR lydcbor_parse_list_array(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, - const cbor_item_t *array_item, struct lyd_node **first_p, struct ly_set *parsed) + const cbor_item_t *array_item, struct lyd_node **first_p, struct ly_set *parsed) { LY_ERR ret = LY_SUCCESS; struct lyd_node *node = NULL; @@ -841,71 +442,80 @@ lydcbor_parse_list_array(struct lyd_cbor_ctx *lydctx, const struct lysc_node *sn cbor_item_t **array_handle; assert(lydctx && snode && array_item && parsed); - - if (!cbor_isa_array(array_item)) { + + if (!cbor_isa_array(array_item)) + { LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Expected CBOR array for list"); return LY_EVALID; } - - if (snode->nodetype != LYS_LIST) { + + if (snode->nodetype != LYS_LIST) + { LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Schema node must be list"); return LY_EVALID; } array_size = cbor_array_size(array_item); array_handle = cbor_array_handle(array_item); - - if (!array_handle && array_size > 0) { + + if (!array_handle && array_size > 0) + { LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Invalid CBOR array structure"); return LY_EVALID; } - for (size_t i = 0; i < array_size; ++i) { + for (size_t i = 0; i < array_size; ++i) + { const cbor_item_t *item = array_handle[i]; - - if (!item) { + + if (!item) + { LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Null array element at index %zu", i); ret = LY_EVALID; goto cleanup; } - - if (!cbor_isa_map(item)) { + + if (!cbor_isa_map(item)) + { LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "List entry must be a CBOR map"); ret = LY_EVALID; goto cleanup; } - + ret = lyd_create_inner(snode, &node); LY_CHECK_GOTO(ret, cleanup); - - if (!node) { + + if (!node) + { LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Failed to create list node"); ret = LY_EVALID; goto cleanup; } - + /* Insert the list node */ ret = lyd_insert_sibling(*first_p, node, first_p); LY_CHECK_GOTO(ret, cleanup); - + /* Add to parsed set */ LY_CHECK_GOTO(ret = ly_set_add(parsed, node, 1, NULL), cleanup); - + /* Parse list entry content */ struct lyd_node *child_first = NULL; ret = lydcbor_parse_subtree(lydctx, node, &child_first, parsed, item); LY_CHECK_GOTO(ret, cleanup); - + /* Link children to list entry */ - if (child_first) { + if (child_first) + { lyd_insert_child(node, child_first); } - + node = NULL; /* Reset pointer after successful processing */ } cleanup: - if (ret && node) { + if (ret && node) + { lyd_free_tree(node); } return ret; @@ -913,87 +523,96 @@ lydcbor_parse_list_array(struct lyd_cbor_ctx *lydctx, const struct lysc_node *sn static LY_ERR lydcbor_parse_list(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, - const cbor_item_t *cbor_value, struct lyd_node **first_p, struct ly_set *parsed) + const cbor_item_t *cbor_value, struct lyd_node **first_p, struct ly_set *parsed) { LY_ERR ret = LY_SUCCESS; - - if (cbor_isa_array(cbor_value)) { + + if (cbor_isa_array(cbor_value)) + { /* Array of list entries */ ret = lydcbor_parse_list_array(lydctx, snode, cbor_value, first_p, parsed); - } else if (cbor_isa_map(cbor_value)) { + } + else if (cbor_isa_map(cbor_value)) + { /* Single list entry */ struct lyd_node *node = NULL; - + ret = lyd_create_inner(snode, &node); LY_CHECK_RET(ret); - + /* Insert into tree */ ret = lyd_insert_sibling(*first_p, node, first_p); - if (ret) { + if (ret) + { lyd_free_tree(node); return ret; } - + /* Add to parsed set */ ret = ly_set_add(parsed, node, 1, NULL); LY_CHECK_RET(ret); - + /* Parse list entry content */ struct lyd_node *child_first = NULL; ret = lydcbor_parse_subtree(lydctx, node, &child_first, parsed, cbor_value); - if (ret) { + if (ret) + { return ret; } - + /* Link children to list entry */ - if (child_first) { + if (child_first) + { lyd_insert_child(node, child_first); } - } else { + } + else + { LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "List \"%s\" value must be a CBOR map or array", snode->name); ret = LY_EVALID; } - + return ret; } static LY_ERR lydcbor_parse_any(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, - const cbor_item_t *cbor_value, struct lyd_node **first_p, struct ly_set *parsed) + const cbor_item_t *cbor_value, struct lyd_node **first_p, struct ly_set *parsed) { LY_ERR ret; struct lyd_node *node = NULL; - + ret = lyd_create_any(snode, cbor_value, LYD_ANYDATA_CBOR, 0, &node); LY_CHECK_RET(ret); - - if (!node) { + + if (!node) + { LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Failed to create any node for \"%s\"", snode->name); return LY_EVALID; } - + /* Insert into tree */ ret = lyd_insert_sibling(*first_p, node, first_p); - if (ret) { + if (ret) + { lyd_free_tree(node); return ret; } - + /* Add to parsed set */ return ly_set_add(parsed, node, 1, NULL); } -LY_ERR -lydcbor_detect_format(struct ly_in *in, enum lyd_cbor_format *format) -{ - /* Simple heuristic: try to parse as CBOR and examine structure */ - /* For now, default to named format */ - (void)in; - *format = LYD_CBOR_NAMED; - return LY_SUCCESS; -} -LY_ERR +/** + * @brief Parse CBOR metadata/attributes. + * + * @param[in] lydctx CBOR parser context. + * @param[in] cbor_item CBOR item containing metadata. + * @param[in,out] node Data node to attach metadata to. + * @return LY_ERR value. + */ +static LY_ERR lydcbor_parse_metadata(struct lyd_cbor_ctx *lydctx, const void *cbor_item, struct lyd_node *node) { /* Future implementation for CBOR metadata parsing */ @@ -1003,67 +622,527 @@ lydcbor_parse_metadata(struct lyd_cbor_ctx *lydctx, const void *cbor_item, struc return LY_SUCCESS; } -LY_ERR -lyd_parse_cbor(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, struct lyd_node *parent, - 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) -{ - LY_ERR ret = LY_SUCCESS; - struct lyd_cbor_ctx *lydctx = NULL; - cbor_item_t *cbor_data = NULL; - struct cbor_load_result result = {0}; - enum lyd_cbor_format format; - /* Detect CBOR format - Named or SID */ - LY_CHECK_GOTO(ret = lydcbor_detect_format(in, &format), cleanup); +/** + * @brief Get module prefix from a qualified name. + * + * @param[in] qname Qualified name (prefix:name or just name). + * @param[in] qname_len Length of the qualified name. + * @param[out] prefix Extracted prefix (points into qname, not allocated). + * @param[out] prefix_len Length of the prefix. + * @param[out] name Local name (points into qname, not allocated). + * @param[out] name_len Length of the local name. + * @return LY_SUCCESS on success. + */ +static LY_ERR +lydcbor_parse_qname(const char *qname, size_t qname_len, const char **prefix, size_t *prefix_len, + const char **name, size_t *name_len) +{ + const char *colon; - /* Initialize context */ - LY_CHECK_GOTO(ret = lydcbor_ctx_init(ctx, in, parse_opts, val_opts, format, &lydctx), cleanup); - - lydctx->int_opts = int_opts; - lydctx->ext = ext; + assert(qname && name && name_len); - /* - * Loads CBOR data from the current input buffer. - * - * Parameters: - * in->current - Pointer to the current position in the input buffer. - * in->length - Length of the data to be loaded. - * &result - Pointer to a variable where the result status will be stored. - * - * Returns: - * cbor_data - Pointer to the loaded CBOR data structure, or NULL on failure. - */ - /* need to convert in->current from const char* to cbor_data type */ - cbor_data = cbor_load(in->current, in->length, &result); - lydctx->cborctx->cbor_data = cbor_data; + *name = qname; + *name_len = qname_len; - if (!cbor_data) { - LOGVAL(ctx, LYVE_SYNTAX, "Failed to parse CBOR data: no data returned from cbor_load()."); - ret = LY_EVALID; - goto cleanup; + if (prefix) + { + *prefix = NULL; } - if (result.error.code != CBOR_ERR_NONE) { - LOGVAL(ctx, LYVE_SYNTAX, "Failed to parse CBOR data: parsing error (code %d).", result.error.code); - ret = LY_EVALID; - goto cleanup; + if (prefix_len) + { + *prefix_len = 0; } - /* Probably need to check if the obtained data is a operational node and - then write functions to parse them accordingly. If not then continue below */ + /* Look for module prefix separator */ + colon = ly_strnchr(qname, ':', qname_len); + if (colon) + { + /* We have a module prefix */ + if (prefix) + { + *prefix = qname; + *prefix_len = colon - qname; + } - /* Parse the CBOR structure */ - ret = lydcbor_parse_subtree(lydctx, parent, first_p, parsed, cbor_data); + /* Local name starts after the colon */ + *name = colon + 1; + *name_len = qname_len - (colon - qname) - 1; -cleanup: - if (cbor_data) - { - cbor_decref(&cbor_data); + /* Validate we have both prefix and name */ + if ((colon == qname) || (*name_len == 0)) + { + return LY_EVALID; + } } - if (ret) - { - if (lydctx) { + return LY_SUCCESS; +} + +/** + * @brief Get schema node from CBOR node name, following lydjson_get_snode logic. + * + * @param[in] lydctx CBOR parser context. + * @param[in] name Node name. + * @param[in] name_len Length of node name. + * @param[in] parent Data parent node. + * @param[out] snode Schema node found. + * @param[out] ext Extension instance if found. + * @return LY_ERR value. + */ +static LY_ERR +lydcbor_get_snode(struct lyd_cbor_ctx *lydctx, const char *name, size_t name_len, + struct lyd_node *parent, const struct lysc_node **snode, + const struct lysc_ext_instance **ext) +{ + LY_ERR ret = LY_SUCCESS, r; + const char *prefix = NULL, *local_name = NULL; + size_t prefix_len = 0, local_name_len = 0; + const struct lys_module *mod = NULL; + const struct lysc_node *sparent = NULL; + uint32_t getnext_opts; + + assert(lydctx && name && snode); + *snode = NULL; + if (ext) + { + *ext = NULL; + } + + /* Parse qualified name */ + LY_CHECK_RET(lydcbor_parse_qname(name, name_len, &prefix, &prefix_len, &local_name, &local_name_len)); + + /* Get parent schema node */ + if (parent) + { + sparent = parent->schema; + if (!sparent) + { + /* Opaque parent */ + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Cannot parse \"%.*s\" node with opaque parent.", + (int)local_name_len, local_name); + ret = LY_EVALID; + goto cleanup; + } + } + else + { + sparent = NULL; + } + + /* Resolve module if prefix is present */ + if (prefix) + { + mod = ly_ctx_get_module_implemented2(lydctx->cborctx->ctx, prefix, prefix_len); + if (!mod) + { + if (lydctx->parse_opts & LYD_PARSE_STRICT) + { + LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, "Unknown module \"%.*s\".", (int)prefix_len, prefix); + ret = LY_EVALID; + goto cleanup; + } + if (!(lydctx->parse_opts & LYD_PARSE_OPAQ)) + { + LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, "Unknown module \"%.*s\".", (int)prefix_len, prefix); + ret = LY_EVALID; + goto cleanup; + } + } + } + else if (!sparent) + { + /* Top-level node without prefix - need to find module */ + /* Try to find the node in all implemented modules */ + const struct lys_module *iter_mod; + uint32_t idx = 0; + ly_bool found = 0; + + while ((iter_mod = ly_ctx_get_module_iter(lydctx->cborctx->ctx, &idx))) + { + if (!iter_mod->implemented) + { + continue; + } + + /* Check if node exists in this module */ + if (lys_find_child(NULL, iter_mod, local_name, local_name_len, 0, 0)) + { + if (found) + { + /* Ambiguous name */ + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Ambiguous node name \"%.*s\", use module prefix.", + (int)local_name_len, local_name); + ret = LY_EVALID; + goto cleanup; + } + mod = iter_mod; + found = 1; + } + } + + if (!found && !(lydctx->parse_opts & LYD_PARSE_OPAQ)) + { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Unknown node \"%.*s\".", (int)local_name_len, local_name); + ret = LY_EVALID; + goto cleanup; + } + } + + /* Set getnext options */ + getnext_opts = lydctx->int_opts & LYD_INTOPT_REPLY ? LYS_GETNEXT_OUTPUT : 0; + if (parent && (parent->schema->nodetype & (LYS_RPC | LYS_ACTION))) + { + if (lydctx->int_opts & LYD_INTOPT_RPC) + { + getnext_opts = 0; + } + else if (lydctx->int_opts & LYD_INTOPT_REPLY) + { + getnext_opts = LYS_GETNEXT_OUTPUT; + } + } + + /* Find schema node */ + if (sparent) + { + /* Search in parent's children */ + *snode = lys_find_child(sparent, sparent->module, local_name, local_name_len, 0, getnext_opts); + + /* Try to find extension data if regular node not found */ + if (!*snode && ext) + { + r = ly_nested_ext_schema(parent, sparent, prefix, prefix_len, LY_VALUE_JSON, NULL, + local_name, local_name_len, snode, ext); + if (r != LY_ENOT) + { + if (r) + { + ret = r; + goto cleanup; + } + } + } + } + else + { + /* Top-level node */ + if (mod) + { + /* Search in specific module */ + *snode = lys_find_child(NULL, mod, local_name, local_name_len, 0, getnext_opts); + } + /* Extension data for top-level not typically handled */ + } + + /* Handle missing schema node */ + if (!*snode) + { + if (lydctx->parse_opts & LYD_PARSE_STRICT) + { + if (prefix) + { + LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, "Unknown element \"%.*s\" in module \"%.*s\".", + (int)local_name_len, local_name, (int)prefix_len, prefix); + } + else + { + LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, "Unknown element \"%.*s\".", + (int)local_name_len, local_name); + } + ret = LY_EVALID; + goto cleanup; + } + else if (!(lydctx->parse_opts & LYD_PARSE_OPAQ)) + { + /* Log error but continue if not in strict mode and opaque allowed */ + if (prefix) + { + LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, "Unknown element \"%.*s\" in module \"%.*s\".", + (int)local_name_len, local_name, (int)prefix_len, prefix); + } + else + { + LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, "Unknown element \"%.*s\".", + (int)local_name_len, local_name); + } + ret = LY_EVALID; + goto cleanup; + } + /* If opaque parsing allowed, *snode remains NULL and caller handles it */ + } + +cleanup: + return ret; +} + +static LY_ERR +lydcbor_parse_leaflist_array(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, + const cbor_item_t *array_item, struct lyd_node **first_p, struct ly_set *parsed) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_node *node = NULL; + size_t array_size; + cbor_item_t **array_handle; + + assert(lydctx && snode && array_item && parsed); + + if (!cbor_isa_array(array_item)) + { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Expected CBOR array for leaf-list"); + return LY_EVALID; + } + + if (snode->nodetype != LYS_LEAFLIST) + { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Schema node must be leaf-list"); + return LY_EVALID; + } + + array_size = cbor_array_size(array_item); + array_handle = cbor_array_handle(array_item); + + if (!array_handle && array_size > 0) + { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Invalid CBOR array structure"); + return LY_EVALID; + } + + for (size_t i = 0; i < array_size; ++i) + { + const cbor_item_t *item = array_handle[i]; + + if (!item) + { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Null array element at index %zu", i); + ret = LY_EVALID; + goto cleanup; + } + + LY_CHECK_GOTO(ret = lydcbor_parse_node_value(lydctx, snode, &node, item), cleanup); + + if (!node) + { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Failed to create node for array element %zu", i); + ret = LY_EVALID; + goto cleanup; + } + + /* Insert the node */ + ret = lyd_insert_sibling(*first_p, node, first_p); + LY_CHECK_GOTO(ret, cleanup); + + /* Add to parsed set */ + LY_CHECK_GOTO(ret = ly_set_add(parsed, node, 1, NULL), cleanup); + node = NULL; /* Reset pointer after successful insertion */ + } + +cleanup: + if (ret && node) + { + lyd_free_tree(node); + } + return ret; +} + +static LY_ERR +lydcbor_parse_subtree(struct lyd_cbor_ctx *lydctx, struct lyd_node *parent, + struct lyd_node **first_p, struct ly_set *parsed, const cbor_item_t *cbor_obj) +{ + LY_ERR ret = LY_SUCCESS; + + printf("Entering lydcbor_parse_subtree\n"); + printf("CBOR object:\n"); + print_json(cbor_obj); + printf("\n"); + + const struct lysc_node *snode = NULL; + char *key_str = NULL; + size_t key_len = 0; + + assert(lydctx && first_p && parsed && cbor_obj); + + /* assuming that the top level structure is always a map + to be modified to include anything else that it can support */ + + if (!cbor_isa_map(cbor_obj)) + { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Expected CBOR map"); + return LY_EVALID; + } + + size_t map_size = cbor_map_size(cbor_obj); + struct cbor_pair *pairs = cbor_map_handle(cbor_obj); + + if (!pairs && map_size > 0) + { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Invalid CBOR map structure"); + return LY_EVALID; + } + + for (size_t i = 0; i < map_size; ++i) + { + const cbor_item_t *key_item = pairs[i].key; + const cbor_item_t *value_item = pairs[i].value; + + if (!key_item || !value_item) + { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Null key or value at map index %zu", i); + ret = LY_EVALID; + goto cleanup; + } + + /* Get key string */ + LY_CHECK_GOTO(ret = lydcbor_get_key_string(lydctx, key_item, &key_str, &key_len), cleanup); + + /* Find schema node */ + LY_CHECK_GOTO(ret = lydcbor_get_snode(lydctx, key_str, key_len, + parent, &snode, NULL), + cleanup); + + /* Handle different node types */ + if (snode->nodetype & (LYS_LEAF | LYS_LEAFLIST)) + { + if (snode->nodetype == LYS_LEAFLIST && cbor_isa_array(value_item)) + { + ret = lydcbor_parse_leaflist_array(lydctx, snode, value_item, first_p, parsed); + } + else + { + ret = lydcbor_parse_terminal(lydctx, snode, value_item, first_p, parsed); + } + } + else if (snode->nodetype == LYS_CONTAINER) + { + ret = lydcbor_parse_container(lydctx, snode, value_item, first_p, parsed); + } + else if (snode->nodetype == LYS_LIST) + { + ret = lydcbor_parse_list(lydctx, snode, value_item, first_p, parsed); + } + else if (snode->nodetype & (LYS_ANYDATA | LYS_ANYXML)) + { + ret = lydcbor_parse_any(lydctx, snode, value_item, first_p, parsed); + } + else + { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Invalid schema node type %d for \"%s\"", + snode->nodetype, snode->name); + ret = LY_EVALID; + } + + LY_CHECK_GOTO(ret, cleanup); + + free(key_str); + key_str = NULL; + } + +cleanup: + free(key_str); + return ret; +} + +/** + * @brief Create a new CBOR parser context. + * + * @param[in] ctx libyang context. + * @param[in] ext Extension instance providing context for the top level element, NULL if none. + * @param[in] parse_opts Parse options, see @ref dataparseroptions. + * @param[in] val_opts Validation options, see @ref datavalidationoptions. + * @param[in] format CBOR format variant (named or SID). + * @param[out] lydctx_p Pointer to the created CBOR parser context. + * @return LY_ERR value. + */ +static LY_ERR +lydcbor_ctx_init(const struct ly_ctx *ctx, struct ly_in *in, uint32_t parse_opts, + uint32_t val_opts, struct lyd_cbor_ctx **lydctx_p) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_cbor_ctx *lydctx = NULL; + + assert(lydctx_p); + + /* Initialize context with calloc to ensure all fields are zero */ + lydctx = calloc(1, sizeof *lydctx); + LY_CHECK_ERR_RET(!lydctx, LOGMEM(ctx), LY_EMEM); + lydctx->parse_opts = parse_opts; + lydctx->val_opts = val_opts; + lydctx->free = lyd_cbor_ctx_free; + + /* Create low-level CBOR context */ + LY_CHECK_GOTO(ret = lycbor_ctx_new(ctx, in, &lydctx->cborctx), cleanup); + + *lydctx_p = lydctx; + return ret; + +cleanup: + if (lydctx) + { + lyd_cbor_ctx_free((struct lyd_ctx *)lydctx); + } + return ret; +} + +LY_ERR +lyd_parse_cbor(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, struct lyd_node *parent, + 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) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_cbor_ctx *lydctx = NULL; + cbor_item_t *cbor_data = NULL; + struct cbor_load_result result = {0}; + + /* Initialize context */ + LY_CHECK_GOTO(ret = lydcbor_ctx_init(ctx, in, parse_opts, val_opts, &lydctx), cleanup); + + lydctx->int_opts = int_opts; + lydctx->ext = ext; + + /* + * Loads CBOR data from the current input buffer. + * + * Parameters: + * in->current - Pointer to the current position in the input buffer. + * in->length - Length of the data to be loaded. + * &result - Pointer to a variable where the result status will be stored. + * + * Returns: + * cbor_data - Pointer to the loaded CBOR data structure, or NULL on failure. + */ + /* need to convert in->current from const char* to cbor_data type */ + cbor_data = cbor_load(in->current, in->length, &result); + lydctx->cborctx->cbor_data = cbor_data; + + if (!cbor_data) + { + LOGVAL(ctx, LYVE_SYNTAX, "Failed to parse CBOR data: no data returned from cbor_load()."); + ret = LY_EVALID; + goto cleanup; + } + if (result.error.code != CBOR_ERR_NONE) + { + LOGVAL(ctx, LYVE_SYNTAX, "Failed to parse CBOR data: parsing error (code %d).", result.error.code); + ret = LY_EVALID; + goto cleanup; + } + + /* Probably need to check if the obtained data is a operational node and + then write functions to parse them accordingly. If not then continue below */ + + /* Parse the CBOR structure */ + ret = lydcbor_parse_subtree(lydctx, parent, first_p, parsed, cbor_data); + +cleanup: + if (cbor_data) + { + cbor_decref(&cbor_data); + } + + if (ret) + { + if (lydctx) + { lyd_cbor_ctx_free((struct lyd_ctx *)lydctx); lydctx = NULL; } diff --git a/src/parser_cbor.h b/src/parser_cbor.h deleted file mode 100644 index 5b6b0dc94..000000000 --- a/src/parser_cbor.h +++ /dev/null @@ -1,279 +0,0 @@ -/** - * @file parser_cbor.h - * @author - * @brief CBOR data parser for libyang - * - * Copyright (c) 2020 - 2023 CESNET, z.s.p.o. - * - * This source code is licensed under BSD 3-Clause License (the "License"). - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://opensource.org/licenses/BSD-3-Clause - */ - -#ifndef LY_PARSER_CBOR_H_ -#define LY_PARSER_CBOR_H_ - -#ifdef ENABLE_CBOR_SUPPORT - -#include -#include -#include - -#include "log.h" -#include "tree_data.h" -#include "parser_internal.h" -#include "set.h" - -struct ly_ctx; -struct ly_in; -struct ly_out; -struct lyd_ctx; -struct lyd_node; -struct ly_set; -struct lysc_ext_instance; -struct lysc_node; - -/** - * @brief CBOR format variants for different encoding schemes - */ -enum lyd_cbor_format -{ - LYD_CBOR_NAMED, /**< CBOR with named identifiers (JSON-like) */ - LYD_CBOR_SID /**< CBOR with Schema Item identifiers (future implementation) */ -}; - -struct lycbor_ctx { - const struct ly_ctx *ctx; /**< libyang context */ - struct ly_in *in; /**< input structure */ - cbor_item_t *cbor_data; /**< parsed CBOR data */ - enum lyd_cbor_format format; /**< CBOR format variant */ - uint32_t parse_opts; /**< parser options */ - uint32_t val_opts; /**< validation options */ -}; - -/** - * @brief Internal context for CBOR YANG data parser. - * - * This structure extends the basic lyd_ctx pattern used throughout libyang - * and provides CBOR-specific parsing state and configuration. - */ -struct lyd_cbor_ctx -{ - const struct lysc_ext_instance *ext; /**< extension instance possibly changing document root context, NULL if none */ - uint32_t parse_opts; /**< various @ref dataparseroptions. */ - uint32_t val_opts; /**< various @ref datavalidationoptions. */ - uint32_t int_opts; /**< internal parser options */ - uint32_t path_len; /**< used bytes in the path buffer */ - char path[LYD_PARSER_BUFSIZE]; /**< buffer for the generated path */ - struct ly_set node_when; /**< set of nodes with "when" conditions */ - struct ly_set node_types; /**< set of nodes with unresolved types */ - struct ly_set meta_types; /**< set of metadata with unresolved types */ - struct ly_set ext_node; /**< set of nodes with extension instances to validate */ - struct ly_set ext_val; /**< set of nested extension data to validate */ - struct lyd_node *op_node; /**< if an operation is being parsed, its node */ - const struct lys_module *val_getnext_ht_mod; - struct ly_ht *val_getnext_ht; - - /* callbacks */ - lyd_ctx_free_clb free; /**< destructor */ - - struct lycbor_ctx *cborctx; /**< CBOR context for low-level operations */ - - /* CBOR-specific members */ - enum lyd_cbor_format format; /**< CBOR format being parsed */ -}; - -/** - * @brief Create new CBOR context for parsing. - * - * @param[in] ctx libyang context. - * @param[in] in Input handler. - * @param[out] cbor_ctx_p Pointer to store the created CBOR context. - * @return LY_ERR value. - */ -LY_ERR -lycbor_ctx_new(const struct ly_ctx *ctx, struct ly_in *in, struct lycbor_ctx **cbor_ctx_p); - -/** - * @brief Free CBOR context. - * - * @param[in] cbor_ctx CBOR context to free. - */ -void -lycbor_ctx_free(struct lycbor_ctx *cbor_ctx); - -/** - * @brief Parse CBOR data into libyang data tree. - * - * This function mirrors the signature and behavior of lyd_parse_json() but handles - * CBOR input instead. It supports both named identifier and SID formats. - * - * @param[in] ctx libyang context. - * @param[in] ext Optional extension instance to parse data following the schema tree specified in the extension instance - * @param[in] parent Parent to connect the parsed nodes to, if any. - * @param[in,out] first_p Pointer to the first top-level parsed node, used only if @p parent is NULL. - * @param[in] in Input structure to read from. - * @param[in] parse_opts Options for parser, see @ref dataparseroptions. - * @param[in] val_opts Options for the validation phase, see @ref datavalidationoptions. - * @param[in] int_opts Internal data parser options. - * @param[out] parsed Set to add all the parsed siblings into. - * @param[out] subtree_sibling Set if ::LYD_PARSE_SUBTREE is used and another subtree is following in @p in. - * @param[out] lydctx_p Data parser context to finish validation. - * @return LY_ERR value. - */ -LY_ERR lyd_parse_cbor(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, struct lyd_node *parent, - 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); - -/** - * @brief Parse CBOR data from memory into libyang data tree. - * - * Convenience function for parsing CBOR data directly from memory buffer. - * - * @param[in] ctx libyang context. - * @param[in] data CBOR data to parse. - * @param[in] data_len Length of @p data. - * @param[in] format CBOR format variant (named or SID). - * @param[in] parse_opts Parse options, see @ref dataparseroptions. - * @param[in] val_opts Validation options, see @ref datavalidationoptions. - * @param[out] tree Parsed data tree. - * @return LY_ERR value. - */ -LIBYANG_API_DECL LY_ERR lyd_parse_cbor_data(const struct ly_ctx *ctx, const char *data, size_t data_len, - enum lyd_cbor_format format, uint32_t parse_opts, uint32_t val_opts, struct lyd_node **tree); - -/** - * @brief Print libyang data tree as CBOR. - * - * @param[in] root Root node of the data tree to print. - * @param[in] format CBOR format variant to use for output. - * @param[in] out Output structure to write to. - * @param[in] options Print options. - * @return LY_ERR value. - */ -LIBYANG_API_DECL LY_ERR lyd_print_cbor_data(const struct lyd_node *root, enum lyd_cbor_format format, - struct ly_out *out, uint32_t options); - -/* Internal functions (used by parser_data.c and other libyang components) */ - -/** - * @brief Detect CBOR format variant from input data. - * - * @param[in] in Input structure to analyze. - * @param[out] format Detected format. - * @return LY_ERR value. - */ -LY_ERR lydcbor_detect_format(struct ly_in *in, enum lyd_cbor_format *format); - -/** - * @brief Parse a single CBOR value according to schema node. - * - * @param[in] lydctx CBOR parser context. - * @param[in] snode Schema node for the value. - * @param[in] cbor_item CBOR item to parse. - * @param[out] node Created data node. - * @return LY_ERR value. - */ -LY_ERR lydcbor_parse_value(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, - const void *cbor_item, struct lyd_node **node); - -/** - * @brief Parse CBOR metadata/attributes. - * - * @param[in] lydctx CBOR parser context. - * @param[in] cbor_item CBOR item containing metadata. - * @param[in,out] node Data node to attach metadata to. - * @return LY_ERR value. - */ -LY_ERR lydcbor_parse_metadata(struct lyd_cbor_ctx *lydctx, const void *cbor_item, struct lyd_node *node); - -/** - * @brief Create a new CBOR parser context. - * - * @param[in] ctx libyang context. - * @param[in] ext Extension instance providing context for the top level element, NULL if none. - * @param[in] parse_opts Parse options, see @ref dataparseroptions. - * @param[in] val_opts Validation options, see @ref datavalidationoptions. - * @param[in] format CBOR format variant (named or SID). - * @param[out] lydctx_p Pointer to the created CBOR parser context. - * @return LY_ERR value. - */ -LY_ERR lydcbor_ctx_init(const struct ly_ctx *ctx, struct ly_in *in, - uint32_t parse_opts, uint32_t val_opts, enum lyd_cbor_format format, - struct lyd_cbor_ctx **lydctx_p); - - /* Add these declarations to parser_cbor.h or at the top of parser_cbor.c */ - -static LY_ERR -lydcbor_parse_leaflist_array(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, - const cbor_item_t *array_item, struct lyd_node **first_p, struct ly_set *parsed); - - -static LY_ERR -lydcbor_parse_list_array(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, - const cbor_item_t *array_item, struct lyd_node **first_p, struct ly_set *parsed); - -/** - * @brief Parse a CBOR container recursive - */ -static LY_ERR -lydcbor_parse_subtree(struct lyd_cbor_ctx *lydctx, struct lyd_node *parent, - struct lyd_node **first_p, struct ly_set *parsed, const cbor_item_t *cbor_obj); -static LY_ERR -lydcbor_parse_any(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, - const cbor_item_t *cbor_value, struct lyd_node **first_p, struct ly_set *parsed); - -static LY_ERR -lydcbor_parse_list(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, - const cbor_item_t *cbor_value, struct lyd_node **first_p, struct ly_set *parsed); - - -static LY_ERR -lydcbor_parse_container(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, - const cbor_item_t *cbor_value, struct lyd_node **first_p, struct ly_set *parsed); - -static LY_ERR -lydcbor_parse_terminal(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, - const cbor_item_t *cbor_value, struct lyd_node **first_p, struct ly_set *parsed); -static LY_ERR -lydcbor_parse_node_value(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, - struct lyd_node **node, const cbor_item_t *cbor_value); - - -/** - * @brief Get schema node from CBOR node name, following lydjson_get_snode logic. - * - * @param[in] lydctx CBOR parser context. - * @param[in] name Node name. - * @param[in] name_len Length of node name. - * @param[in] parent Data parent node. - * @param[out] snode Schema node found. - * @param[out] ext Extension instance if found. - * @return LY_ERR value. - */ -static LY_ERR -lydcbor_get_snode(struct lyd_cbor_ctx *lydctx, const char *name, size_t name_len, - struct lyd_node *parent, const struct lysc_node **snode, - const struct lysc_ext_instance **ext); - - -/** - * @brief Get module prefix from a qualified name. - * - * @param[in] qname Qualified name (prefix:name or just name). - * @param[in] qname_len Length of the qualified name. - * @param[out] prefix Extracted prefix (points into qname, not allocated). - * @param[out] prefix_len Length of the prefix. - * @param[out] name Local name (points into qname, not allocated). - * @param[out] name_len Length of the local name. - * @return LY_SUCCESS on success. - */ -static LY_ERR -lydcbor_parse_qname(const char *qname, size_t qname_len, const char **prefix, size_t *prefix_len, - const char **name, size_t *name_len); - -#endif /* ENABLE_CBOR_SUPPORT */ - -#endif /* LY_PARSER_CBOR_H_ */ \ No newline at end of file diff --git a/src/parser_internal.h b/src/parser_internal.h index 4068d0ad3..e22209e4c 100644 --- a/src/parser_internal.h +++ b/src/parser_internal.h @@ -179,6 +179,34 @@ struct lyd_lyb_ctx { struct lylyb_ctx *lybctx; /* LYB context */ }; +#ifdef ENABLE_CBOR_SUPPORT +/** + * @brief Internal context for CBOR data parser. + */ +struct lyd_cbor_ctx +{ + const struct lysc_ext_instance *ext; /**< extension instance possibly changing document root context, NULL if none */ + uint32_t parse_opts; /**< various @ref dataparseroptions. */ + uint32_t val_opts; /**< various @ref datavalidationoptions. */ + uint32_t int_opts; /**< internal parser options */ + uint32_t path_len; /**< used bytes in the path buffer */ + char path[LYD_PARSER_BUFSIZE]; /**< buffer for the generated path */ + struct ly_set node_when; /**< set of nodes with "when" conditions */ + struct ly_set node_types; /**< set of nodes with unresolved types */ + struct ly_set meta_types; /**< set of metadata with unresolved types */ + struct ly_set ext_node; /**< set of nodes with extension instances to validate */ + struct ly_set ext_val; /**< set of nested extension data to validate */ + struct lyd_node *op_node; /**< if an operation is being parsed, its node */ + const struct lys_module *val_getnext_ht_mod; + struct ly_ht *val_getnext_ht; + + /* callbacks */ + lyd_ctx_free_clb free; /**< destructor */ + + struct lycbor_ctx *cborctx; /**< CBOR context for low-level operations */ +}; +#endif /* ENABLE_CBOR_SUPPORT */ + /** * @brief Parsed extension instance data to validate. */ @@ -348,6 +376,32 @@ LY_ERR lyd_parse_lyb(const struct ly_ctx *ctx, const struct lysc_ext_instance *e 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 +/** + * @brief Parse CBOR data into libyang data tree. + * + * This function mirrors the signature and behavior of lyd_parse_json() but handles + * CBOR input instead. It supports both named identifier and SID formats. + * + * @param[in] ctx libyang context. + * @param[in] ext Optional extension instance to parse data following the schema tree specified in the extension instance + * @param[in] parent Parent to connect the parsed nodes to, if any. + * @param[in,out] first_p Pointer to the first top-level parsed node, used only if @p parent is NULL. + * @param[in] in Input structure to read from. + * @param[in] parse_opts Options for parser, see @ref dataparseroptions. + * @param[in] val_opts Options for the validation phase, see @ref datavalidationoptions. + * @param[in] int_opts Internal data parser options. + * @param[out] parsed Set to add all the parsed siblings into. + * @param[out] subtree_sibling Set if ::LYD_PARSE_SUBTREE is used and another subtree is following in @p in. + * @param[out] lydctx_p Data parser context to finish validation. + * @return LY_ERR value. + */ +LY_ERR lyd_parse_cbor(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, struct lyd_node *parent, + 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); + +#endif /* ENABLE_CBOR_SUPPORT */ + /** * @brief Validate eventTime date-and-time value. * diff --git a/src/tree_data.c b/src/tree_data.c index a397f28ef..7e1cf57d1 100644 --- a/src/tree_data.c +++ b/src/tree_data.c @@ -35,7 +35,6 @@ #include "in_internal.h" #include "log.h" #include "ly_common.h" -#include "parser_cbor.h" #include "parser_data.h" #include "parser_internal.h" #include "path.h" From 8a411921e135dfc1e728f48f7ff34c02ce800b29 Mon Sep 17 00:00:00 2001 From: MeherRushi Date: Fri, 25 Jul 2025 11:07:59 +0530 Subject: [PATCH 6/9] printer_cbor.c setup (nonfunctional) and function renaming parser_cbor.c - made small changes to printer_cbor.c - and made small function renaming in parser_cbor --- CMakeLists.txt | 8 +- src/parser_cbor.c | 131 ++++++- src/parser_data.h | 5 + src/printer_cbor.c | 782 +++++++++++++++++++++++++++++++++++++++++ src/printer_data.c | 2 +- src/printer_internal.h | 10 + 6 files changed, 920 insertions(+), 18 deletions(-) create mode 100644 src/printer_cbor.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 7baf3d1d5..8792c7a80 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -327,10 +327,10 @@ if(ENABLE_CBOR_SUPPORT) add_definitions(-DENABLE_CBOR_SUPPORT) include_directories(${LIBCBOR_INCLUDE_DIRS}) # Add CBOR parser files to the library sources - list(APPEND libsrc src/parser_cbor.c src/lcbor.c) + list(APPEND libsrc src/parser_cbor.c src/lcbor.c src/printer_cbor.c) list(APPEND headers src/lcbor.h) # Add CBOR files to format sources - list(APPEND format_sources src/parser_cbor.c src/lcbor.h src/lcbor.c) + list(APPEND format_sources src/parser_cbor.c src/lcbor.h src/lcbor.c src/printer_cbor.c) else() message(FATAL_ERROR "libcbor not found! Please install libcbor development package or disable CBOR support with -DENABLE_CBOR_SUPPORT=OFF") endif() @@ -344,10 +344,10 @@ if(ENABLE_CBOR_SUPPORT) include_directories(${LIBCBOR_INCLUDE_DIR}) set(LIBCBOR_LIBRARIES ${LIBCBOR_LIBRARY}) # Add CBOR parser files to the library sources - list(APPEND libsrc src/parser_cbor.c src/lcbor.c) + list(APPEND libsrc src/parser_cbor.c src/lcbor.c src/printer_cbor.c) list(APPEND headers src/lcbor.h) # Add CBOR files to format sources - list(APPEND format_sources src/parser_cbor.c src/lcbor.h src/lcbor.c) + list(APPEND format_sources src/parser_cbor.c src/lcbor.h src/lcbor.c src/printer_cbor.c) else() message(FATAL_ERROR "libcbor not found! Please install libcbor development package or disable CBOR support with -DENABLE_CBOR_SUPPORT=OFF") endif() diff --git a/src/parser_cbor.c b/src/parser_cbor.c index bcc12d2aa..1362c6033 100644 --- a/src/parser_cbor.c +++ b/src/parser_cbor.c @@ -41,7 +41,6 @@ #include #include - void print_json(cbor_item_t *item); void print_json_string(const cbor_item_t *item) @@ -51,6 +50,22 @@ void print_json_string(const cbor_item_t *item) printf("\"%.*s\"", (int)length, str); } +void print_json_array(const cbor_item_t *item) +{ + printf("["); + size_t size = cbor_array_size(item); + cbor_item_t **handle = cbor_array_handle(item); + + for (size_t i = 0; i < size; ++i) + { + print_json(handle[i]); + if (i < size - 1) + printf(", "); + } + + printf("]"); +} + void print_json_map(const cbor_item_t *item) { printf("{"); @@ -69,36 +84,92 @@ void print_json_map(const cbor_item_t *item) printf("}"); } +void print_json_number(const cbor_item_t *item) +{ + if (cbor_isa_uint(item)) { + printf("%lu", cbor_get_uint64(item)); + } else if (cbor_isa_negint(item)) { + printf("-%lu", cbor_get_uint64(item) + 1); + } else if (cbor_isa_float_ctrl(item)) { + if (cbor_float_get_width(item) == CBOR_FLOAT_64) { + printf("%f", cbor_float_get_float8(item)); + } else if (cbor_float_get_width(item) == CBOR_FLOAT_32) { + printf("%f", cbor_float_get_float4(item)); + } else if (cbor_float_get_width(item) == CBOR_FLOAT_16) { + printf("%f", cbor_float_get_float2(item)); + } + } +} + void print_json_bool(const cbor_item_t *item) { printf(cbor_is_bool(item) && cbor_ctrl_value(item) ? "true" : "false"); } +void print_json_null(const cbor_item_t *item) +{ + printf("null"); +} + void print_json(cbor_item_t *item) { + if (!item) { + printf("null"); + return; + } + if (cbor_isa_map(item)) { print_json_map(item); } + else if (cbor_isa_array(item)) + { + print_json_array(item); + } else if (cbor_isa_string(item)) { print_json_string(item); } + else if (cbor_isa_uint(item) || cbor_isa_negint(item)) + { + print_json_number(item); + } + else if (cbor_isa_float_ctrl(item)) + { + // Check if it's a control value (null, undefined, true, false) + if (cbor_float_get_width(item) == CBOR_FLOAT_0) { + uint8_t ctrl = cbor_ctrl_value(item); + if (ctrl == 20) { + printf("false"); + } else if (ctrl == 21) { + printf("true"); + } else if (ctrl == 22) { + printf("null"); + } else if (ctrl == 23) { + printf("undefined"); + } else { + printf("null"); // unknown control value + } + } else { + print_json_number(item); + } + } else if (cbor_is_bool(item)) { print_json_bool(item); } else { - printf("null"); // fallback for unsupported types + printf("null"); // fallback for truly unsupported types } } -static LY_ERR lydcbor_parse_subtree(struct lyd_cbor_ctx *lydctx, struct lyd_node *parent, +static LY_ERR lydcbor_subtree_r(struct lyd_cbor_ctx *lydctx, struct lyd_node *parent, struct lyd_node **first_p, struct ly_set *parsed, const cbor_item_t *cbor_obj); /** - * @brief Free the CBOR parser context + * @brief Free the CBOR data parser context + * CBOR implementation of lyd_ctx_free_clb(). * * @param[in] lydctx Data parser context to free. */ @@ -372,6 +443,7 @@ lydcbor_parse_terminal(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snod return LY_EVALID; } + lyd_hash(node); /* Insert into tree */ ret = lyd_insert_sibling(*first_p, node, first_p); if (ret) @@ -400,6 +472,7 @@ lydcbor_parse_container(struct lyd_cbor_ctx *lydctx, const struct lysc_node *sno return LY_EVALID; } + lyd_hash(node); /* Insert into tree first */ ret = lyd_insert_sibling(*first_p, node, first_p); if (ret) @@ -416,7 +489,7 @@ lydcbor_parse_container(struct lyd_cbor_ctx *lydctx, const struct lysc_node *sno if (cbor_isa_map(cbor_value) && cbor_map_size(cbor_value) > 0) { struct lyd_node *child_first = NULL; - ret = lydcbor_parse_subtree(lydctx, node, &child_first, parsed, cbor_value); + ret = lydcbor_subtree_r(lydctx, node, &child_first, parsed, cbor_value); if (ret) { return ret; @@ -432,6 +505,27 @@ lydcbor_parse_container(struct lyd_cbor_ctx *lydctx, const struct lysc_node *sno return LY_SUCCESS; } +/* Helper function to check if CBOR item is null/undefined */ +static ly_bool +lydcbor_is_null(const cbor_item_t *item) +{ + if (!item) { + return 1; + } + + /* Check for CBOR null primitive */ + if (cbor_isa_float_ctrl(item)) { + if (cbor_float_get_width(item) == CBOR_FLOAT_0) { + uint8_t ctrl = cbor_ctrl_value(item); + if (ctrl == 22 || ctrl == 23) { /* null or undefined */ + return 1; + } + } + } + + return 0; +} + static LY_ERR lydcbor_parse_list_array(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, const cbor_item_t *array_item, struct lyd_node **first_p, struct ly_set *parsed) @@ -491,7 +585,7 @@ lydcbor_parse_list_array(struct lyd_cbor_ctx *lydctx, const struct lysc_node *sn ret = LY_EVALID; goto cleanup; } - + lyd_hash(node); /* Insert the list node */ ret = lyd_insert_sibling(*first_p, node, first_p); LY_CHECK_GOTO(ret, cleanup); @@ -501,7 +595,7 @@ lydcbor_parse_list_array(struct lyd_cbor_ctx *lydctx, const struct lysc_node *sn /* Parse list entry content */ struct lyd_node *child_first = NULL; - ret = lydcbor_parse_subtree(lydctx, node, &child_first, parsed, item); + ret = lydcbor_subtree_r(lydctx, node, &child_first, parsed, item); LY_CHECK_GOTO(ret, cleanup); /* Link children to list entry */ @@ -529,7 +623,6 @@ lydcbor_parse_list(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, if (cbor_isa_array(cbor_value)) { - /* Array of list entries */ ret = lydcbor_parse_list_array(lydctx, snode, cbor_value, first_p, parsed); } else if (cbor_isa_map(cbor_value)) @@ -540,6 +633,7 @@ lydcbor_parse_list(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, ret = lyd_create_inner(snode, &node); LY_CHECK_RET(ret); + lyd_hash(node); /* Insert into tree */ ret = lyd_insert_sibling(*first_p, node, first_p); if (ret) @@ -554,7 +648,7 @@ lydcbor_parse_list(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, /* Parse list entry content */ struct lyd_node *child_first = NULL; - ret = lydcbor_parse_subtree(lydctx, node, &child_first, parsed, cbor_value); + ret = lydcbor_subtree_r(lydctx, node, &child_first, parsed, cbor_value); if (ret) { return ret; @@ -591,6 +685,7 @@ lydcbor_parse_any(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, return LY_EVALID; } + lyd_hash(node); /* Insert into tree */ ret = lyd_insert_sibling(*first_p, node, first_p); if (ret) @@ -927,7 +1022,8 @@ lydcbor_parse_leaflist_array(struct lyd_cbor_ctx *lydctx, const struct lysc_node ret = LY_EVALID; goto cleanup; } - + + lyd_hash(node); /* Insert the node */ ret = lyd_insert_sibling(*first_p, node, first_p); LY_CHECK_GOTO(ret, cleanup); @@ -946,12 +1042,12 @@ lydcbor_parse_leaflist_array(struct lyd_cbor_ctx *lydctx, const struct lysc_node } static LY_ERR -lydcbor_parse_subtree(struct lyd_cbor_ctx *lydctx, struct lyd_node *parent, +lydcbor_subtree_r(struct lyd_cbor_ctx *lydctx, struct lyd_node *parent, struct lyd_node **first_p, struct ly_set *parsed, const cbor_item_t *cbor_obj) { LY_ERR ret = LY_SUCCESS; - printf("Entering lydcbor_parse_subtree\n"); + printf("Entering lydcbor_subtree_r\n"); printf("CBOR object:\n"); print_json(cbor_obj); printf("\n"); @@ -992,6 +1088,11 @@ lydcbor_parse_subtree(struct lyd_cbor_ctx *lydctx, struct lyd_node *parent, goto cleanup; } + if (lydcbor_is_null(value_item)) { + // Skip null values - don't create any nodes + continue; // or return LY_SUCCESS depending on your loop structure + } + /* Get key string */ LY_CHECK_GOTO(ret = lydcbor_get_key_string(lydctx, key_item, &key_str, &key_len), cleanup); @@ -1099,6 +1200,10 @@ lyd_parse_cbor(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, st lydctx->int_opts = int_opts; lydctx->ext = ext; + /* find the operation node if it exists already */ + LY_CHECK_GOTO(ret = lyd_parser_find_operation(parent, int_opts, &lydctx->op_node), cleanup); + + /* * Loads CBOR data from the current input buffer. * @@ -1131,7 +1236,7 @@ lyd_parse_cbor(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, st then write functions to parse them accordingly. If not then continue below */ /* Parse the CBOR structure */ - ret = lydcbor_parse_subtree(lydctx, parent, first_p, parsed, cbor_data); + ret = lydcbor_subtree_r(lydctx, parent, first_p, parsed, cbor_data); cleanup: if (cbor_data) diff --git a/src/parser_data.h b/src/parser_data.h index a4b420faf..f6cb3f2a9 100644 --- a/src/parser_data.h +++ b/src/parser_data.h @@ -40,6 +40,11 @@ struct ly_in; * can be found in [RFC 7951](http://tools.ietf.org/html/rfc7951). The specification does not cover RPCs, actions and * Notifications, so the representation of these data trees is proprietary and corresponds to the representation of these * trees in XML. + * + * - CBOR + * + * The reference documentation would be `Encoding of Data Modeled with YANG in the Concise Binary Object + * Representation (CBOR)` : [RFC 9254](https://datatracker.ietf.org/doc/html/rfc9254) * * While the parsers themselves process the input data only syntactically, all the parser functions actually incorporate * the [common validator](@ref howtoDataValidation) checking the input data semantically. Therefore, the parser functions diff --git a/src/printer_cbor.c b/src/printer_cbor.c new file mode 100644 index 000000000..d0cc0997a --- /dev/null +++ b/src/printer_cbor.c @@ -0,0 +1,782 @@ +/** + * @file printer_cbor.c + * @author Meher Rushi + * @brief CBOR printer for libyang data tree using libcbor + * + * Copyright (c) 2024 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#include "printer_data.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "context.h" +#include "log.h" +#include "ly_common.h" +#include "out.h" +#include "plugins_exts.h" +#include "printer_internal.h" +#include "set.h" +#include "tree_data.h" +#include "tree_schema.h" + +/** + * @brief CBOR printer context + */ +struct cborpr_ctx { + struct ly_out *out; /**< output structure */ + const struct lyd_node *root; /**< root node of the subtree being printed */ + const struct lyd_node *print_sibling_metadata; /**< node with metadata supposed to be printed */ + ly_bool simple_status; /**< flag for simple status */ + + uint16_t level; /**< current nesting level */ + uint32_t options; /**< [printer flags](@ref dataprinterflags) */ + const struct ly_ctx *ctx; /**< libyang context */ + + struct ly_set prefix; /**< printed module prefixes */ + uint32_t array_index; /**< index in array if we are printing an array element */ + + cbor_item_t *root_item; /**< root CBOR item */ +}; + +/** + * @brief Check if module needs prefix - ROBUST VERSION + */ +static ly_bool +cbor_module_needs_prefix(struct cborpr_ctx *ctx, const struct lys_module *module) +{ + /* CRITICAL: Add comprehensive null checks */ + if (!ctx) { + fprintf(stderr, "DEBUG: cbor_module_needs_prefix called with NULL ctx\n"); + return 0; + } + + if (!module) { + fprintf(stderr, "DEBUG: cbor_module_needs_prefix called with NULL module\n"); + return 0; + } + + if (!module->name) { + fprintf(stderr, "DEBUG: Module has NULL name\n"); + return 0; + } + + fprintf(stderr, "DEBUG: Checking prefix for module: '%s'\n", module->name); + + /* Always add prefix if explicitly requested */ + if (ctx->options & LYD_PRINT_WD_ALL_TAG) { + fprintf(stderr, "DEBUG: Prefix requested via options\n"); + return 1; + } + + /* Check if it's an internal libyang module */ + if (!strcmp(module->name, "ietf-yang-metadata") || + !strcmp(module->name, "yang") || + !strcmp(module->name, "ietf-inet-types") || + !strcmp(module->name, "ietf-yang-types") || + !strcmp(module->name, "ietf-yang-structure-ext")) { + fprintf(stderr, "DEBUG: Internal module, no prefix needed\n"); + return 0; + } + + /* For now, don't add prefixes unless explicitly requested */ + fprintf(stderr, "DEBUG: No prefix needed for module: '%s'\n", module->name); + return 0; +} + + +/** + * @brief Safe wrapper for cbor_build_string that handles NULL inputs - ENHANCED + */ +static cbor_item_t * +safe_cbor_build_string(const char *str) +{ + if (!str) { + fprintf(stderr, "DEBUG: NULL string passed to safe_cbor_build_string, using empty string\n"); + return cbor_build_string(""); + } + + fprintf(stderr, "DEBUG: Building CBOR string: '%s' (len=%zu)\n", str, strlen(str)); + cbor_item_t *item = cbor_build_string(str); + if (!item) { + fprintf(stderr, "DEBUG: cbor_build_string failed for '%s'\n", str); + } + return item; +} + +/** + * @brief Convert YANG value to CBOR item + */ +static cbor_item_t * +cbor_value_to_item(const struct lyd_node *node) +{ + const char *str; + cbor_item_t *item = NULL; + + if (!node) { + fprintf(stderr, "DEBUG: cbor_value_to_item called with NULL node\n"); + return safe_cbor_build_string(""); + } + + fprintf(stderr, "DEBUG: cbor_value_to_item - node type: %d\n", node->schema->nodetype); + + if (!(node->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + fprintf(stderr, "DEBUG: Non-leaf node, returning empty string\n"); + return safe_cbor_build_string(""); /* Empty string for non-leaf nodes */ + } + + str = lyd_get_value(node); + fprintf(stderr, "DEBUG: Node value: '%s'\n", str ? str : "NULL"); + + if (!str || strlen(str) == 0) { + fprintf(stderr, "DEBUG: Empty or NULL value, returning empty string\n"); + return safe_cbor_build_string(""); + } + + /* FIXED: Add null check for schema before casting */ + if (!node->schema) { + fprintf(stderr, "DEBUG: Node has NULL schema, using string value\n"); + return cbor_build_string(str); + } + + /* Handle different data types based on YANG type */ + switch (((struct lysc_node_leaf *)node->schema)->type->basetype) { + case LY_TYPE_BOOL: + if (strcmp(str, "true") == 0) { + item = cbor_build_bool(true); + } else { + item = cbor_build_bool(false); + } + break; + + case LY_TYPE_INT8: + case LY_TYPE_INT16: + case LY_TYPE_INT32: + case LY_TYPE_INT64: { + char *endptr; + long long num = strtoll(str, &endptr, 10); + if (*endptr == '\0') { + if (num >= 0) { + item = cbor_build_uint64((uint64_t)num); + } else { + item = cbor_build_negint64((uint64_t)(-num - 1)); + } + } else { + item = cbor_build_string(str); + } + break; + } + + case LY_TYPE_UINT8: + case LY_TYPE_UINT16: + case LY_TYPE_UINT32: + case LY_TYPE_UINT64: { + char *endptr; + unsigned long long num = strtoull(str, &endptr, 10); + if (*endptr == '\0') { + item = cbor_build_uint64(num); + } else { + item = cbor_build_string(str); + } + break; + } + + case LY_TYPE_DEC64: { + char *endptr; + double num = strtod(str, &endptr); + if (*endptr == '\0') { + item = cbor_build_float8(num); + } else { + item = cbor_build_string(str); + } + break; + } + + case LY_TYPE_EMPTY: + item = cbor_build_string(""); + break; + + default: + /* String types and others */ + item = cbor_build_string(str); + break; + } + + return item; +} + +/** + * @brief Count direct children of a node + */ +static size_t +cbor_count_children(const struct lyd_node *node) +{ + size_t count = 0; + const struct lyd_node *child; + + LY_LIST_FOR(lyd_child(node), child) { + count++; + } + return count; +} + +/** + * @brief Count sibling nodes with the same name (for leaf-lists) + */ +static size_t +cbor_count_siblings_same_name(const struct lyd_node *node) +{ + size_t count = 0; + const struct lyd_node *sibling; + const struct lysc_node *schema = node->schema; + + /* Check if this is a leaf-list */ + if (!(schema->nodetype & LYS_LEAFLIST)) { + return 1; + } + + /* Count siblings with same schema */ + LY_LIST_FOR(node, sibling) { + if (sibling->schema == schema) { + count++; + } else { + break; /* leaf-list instances are consecutive */ + } + } + + return count; +} + +/** + * @brief Print a single node recursively + */ +static LY_ERR cbor_print_node(struct cborpr_ctx *ctx, const struct lyd_node *node, cbor_item_t *parent_map); + +/** + * @brief Print container or list node - EXTRA SAFE VERSION + */ +static LY_ERR +cbor_print_container(struct cborpr_ctx *ctx, const struct lyd_node *node, cbor_item_t *parent_map) +{ + cbor_item_t *node_map = NULL; + cbor_item_t *key_item = NULL; + char *node_name = NULL; + const struct lyd_node *child; + LY_ERR ret = LY_SUCCESS; + size_t child_count; + + fprintf(stderr, "DEBUG: cbor_print_container called for node: %s\n", + node && node->schema && node->schema->name ? node->schema->name : "NULL"); + + /* COMPREHENSIVE NULL CHECKS */ + if (!ctx) { + fprintf(stderr, "DEBUG: Container called with NULL ctx\n"); + return LY_EINVAL; + } + + if (!node) { + fprintf(stderr, "DEBUG: Container called with NULL node\n"); + return LY_EINVAL; + } + + if (!node->schema) { + fprintf(stderr, "DEBUG: Container node has NULL schema\n"); + return LY_EINVAL; + } + + if (!parent_map) { + fprintf(stderr, "DEBUG: Container called with NULL parent_map\n"); + return LY_EINVAL; + } + + /* Get node name - This should now be safe */ + // node_name = cbor_get_node_name(ctx, node); + node_name = ""; if (!node_name) { + fprintf(stderr, "DEBUG: Failed to get container node name\n"); + ret = LY_EMEM; + goto cleanup; + } + + fprintf(stderr, "DEBUG: Container name: '%s'\n", node_name); + + /* Count children */ + child_count = cbor_count_children(node); + fprintf(stderr, "DEBUG: Container has %zu children\n", child_count); + + /* Create map for this container/list */ + node_map = cbor_new_definite_map(child_count); + if (!node_map) { + fprintf(stderr, "DEBUG: Failed to create CBOR map for container\n"); + ret = LY_EMEM; + goto cleanup; + } + + /* Add all children to the map */ + LY_LIST_FOR(lyd_child(node), child) { + if (!child || !child->schema || !child->schema->name) { + fprintf(stderr, "DEBUG: Skipping invalid child\n"); + continue; + } + + fprintf(stderr, "DEBUG: Processing child: %s\n", child->schema->name); + ret = cbor_print_node(ctx, child, node_map); + if (ret != LY_SUCCESS) { + fprintf(stderr, "DEBUG: Failed to process child node\n"); + goto cleanup; + } + } + + /* Add this container/list to parent map */ + key_item = safe_cbor_build_string(node_name); + if (!key_item) { + fprintf(stderr, "DEBUG: Failed to create key item for container\n"); + ret = LY_EMEM; + goto cleanup; + } + + if (!cbor_map_add(parent_map, (struct cbor_pair) { + .key = key_item, + .value = node_map + })) { + fprintf(stderr, "DEBUG: Failed to add container to parent map\n"); + ret = LY_EMEM; + goto cleanup; + } + + fprintf(stderr, "DEBUG: Container added successfully\n"); + + /* Items are now owned by the map, don't decref them */ + key_item = NULL; + node_map = NULL; + +cleanup: + if (key_item) { + cbor_decref(&key_item); + } + if (node_map) { + cbor_decref(&node_map); + } + if (node_name) { + free(node_name); + } + return ret; +} + +/** + * @brief Print leaf or leaf-list node + */ +static LY_ERR +cbor_print_leaf(struct cborpr_ctx *ctx, const struct lyd_node *node, cbor_item_t *parent_map) +{ + cbor_item_t *key_item = NULL; + cbor_item_t *value_item = NULL; + cbor_item_t *array_item = NULL; + char *node_name = NULL; + const struct lyd_node *sibling; + LY_ERR ret = LY_SUCCESS; + size_t sibling_count; + + fprintf(stderr, "DEBUG: cbor_print_leaf called for node: %s\n", + node && node->schema && node->schema->name ? node->schema->name : "NULL"); + + /* FIXED: Add null checks */ + if (!node || !node->schema) { + fprintf(stderr, "DEBUG: Leaf node or schema is NULL\n"); + return LY_EINVAL; + } + + /* Get node name */ + // node_name = cbor_get_node_name(ctx, node); + node_name = ""; + if (!node_name) { + fprintf(stderr, "DEBUG: Failed to get node name\n"); + ret = LY_EMEM; + goto cleanup; + } + + fprintf(stderr, "DEBUG: Got node name: '%s'\n", node_name); + + /* Check if this is a leaf-list with multiple values */ + sibling_count = cbor_count_siblings_same_name(node); + fprintf(stderr, "DEBUG: Sibling count: %zu\n", sibling_count); + + if (sibling_count > 1 && (node->schema->nodetype & LYS_LEAFLIST)) { + fprintf(stderr, "DEBUG: Processing leaf-list with %zu values\n", sibling_count); + + /* Create array for leaf-list */ + array_item = cbor_new_definite_array(sibling_count); + if (!array_item) { + fprintf(stderr, "DEBUG: Failed to create CBOR array\n"); + ret = LY_EMEM; + goto cleanup; + } + + /* Add all values to array */ + LY_LIST_FOR(node, sibling) { + if (sibling->schema != node->schema) { + break; /* Different schema, stop */ + } + + fprintf(stderr, "DEBUG: Adding leaf-list value to array\n"); + value_item = cbor_value_to_item(sibling); + if (!value_item) { + fprintf(stderr, "DEBUG: Failed to create CBOR value item\n"); + ret = LY_EMEM; + goto cleanup; + } + + if (!cbor_array_push(array_item, value_item)) { + fprintf(stderr, "DEBUG: Failed to add item to CBOR array\n"); + cbor_decref(&value_item); + ret = LY_EMEM; + goto cleanup; + } + + value_item = NULL; /* Array owns it now */ + } + + /* Add array to parent map */ + key_item = safe_cbor_build_string(node_name); + if (!key_item) { + fprintf(stderr, "DEBUG: Failed to create CBOR key item for leaf-list\n"); + ret = LY_EMEM; + goto cleanup; + } + + if (!cbor_map_add(parent_map, (struct cbor_pair) { + .key = key_item, + .value = array_item + })) { + fprintf(stderr, "DEBUG: Failed to add leaf-list to parent map\n"); + ret = LY_EMEM; + goto cleanup; + } + + /* Items are now owned by the map */ + key_item = NULL; + array_item = NULL; + + } else { + fprintf(stderr, "DEBUG: Processing single leaf value\n"); + + /* Single leaf value */ + value_item = cbor_value_to_item(node); + if (!value_item) { + fprintf(stderr, "DEBUG: Failed to create CBOR value item for leaf\n"); + ret = LY_EMEM; + goto cleanup; + } + + key_item = safe_cbor_build_string(node_name); + if (!key_item) { + fprintf(stderr, "DEBUG: Failed to create CBOR key item for leaf\n"); + ret = LY_EMEM; + goto cleanup; + } + + if (!cbor_map_add(parent_map, (struct cbor_pair) { + .key = key_item, + .value = value_item + })) { + fprintf(stderr, "DEBUG: Failed to add leaf to parent map\n"); + ret = LY_EMEM; + goto cleanup; + } + + /* Items are now owned by the map */ + key_item = NULL; + value_item = NULL; + } + + fprintf(stderr, "DEBUG: cbor_print_leaf completed successfully\n"); + +cleanup: + if (key_item) { + cbor_decref(&key_item); + } + if (value_item) { + cbor_decref(&value_item); + } + if (array_item) { + cbor_decref(&array_item); + } + free(node_name); + return ret; +} + +/** + * @brief Print anydata/anyxml node + */ +static LY_ERR +cbor_print_any(struct cborpr_ctx *ctx, const struct lyd_node *node, cbor_item_t *parent_map) +{ + cbor_item_t *key_item = NULL; + cbor_item_t *value_item = NULL; + char *node_name = NULL; + LY_ERR ret = LY_SUCCESS; + struct lyd_node_any *any = (struct lyd_node_any *)node; + const char *value_str = ""; + + /* FIXED: Add null checks */ + if (!node || !node->schema) { + fprintf(stderr, "DEBUG: Any node or schema is NULL\n"); + return LY_EINVAL; + } + + /* Get node name */ + // node_name = cbor_get_node_name(ctx, node); + node_name = ""; if (!node_name) { + ret = LY_EMEM; + goto cleanup; + } + + /* Convert anydata to string representation for now */ + /* TODO: Could be enhanced to preserve the actual data format */ + switch (any->value_type) { + case LYD_ANYDATA_STRING: + value_str = any->value.str ? (char *)any->value.str : ""; + break; + case LYD_ANYDATA_DATATREE: + /* For now, just indicate it's a data tree */ + value_str = "[DATA TREE]"; + break; + case LYD_ANYDATA_XML: + value_str = any->value.str ? (char *)any->value.str : ""; + break; + case LYD_ANYDATA_JSON: + value_str = any->value.str ? (char *)any->value.str : ""; + break; + default: + value_str = ""; + break; + } + + value_item = cbor_build_string(value_str); + if (!value_item) { + ret = LY_EMEM; + goto cleanup; + } + + key_item = cbor_build_string(node_name); + if (!key_item) { + ret = LY_EMEM; + goto cleanup; + } + + if (!cbor_map_add(parent_map, (struct cbor_pair) { + .key = key_item, + .value = value_item + })) { + ret = LY_EMEM; + goto cleanup; + } + + /* Items are now owned by the map */ + key_item = NULL; + value_item = NULL; + +cleanup: + if (key_item) { + cbor_decref(&key_item); + } + if (value_item) { + cbor_decref(&value_item); + } + free(node_name); + return ret; +} + +/** + * @brief Print a single node recursively + */ +static LY_ERR +cbor_print_node(struct cborpr_ctx *ctx, const struct lyd_node *node, cbor_item_t *parent_map) +{ + /* FIXED: Add null checks at the beginning */ + if (!node || !node->schema) { + fprintf(stderr, "DEBUG: cbor_print_node called with NULL node or schema\n"); + return LY_EINVAL; + } + + switch (node->schema->nodetype) { + case LYS_CONTAINER: + case LYS_LIST: + return cbor_print_container(ctx, node, parent_map); + case LYS_LEAF: + case LYS_LEAFLIST: + return cbor_print_leaf(ctx, node, parent_map); + case LYS_ANYXML: + case LYS_ANYDATA: + return cbor_print_any(ctx, node, parent_map); + default: + /* Skip unknown node types */ + fprintf(stderr, "DEBUG: Skipping unknown node type: %d\n", node->schema->nodetype); + return LY_SUCCESS; + } +} + +/** + * @brief Count root level nodes, handling leaf-lists correctly + */ +static size_t +cbor_count_root_nodes(const struct lyd_node *root) +{ + size_t count = 0; + const struct lyd_node *node; + const struct lysc_node *last_schema = NULL; + + LY_LIST_FOR(root, node) { + /* FIXED: Add null check for schema */ + if (!node->schema) { + continue; + } + + /* For leaf-lists, only count the first occurrence */ + if (node->schema != last_schema) { + count++; + last_schema = node->schema; + } else if (!(node->schema->nodetype & LYS_LEAFLIST)) { + count++; + } + } + + return count; +} + +/** + * @brief Main function to print data tree in CBOR format + */ +LY_ERR +cbor_print_data(struct ly_out *out, const struct lyd_node *root, uint32_t options) +{ + LY_ERR ret = LY_SUCCESS; + struct cborpr_ctx ctx = {0}; + const struct lyd_node *node; + const struct lysc_node *last_schema = NULL; + size_t root_count; + unsigned char *cbor_data = NULL; + size_t cbor_data_len = 0; + + if (!out) { + return LY_EINVAL; + } + + /* Initialize context */ + ctx.out = out; + ctx.root = root; + ctx.options = options; + ctx.level = 0; + + if (root) { + ctx.ctx = LYD_CTX(root); + + /* Count root level nodes */ + root_count = cbor_count_root_nodes(root); + + /* Debug: Print what we're processing */ + fprintf(stderr, "DEBUG: Processing %zu root nodes\n", root_count); + + /* Create root map */ + ctx.root_item = cbor_new_definite_map(root_count); + if (!ctx.root_item) { + fprintf(stderr, "DEBUG: Failed to create root map\n"); + ret = LY_EMEM; + goto cleanup; + } + + /* Process all root nodes */ + LY_LIST_FOR(root, node) { + /* FIXED: Add null check for schema */ + if (!node->schema) { + fprintf(stderr, "DEBUG: Skipping node with NULL schema\n"); + continue; + } + + /* Skip duplicate leaf-list entries (they are handled together) */ + if ((node->schema->nodetype & LYS_LEAFLIST) && (node->schema == last_schema)) { + continue; + } + + fprintf(stderr, "DEBUG: Processing node: %s\n", node->schema->name); + + ctx.root = node; + ret = cbor_print_node(&ctx, node, ctx.root_item); + if (ret != LY_SUCCESS) { + fprintf(stderr, "DEBUG: Failed to print node: %s\n", node->schema->name); + goto cleanup; + } + + last_schema = node->schema; + + /* Break if not printing siblings */ + if (!(options & LYD_PRINT_WITHSIBLINGS)) { + break; + } + } + } else { + fprintf(stderr, "DEBUG: Empty data tree\n"); + /* Empty data tree - create empty map */ + ctx.root_item = cbor_new_definite_map(0); + if (!ctx.root_item) { + ret = LY_EMEM; + goto cleanup; + } + } + + /* Serialize CBOR to bytes */ + cbor_data_len = cbor_serialize_alloc(ctx.root_item, &cbor_data, &cbor_data_len); + if (cbor_data_len == 0 || !cbor_data) { + fprintf(stderr, "DEBUG: Failed to serialize CBOR or got 0 bytes\n"); + ret = LY_EMEM; + goto cleanup; + } + + fprintf(stderr, "DEBUG: Generated %zu bytes of CBOR data\n", cbor_data_len); + + /* Write to output using ly_print_ macro */ + ly_print_(out, "%.*s", (int)cbor_data_len, cbor_data); + +cleanup: + if (ctx.root_item) { + cbor_decref(&ctx.root_item); + } + if (cbor_data) { + free(cbor_data); + } + + return ret; +} + +/** + * @brief Print data subtree in CBOR format + */ +LY_ERR +cbor_print_tree(struct ly_out *out, const struct lyd_node *root, uint32_t options, size_t max_depth) +{ + /* For now, ignore max_depth and use the regular print function */ + /* TODO: Implement depth limiting if needed */ + (void)max_depth; + return cbor_print_data(out, root, options); +} + +/** + * @brief Print all data trees in CBOR format + */ +LY_ERR +cbor_print_all(struct ly_out *out, const struct lyd_node *root, uint32_t options) +{ + return cbor_print_data(out, root, options); +} \ No newline at end of file diff --git a/src/printer_data.c b/src/printer_data.c index bdbf58717..d4c73858b 100644 --- a/src/printer_data.c +++ b/src/printer_data.c @@ -39,7 +39,7 @@ lyd_print_(struct ly_out *out, const struct lyd_node *root, LYD_FORMAT format, u ret = lyb_print_data(out, root, options); break; case LYD_CBOR: - // ret = cbor_print_data(out, root, options); + ret = cbor_print_data(out, root, options); break; case LYD_UNKNOWN: LOGINT(root ? LYD_CTX(root) : NULL); diff --git a/src/printer_internal.h b/src/printer_internal.h index c835567d3..aa03b90f3 100644 --- a/src/printer_internal.h +++ b/src/printer_internal.h @@ -215,4 +215,14 @@ LY_ERR json_print_data(struct ly_out *out, const struct lyd_node *root, uint32_t */ LY_ERR lyb_print_data(struct ly_out *out, const struct lyd_node *root, uint32_t options); +/** + * @brief CBOR printer of YANG data. + * + * @param[in] out Output structure. + * @param[in] root The root element of the (sub)tree to print. + * @param[in] options [Data printer flags](@ref dataprinterflags). + * @return LY_ERR value, number of the printed bytes is updated in ::ly_out.printed. + */ +LY_ERR cbor_print_data(struct ly_out *out, const struct lyd_node *root, uint32_t options); + #endif /* LY_PRINTER_INTERNAL_H_ */ From 2a5e85450ca306a3672116c0e05ce7ff04124880 Mon Sep 17 00:00:00 2001 From: MeherRushi Date: Sun, 12 Oct 2025 05:05:27 +0100 Subject: [PATCH 7/9] refactor: Restructure CBOR implementation to match libyang conventions - 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 --- src/lcbor.c | 100 ++++++++++++++++++++++++++++++++------ src/lcbor.h | 18 ++++--- src/log.h | 1 + src/parser_cbor.c | 109 ++++++++++++++++-------------------------- src/parser_data.h | 3 +- src/parser_internal.h | 1 + 6 files changed, 142 insertions(+), 90 deletions(-) diff --git a/src/lcbor.c b/src/lcbor.c index e3962fa46..e92452eec 100644 --- a/src/lcbor.c +++ b/src/lcbor.c @@ -23,16 +23,71 @@ #include "log.h" #include "ly_common.h" +const char * +lycbor_token2str(enum cbor_type cbortype) +{ + switch (cbortype) { + case CBOR_TYPE_UINT: + return "unsigned integer"; + case CBOR_TYPE_NEGINT: + return "negative integer"; + case CBOR_TYPE_BYTESTRING: + return "byte string"; + case CBOR_TYPE_STRING: + return "string"; + case CBOR_TYPE_ARRAY: + return "array"; + case CBOR_TYPE_MAP: + return "map"; + case CBOR_TYPE_TAG: + return "tag"; + case CBOR_TYPE_FLOAT_CTRL: + return "decimals and special values (true, false, nil, ...)"; + } + + return ""; +} + + return "object"; + case LYJSON_OBJECT_NEXT: + return "object next"; + case LYJSON_OBJECT_CLOSED: + return "object closed"; + case LYJSON_ARRAY: + return "array"; + case LYJSON_ARRAY_NEXT: + return "array next"; + case LYJSON_ARRAY_CLOSED: + return "array closed"; + case LYJSON_OBJECT_NAME: + return "object name"; + case LYJSON_NUMBER: + return "number"; + case LYJSON_STRING: + return "string"; + case LYJSON_TRUE: + return "true"; + case LYJSON_FALSE: + return "false"; + case LYJSON_NULL: + return "null"; + case LYJSON_END: + return "end of input"; + } + + return ""; +} + /** * @brief Free CBOR context. * - * @param[in] cbor_ctx CBOR context to free. + * @param[in] cborctx CBOR context to free. */ -void lycbor_ctx_free(struct lycbor_ctx *cbor_ctx) +void lycbor_ctx_free(struct lycbor_ctx *cborctx) { - if (cbor_ctx) + if (cborctx) { - free(cbor_ctx); + free(cborctx); } } @@ -58,31 +113,48 @@ lydcbor_detect_format(struct ly_in *in, enum lyd_cbor_format *format) * * @param[in] ctx libyang context. * @param[in] in Input handler. - * @param[out] cbor_ctx_p Pointer to store the created CBOR context. + * @param[out] cborctx_p Pointer to store the created CBOR context. * @return LY_ERR value. */ LY_ERR -lycbor_ctx_new(const struct ly_ctx *ctx, struct ly_in *in, struct lycbor_ctx **cbor_ctx_p) +lycbor_ctx_new(const struct ly_ctx *ctx, struct ly_in *in, struct lycbor_ctx **cborctx_p) { /* TODO : Need to restructure error handling here */ LY_ERR ret = LY_SUCCESS; - struct lycbor_ctx *cbor_ctx; + struct lycbor_ctx *cborctx; + struct cbor_load_result result = {0}; enum lyd_cbor_format format; - assert(ctx && in && cbor_ctx_p); + assert(ctx && in && cborctx_p); /* TODO : error handling after the detect_format function call */ ret = lydcbor_detect_format(in, &format); /* Allocate and initialize CBOR context */ - cbor_ctx = calloc(1, sizeof *cbor_ctx); - LY_CHECK_ERR_RET(!cbor_ctx, LOGMEM(ctx), LY_EMEM); + cborctx = calloc(1, sizeof *cborctx); + LY_CHECK_ERR_RET(!cborctx, LOGMEM(ctx), LY_EMEM); + cborctx->ctx = ctx; + cborctx->in = in; + cborctx->format = format; + + /* load and parse CBOR data */ + cborctx->cbor_data = cbor_load(in->current, in->length, &result); + if (!cborctx->cbor_data) { + LOGVAL(ctx, LYVE_SYNTAX, "Failed to parse CBOR data."); + free(cborctx); + return LY_EVALID; + } + if (result.error.code != CBOR_ERR_NONE) { + LOGVAL(ctx, LYVE_SYNTAX, "CBOR parsing error (code %d).", result.error.code); + cbor_decref(&cborctx->cbor_data); + free(cborctx); + return LY_EVALID; + } - cbor_ctx->ctx = ctx; - cbor_ctx->in = in; - cbor_ctx->format = format; + /* input line logging */ + ly_log_location(NULL, NULL, NULL, in); - *cbor_ctx_p = cbor_ctx; + *cborctx_p = cborctx; return ret; } diff --git a/src/lcbor.h b/src/lcbor.h index 22d2fb486..838077ca8 100644 --- a/src/lcbor.h +++ b/src/lcbor.h @@ -41,10 +41,16 @@ enum lyd_cbor_format struct lycbor_ctx { const struct ly_ctx *ctx; /**< libyang context */ struct ly_in *in; /**< input structure */ + cbor_item_t *cbor_data; /**< parsed CBOR data */ + enum lyd_cbor_format format; /**< CBOR format variant */ - uint32_t parse_opts; /**< parser options */ - uint32_t val_opts; /**< validation options */ + + struct { + cbor_item_t *cbor_data; /**< parsed CBOR data */ + enum lyd_cbor_format format; /**< CBOR format variant */ + const char *input; + } backup; }; /** @@ -52,19 +58,19 @@ struct lycbor_ctx { * * @param[in] ctx libyang context. * @param[in] in Input handler. - * @param[out] cbor_ctx_p Pointer to store the created CBOR context. + * @param[out] cborctx_p Pointer to store the created CBOR context. * @return LY_ERR value. */ LY_ERR -lycbor_ctx_new(const struct ly_ctx *ctx, struct ly_in *in, struct lycbor_ctx **cbor_ctx_p); +lycbor_ctx_new(const struct ly_ctx *ctx, struct ly_in *in, struct lycbor_ctx **cborctx_p); /** * @brief Free CBOR context. * - * @param[in] cbor_ctx CBOR context to free. + * @param[in] cborctx CBOR context to free. */ void -lycbor_ctx_free(struct lycbor_ctx *cbor_ctx); +lycbor_ctx_free(struct lycbor_ctx *cborctx); #endif /* ENABLE_CBOR_SUPPORT */ diff --git a/src/log.h b/src/log.h index 85f2ac6d4..c7443b5fb 100644 --- a/src/log.h +++ b/src/log.h @@ -271,6 +271,7 @@ typedef enum { LYVE_SEMANTICS, /**< generic semantic error */ LYVE_SYNTAX_XML, /**< XML-related syntax error */ LYVE_SYNTAX_JSON, /**< JSON-related syntax error */ + LYVE_SYNTAX_CBOR, /**< CBOR-related syntax error */ LYVE_DATA, /**< YANG data does not reflect some of the module restrictions */ LYVE_OTHER /**< Unknown error */ diff --git a/src/parser_cbor.c b/src/parser_cbor.c index 1362c6033..6b7613848 100644 --- a/src/parser_cbor.c +++ b/src/parser_cbor.c @@ -1058,15 +1058,6 @@ lydcbor_subtree_r(struct lyd_cbor_ctx *lydctx, struct lyd_node *parent, assert(lydctx && first_p && parsed && cbor_obj); - /* assuming that the top level structure is always a map - to be modified to include anything else that it can support */ - - if (!cbor_isa_map(cbor_obj)) - { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Expected CBOR map"); - return LY_EVALID; - } - size_t map_size = cbor_map_size(cbor_obj); struct cbor_pair *pairs = cbor_map_handle(cbor_obj); @@ -1159,28 +1150,37 @@ lydcbor_ctx_init(const struct ly_ctx *ctx, struct ly_in *in, uint32_t parse_opts uint32_t val_opts, struct lyd_cbor_ctx **lydctx_p) { LY_ERR ret = LY_SUCCESS; - struct lyd_cbor_ctx *lydctx = NULL; + struct lyd_cbor_ctx *lydctx; + enum cbor_type cbortype; assert(lydctx_p); - /* Initialize context with calloc to ensure all fields are zero */ + /* init context */ lydctx = calloc(1, sizeof *lydctx); LY_CHECK_ERR_RET(!lydctx, LOGMEM(ctx), LY_EMEM); lydctx->parse_opts = parse_opts; lydctx->val_opts = val_opts; lydctx->free = lyd_cbor_ctx_free; - /* Create low-level CBOR context */ - LY_CHECK_GOTO(ret = lycbor_ctx_new(ctx, in, &lydctx->cborctx), cleanup); - - *lydctx_p = lydctx; - return ret; + /* Create low-level CBOR context (includes CBOR parsing) */ + LY_CHECK_ERR_RET(ret = lycbor_ctx_new(ctx, in, &lydctx->cborctx), free(lydctx), ret); + cbortype = cbor_typeof(lydctx->cborctx->cbor_data); -cleanup: - if (lydctx) + /* assuming that the top level structure is always a map + - though this is not mentioned explicitly in RFC9254 - it is implied + and it is almost always the case - This is a similar assumption made + to the RFC 7951 where JSON Encoding of data modeled by YANG is always assumed + to a have a top-level structure as an object */ + if (!cbor_isa_map(lydctx->cborctx->cbor_data)) { + /* expecting top-level map */ + LOGVAL(ctx, LYVE_SYNTAX_CBOR, "Expected top-level CBOR map, but %s found.", lycbor_token2str(cbortype)); + *lydctx_p = NULL; lyd_cbor_ctx_free((struct lyd_ctx *)lydctx); + return LY_EVALID; } + + *lydctx_p = lydctx; return ret; } @@ -1189,72 +1189,43 @@ lyd_parse_cbor(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, st 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) { - LY_ERR ret = LY_SUCCESS; + LY_ERR r, rc = LY_SUCCESS; struct lyd_cbor_ctx *lydctx = NULL; - cbor_item_t *cbor_data = NULL; - struct cbor_load_result result = {0}; + printf("Entering lyd_parse_cbor\n AHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH\n"); - /* Initialize context */ - LY_CHECK_GOTO(ret = lydcbor_ctx_init(ctx, in, parse_opts, val_opts, &lydctx), cleanup); + /* Initialize context (CBOR parsing happens in lycbor_ctx_new) */ + rc = lydcbor_ctx_init(ctx, in, parse_opts, val_opts, &lydctx); + LY_CHECK_GOTO(rc, cleanup); lydctx->int_opts = int_opts; lydctx->ext = ext; /* find the operation node if it exists already */ - LY_CHECK_GOTO(ret = lyd_parser_find_operation(parent, int_opts, &lydctx->op_node), cleanup); - - - /* - * Loads CBOR data from the current input buffer. - * - * Parameters: - * in->current - Pointer to the current position in the input buffer. - * in->length - Length of the data to be loaded. - * &result - Pointer to a variable where the result status will be stored. - * - * Returns: - * cbor_data - Pointer to the loaded CBOR data structure, or NULL on failure. - */ - /* need to convert in->current from const char* to cbor_data type */ - cbor_data = cbor_load(in->current, in->length, &result); - lydctx->cborctx->cbor_data = cbor_data; - - if (!cbor_data) - { - LOGVAL(ctx, LYVE_SYNTAX, "Failed to parse CBOR data: no data returned from cbor_load()."); - ret = LY_EVALID; - goto cleanup; - } - if (result.error.code != CBOR_ERR_NONE) - { - LOGVAL(ctx, LYVE_SYNTAX, "Failed to parse CBOR data: parsing error (code %d).", result.error.code); - ret = LY_EVALID; - goto cleanup; - } + LY_CHECK_GOTO(rc = lyd_parser_find_operation(parent, int_opts, &lydctx->op_node), cleanup); - /* Probably need to check if the obtained data is a operational node and - then write functions to parse them accordingly. If not then continue below */ + /* Parse the CBOR structure - read subtrees */ + r = lydcbor_subtree_r(lydctx, parent, first_p, parsed, lydctx->cborctx->cbor_data); + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); - /* Parse the CBOR structure */ - ret = lydcbor_subtree_r(lydctx, parent, first_p, parsed, cbor_data); + /* Unexpected sibling node error handling */ -cleanup: - if (cbor_data) - { - cbor_decref(&cbor_data); + /* Validate operation node presence */ + if ((int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_ACTION | LYD_INTOPT_NOTIF | LYD_INTOPT_REPLY)) && + !lydctx->op_node) { + LOGVAL(ctx, LYVE_DATA, "Missing the operation node."); + r = LY_EVALID; + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); } + /* also need to deal with metadata linking etc*/ - if (ret) - { - if (lydctx) - { - lyd_cbor_ctx_free((struct lyd_ctx *)lydctx); - lydctx = NULL; - } +cleanup: + if (rc && (!lydctx || !(lydctx->val_opts & LYD_VALIDATE_MULTI_ERROR) || (rc != LY_EVALID))) { + lyd_cbor_ctx_free((struct lyd_ctx *)lydctx); + lydctx = NULL; } *lydctx_p = (struct lyd_ctx *)lydctx; - return ret; + return rc; } #endif /* ENABLE_CBOR_SUPPORT */ \ No newline at end of file diff --git a/src/parser_data.h b/src/parser_data.h index f6cb3f2a9..4a20ce6a4 100644 --- a/src/parser_data.h +++ b/src/parser_data.h @@ -44,7 +44,8 @@ struct ly_in; * - CBOR * * The reference documentation would be `Encoding of Data Modeled with YANG in the Concise Binary Object - * Representation (CBOR)` : [RFC 9254](https://datatracker.ietf.org/doc/html/rfc9254) + * Representation (CBOR)` : [RFC 9254](https://datatracker.ietf.org/doc/html/rfc9254). < $TODO$ Look at the edge cases of + * RPCs, actions and Notifications and maybe like json only - where we make a proprietary representation> * * While the parsers themselves process the input data only syntactically, all the parser functions actually incorporate * the [common validator](@ref howtoDataValidation) checking the input data semantically. Therefore, the parser functions diff --git a/src/parser_internal.h b/src/parser_internal.h index e22209e4c..a3501bfb4 100644 --- a/src/parser_internal.h +++ b/src/parser_internal.h @@ -204,6 +204,7 @@ struct lyd_cbor_ctx lyd_ctx_free_clb free; /**< destructor */ struct lycbor_ctx *cborctx; /**< CBOR context for low-level operations */ + const struct lysc_node *any_schema; /**< parent anyxml/anydata schema node if parsing nested data tree */ }; #endif /* ENABLE_CBOR_SUPPORT */ From d5597118a442530bf0cc099c63b6644fb383c613 Mon Sep 17 00:00:00 2001 From: MeherRushi Date: Mon, 20 Oct 2025 06:46:18 +0100 Subject: [PATCH 8/9] refactor and fix: - 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 --- src/lcbor.c | 30 - src/parser_cbor.c | 2186 +++++++++++++++++++-------- src/parser_json.c | 1 + src/path.c | 1 + src/plugins_types.c | 4 + src/plugins_types/instanceid.c | 1 + src/plugins_types/instanceid_keys.c | 1 + src/plugins_types/node_instanceid.c | 2 + src/plugins_types/xpath1.0.c | 1 + src/printer_json.c | 2 + src/printer_lyb.c | 1 + src/printer_xml.c | 1 + src/schema_compile_node.c | 1 + src/tree.h | 2 +- src/tree_data_common.c | 8 + src/tree_data_new.c | 1 + src/tree_schema_internal.h | 2 + src/xpath.c | 2 + 18 files changed, 1552 insertions(+), 695 deletions(-) diff --git a/src/lcbor.c b/src/lcbor.c index e92452eec..097ea7eec 100644 --- a/src/lcbor.c +++ b/src/lcbor.c @@ -45,36 +45,6 @@ lycbor_token2str(enum cbor_type cbortype) return "decimals and special values (true, false, nil, ...)"; } - return ""; -} - - return "object"; - case LYJSON_OBJECT_NEXT: - return "object next"; - case LYJSON_OBJECT_CLOSED: - return "object closed"; - case LYJSON_ARRAY: - return "array"; - case LYJSON_ARRAY_NEXT: - return "array next"; - case LYJSON_ARRAY_CLOSED: - return "array closed"; - case LYJSON_OBJECT_NAME: - return "object name"; - case LYJSON_NUMBER: - return "number"; - case LYJSON_STRING: - return "string"; - case LYJSON_TRUE: - return "true"; - case LYJSON_FALSE: - return "false"; - case LYJSON_NULL: - return "null"; - case LYJSON_END: - return "end of input"; - } - return ""; } diff --git a/src/parser_cbor.c b/src/parser_cbor.c index 6b7613848..064dbe62d 100644 --- a/src/parser_cbor.c +++ b/src/parser_cbor.c @@ -38,7 +38,6 @@ #include "tree_schema.h" #include "validation.h" - #include #include void print_json(cbor_item_t *item); @@ -86,16 +85,26 @@ void print_json_map(const cbor_item_t *item) void print_json_number(const cbor_item_t *item) { - if (cbor_isa_uint(item)) { + if (cbor_isa_uint(item)) + { printf("%lu", cbor_get_uint64(item)); - } else if (cbor_isa_negint(item)) { + } + else if (cbor_isa_negint(item)) + { printf("-%lu", cbor_get_uint64(item) + 1); - } else if (cbor_isa_float_ctrl(item)) { - if (cbor_float_get_width(item) == CBOR_FLOAT_64) { + } + else if (cbor_isa_float_ctrl(item)) + { + if (cbor_float_get_width(item) == CBOR_FLOAT_64) + { printf("%f", cbor_float_get_float8(item)); - } else if (cbor_float_get_width(item) == CBOR_FLOAT_32) { + } + else if (cbor_float_get_width(item) == CBOR_FLOAT_32) + { printf("%f", cbor_float_get_float4(item)); - } else if (cbor_float_get_width(item) == CBOR_FLOAT_16) { + } + else if (cbor_float_get_width(item) == CBOR_FLOAT_16) + { printf("%f", cbor_float_get_float2(item)); } } @@ -113,7 +122,8 @@ void print_json_null(const cbor_item_t *item) void print_json(cbor_item_t *item) { - if (!item) { + if (!item) + { printf("null"); return; } @@ -137,20 +147,32 @@ void print_json(cbor_item_t *item) else if (cbor_isa_float_ctrl(item)) { // Check if it's a control value (null, undefined, true, false) - if (cbor_float_get_width(item) == CBOR_FLOAT_0) { + if (cbor_float_get_width(item) == CBOR_FLOAT_0) + { uint8_t ctrl = cbor_ctrl_value(item); - if (ctrl == 20) { + if (ctrl == 20) + { printf("false"); - } else if (ctrl == 21) { + } + else if (ctrl == 21) + { printf("true"); - } else if (ctrl == 22) { + } + else if (ctrl == 22) + { printf("null"); - } else if (ctrl == 23) { + } + else if (ctrl == 23) + { printf("undefined"); - } else { + } + else + { printf("null"); // unknown control value } - } else { + } + else + { print_json_number(item); } } @@ -164,14 +186,10 @@ void print_json(cbor_item_t *item) } } -static LY_ERR lydcbor_subtree_r(struct lyd_cbor_ctx *lydctx, struct lyd_node *parent, - struct lyd_node **first_p, struct ly_set *parsed, const cbor_item_t *cbor_obj); - /** - * @brief Free the CBOR data parser context - * CBOR implementation of lyd_ctx_free_clb(). + * @brief Free the CBOR data parser context. * - * @param[in] lydctx Data parser context to free. + * CBOR implementation of lyd_ctx_free_clb(). */ static void lyd_cbor_ctx_free(struct lyd_ctx *lydctx) @@ -187,10 +205,367 @@ lyd_cbor_ctx_free(struct lyd_ctx *lydctx) } /** - * @brief Convert a CBOR item to a string representation. + * @brief Parse CBOR member-name as [\@][prefix:][name] + * + * \@ - metadata flag, maps to 1 in @p is_meta_p + * prefix - name of the module of the data node + * name - name of the data node + * + * All the output parameters are mandatory. Function only parses the member-name. + * + * @param[in] value String to parse + * @param[in] value_len Length of the @p value. + * @param[out] name_p Pointer to the beginning of the parsed name. + * @param[out] name_len_p Pointer to the length of the parsed name. + * @param[out] prefix_p Pointer to the beginning of the parsed prefix. If the member-name does not contain prefix, result is NULL. + * @param[out] prefix_len_p Pointer to the length of the parsed prefix. If the member-name does not contain prefix, result is 0. + * @param[out] is_meta_p Pointer to the metadata flag, set to 1 if the member-name contains \@, 0 otherwise. + */ +static void +lydcbor_parse_name(const char *value, size_t value_len, const char **name_p, size_t *name_len_p, const char **prefix_p, + size_t *prefix_len_p, ly_bool *is_meta_p) +{ + const char *name, *prefix = NULL; + size_t name_len, prefix_len = 0; + ly_bool is_meta = 0; + + name = memchr(value, ':', value_len); + if (name != NULL) + { + prefix = value; + if (*prefix == '@') + { + is_meta = 1; + prefix++; + } + prefix_len = name - prefix; + name++; + name_len = value_len - (prefix_len + 1) - is_meta; + } + else + { + name = value; + if (name[0] == '@') + { + is_meta = 1; + name++; + } + name_len = value_len - is_meta; + } + + *name_p = name; + *name_len_p = name_len; + *prefix_p = prefix; + *prefix_len_p = prefix_len; + *is_meta_p = is_meta; +} + +/** + * @brief Get correct prefix (module_name) inside the @p node. + * + * @param[in] node Data node to get inherited prefix. + * @param[in] local_prefix Local prefix to replace the inherited prefix. + * @param[in] local_prefix_len Length of the @p local_prefix string. In case of 0, the inherited prefix is taken. + * @param[out] prefix_p Pointer to the resulting prefix string. + * @param[out] prefix_len_p Pointer to the length of the resulting @p prefix_p string. + * @return LY_ERR value. + */ +static LY_ERR +lydcbor_get_node_prefix(struct lyd_node *node, const char *local_prefix, size_t local_prefix_len, const char **prefix_p, + size_t *prefix_len_p) +{ + struct lyd_node_opaq *onode; + const char *module_name = NULL; + + assert(prefix_p && prefix_len_p); + + if (local_prefix_len) + { + *prefix_p = local_prefix; + *prefix_len_p = local_prefix_len; + return LY_SUCCESS; + } + + while (node) + { + if (node->schema) + { + module_name = node->schema->module->name; + break; + } + onode = (struct lyd_node_opaq *)node; + if (onode->name.module_name) + { + module_name = onode->name.module_name; + break; + } + else if (onode->name.prefix) + { + module_name = onode->name.prefix; + break; + } + node = lyd_parent(node); + } + + *prefix_p = module_name; + *prefix_len_p = ly_strlen(module_name); + return LY_SUCCESS; +} + +/** + * @brief Skip the current CBOR item based on its type. + * + * @param[in] cborctx CBOR context. + * @param[in] item CBOR item to skip. + * @return LY_ERR value. + */ +static LY_ERR +lydcbor_data_skip(struct lycbor_ctx *cborctx) +{ + (void)cborctx; + /* In CBOR, items are already parsed, so skipping is implicit */ + return LY_SUCCESS; +} + +/** + * @brief Get schema node corresponding to the input parameters. + * + * @param[in] lydctx CBOR data parser context. + * @param[in] is_attr Flag if the reference to the node is an attribute. + * @param[in] prefix Requested node's prefix (module name). + * @param[in] prefix_len Length of the @p prefix. + * @param[in] name Requested node's name. + * @param[in] name_len Length of the @p name. + * @param[in] parent Parent of the node being processed. + * @param[out] snode Found schema node corresponding to the input parameters. + * @param[out] ext Extension instance that provided @p snode, if any. + * @return LY_SUCCESS on success. + * @return LY_ENOT if the whole object was parsed (skipped or as an extension). + * @return LY_ERR on error. + */ +static LY_ERR +lydcbor_get_snode(struct lyd_cbor_ctx *lydctx, ly_bool is_attr, const char *prefix, size_t prefix_len, const char *name, + size_t name_len, struct lyd_node *parent, const struct lysc_node **snode, struct lysc_ext_instance **ext) +{ + LY_ERR ret = LY_SUCCESS, r; + struct lys_module *mod = NULL; + uint32_t getnext_opts = lydctx->int_opts & LYD_INTOPT_REPLY ? LYS_GETNEXT_OUTPUT : 0; + + *snode = NULL; + *ext = NULL; + + /* get the element module, prefer parent context because of extensions */ + if (prefix_len) + { + mod = ly_ctx_get_module_implemented2(parent ? LYD_CTX(parent) : lydctx->cborctx->ctx, prefix, prefix_len); + } + else if (parent) + { + if (parent->schema) + { + mod = parent->schema->module; + } + } + else if (!(lydctx->int_opts & LYD_INTOPT_ANY)) + { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Top-level CBOR object member \"%.*s\" must be namespace-qualified.", + (int)(is_attr ? name_len + 1 : name_len), is_attr ? name - 1 : name); + ret = LY_EVALID; + goto cleanup; + } + if (!mod) + { + /* check for extension data */ + r = ly_nested_ext_schema(parent, NULL, prefix, prefix_len, LY_VALUE_CBOR, NULL, name, name_len, snode, ext); + if (r != LY_ENOT) + { + ret = r; + goto cleanup; + } + + /* unknown module */ + if (lydctx->parse_opts & LYD_PARSE_STRICT) + { + LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, "No module named \"%.*s\" in the context.", (int)prefix_len, prefix); + ret = LY_EVALID; + goto cleanup; + } + } + + /* get the schema node */ + if (mod && (!parent || parent->schema)) + { + if (!parent && lydctx->ext) + { + *snode = lysc_ext_find_node(lydctx->ext, mod, name, name_len, 0, getnext_opts); + } + else + { + *snode = lys_find_child(lyd_parser_node_schema(parent), mod, name, name_len, 0, getnext_opts); + } + if (!*snode) + { + /* check for extension data */ + r = ly_nested_ext_schema(parent, NULL, prefix, prefix_len, LY_VALUE_CBOR, NULL, name, name_len, snode, ext); + if (r != LY_ENOT) + { + ret = r; + goto cleanup; + } + + /* unknown data node */ + printf("checkpoint1-web\n"); + if (lydctx->parse_opts & LYD_PARSE_STRICT) + { + if (parent) + { + LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, "Node \"%.*s\" not found as a child of \"%s\" node.", + (int)name_len, name, LYD_NAME(parent)); + } + else if (lydctx->ext) + { + if (lydctx->ext->argument) + { + LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, + "Node \"%.*s\" not found in the \"%s\" %s extension instance.", + (int)name_len, name, lydctx->ext->argument, lydctx->ext->def->name); + } + else + { + LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, "Node \"%.*s\" not found in the %s extension instance.", + (int)name_len, name, lydctx->ext->def->name); + } + } + else + { + LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, "Node \"%.*s\" not found in the \"%s\" module.", + (int)name_len, name, mod->name); + } + ret = LY_EVALID; + goto cleanup; + } + } + else + { + /* check that schema node is valid and can be used */ + ret = lyd_parser_check_schema((struct lyd_ctx *)lydctx, *snode); + } + } + +cleanup: + return ret; +} + +/** + * @brief Check if CBOR item is null/undefined. + * + * @param[in] item CBOR item to check. + * @return 1 if null/undefined, 0 otherwise. + */ +static ly_bool +lydcbor_is_null(const cbor_item_t *item) +{ + if (!item) + { + return 1; + } + + if (cbor_isa_float_ctrl(item)) + { + if (cbor_float_get_width(item) == CBOR_FLOAT_0) + { + uint8_t ctrl = cbor_ctrl_value(item); + if (ctrl == 22 || ctrl == 23) + { /* null or undefined */ + return 1; + } + } + } + + return 0; +} + +/** + * @brief Get the hint for the data type parsers according to the current CBOR type. * - * This function handles the low-level CBOR to string conversion, - * similar to how JSON parser converts JSON values to strings. + * @param[in] lydctx CBOR data parser context. + * @param[in] item CBOR item. + * @param[out] type_hint_p Pointer to the variable to store the result. + * @return LY_SUCCESS in case of success. + * @return LY_EINVAL in case of invalid CBOR type. + */ +static LY_ERR +lydcbor_value_type_hint(struct lyd_cbor_ctx *lydctx, const cbor_item_t *item, uint32_t *type_hint_p) +{ + enum cbor_type type; + + *type_hint_p = 0; + + if (!item) + { + return LY_EINVAL; + } + + type = cbor_typeof(item); + + if (type == CBOR_TYPE_ARRAY) + { + /* check for [null] */ + if (cbor_array_size(item) == 1) + { + cbor_item_t **handle = cbor_array_handle(item); + if (handle && lydcbor_is_null(handle[0])) + { + *type_hint_p = LYD_VALHINT_EMPTY; + return LY_SUCCESS; + } + } + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Expected CBOR value or [null], but array found."); + return LY_EINVAL; + } + else if (type == CBOR_TYPE_STRING) + { + *type_hint_p = LYD_VALHINT_STRING | LYD_VALHINT_NUM64; + } + else if (type == CBOR_TYPE_UINT || type == CBOR_TYPE_NEGINT) + { + *type_hint_p = LYD_VALHINT_DECNUM; + } + else if (type == CBOR_TYPE_FLOAT_CTRL) + { + if (cbor_float_ctrl_is_ctrl(item)) + { + uint8_t ctrl = cbor_ctrl_value(item); + if (ctrl == CBOR_CTRL_TRUE || ctrl == CBOR_CTRL_FALSE) + { + *type_hint_p = LYD_VALHINT_BOOLEAN; + } + else if (ctrl == CBOR_CTRL_NULL) + { + *type_hint_p = 0; + } + else + { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Unexpected CBOR control value."); + return LY_EINVAL; + } + } + else + { + *type_hint_p = LYD_VALHINT_DECNUM; + } + } + else + { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Unexpected CBOR data type."); + return LY_EINVAL; + } + + return LY_SUCCESS; +} + +/** + * @brief Convert a CBOR item to a string representation. * * @param[in] item CBOR item to convert. * @param[out] str_val String value (allocated, caller must free). @@ -206,14 +581,11 @@ lydcbor_item_to_string(const cbor_item_t *item, char **str_val, size_t *str_len) *str_val = NULL; *str_len = 0; - switch (cbor_typeof(item)) - { - case CBOR_TYPE_UINT: - { + switch (cbor_typeof(item)) { + case CBOR_TYPE_UINT: { uint64_t val = cbor_get_int(item); int len = snprintf(NULL, 0, "%" PRIu64, val); - if (len < 0) - { + if (len < 0) { return LY_ESYS; } *str_val = malloc(len + 1); @@ -222,12 +594,10 @@ lydcbor_item_to_string(const cbor_item_t *item, char **str_val, size_t *str_len) *str_len = len; break; } - case CBOR_TYPE_NEGINT: - { + case CBOR_TYPE_NEGINT: { int64_t val = -1 - (int64_t)cbor_get_int(item); int len = snprintf(NULL, 0, "%" PRId64, val); - if (len < 0) - { + if (len < 0) { return LY_ESYS; } *str_val = malloc(len + 1); @@ -251,10 +621,8 @@ lydcbor_item_to_string(const cbor_item_t *item, char **str_val, size_t *str_len) (*str_val)[*str_len] = '\0'; break; case CBOR_TYPE_FLOAT_CTRL: - if (cbor_float_ctrl_is_ctrl(item)) - { - switch (cbor_ctrl_value(item)) - { + if (cbor_float_ctrl_is_ctrl(item)) { + switch (cbor_ctrl_value(item)) { case CBOR_CTRL_TRUE: *str_val = strdup("true"); *str_len = 4; @@ -273,14 +641,10 @@ lydcbor_item_to_string(const cbor_item_t *item, char **str_val, size_t *str_len) break; } LY_CHECK_ERR_RET(!*str_val, LOGMEM(NULL), LY_EMEM); - } - else - { - /* Float value */ + } else { double val = cbor_float_get_float(item); int len = snprintf(NULL, 0, "%g", val); - if (len < 0) - { + if (len < 0) { return LY_ESYS; } *str_val = malloc(len + 1); @@ -299,855 +663,1327 @@ lydcbor_item_to_string(const cbor_item_t *item, char **str_val, size_t *str_len) } /** - * @brief Get string key from CBOR map item. + * @brief Check in advance if the input data are parsable according to the provided @p snode. + * + * Note that the checks are done only in case the LYD_PARSE_OPAQ is allowed. Otherwise the same checking + * is naturally done when the data are really parsed. * - * For named identifier format, keys should be strings. - * For SID format, keys would be integers (future implementation). + * @param[in] lydctx CBOR data parser context. + * @param[in] snode Schema node corresponding to the member currently being processed. + * @param[in] cbor_value CBOR value to check. + * @param[out] type_hint_p Pointer to store detected value type hint. + * @return LY_SUCCESS if data are parsable. + * @return LY_ENOT if input data are not sufficient. + * @return LY_EINVAL in case of invalid encoding. */ static LY_ERR -lydcbor_get_key_string(struct lyd_cbor_ctx *lydctx, const cbor_item_t *key_item, - char **key_str, size_t *key_len) +lydcbor_data_check_opaq(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, const cbor_item_t *cbor_value, + uint32_t *type_hint_p) { LY_ERR ret = LY_SUCCESS; + uint32_t *prev_lo, temp_lo = 0; + char *str_val = NULL; + size_t str_len = 0; - assert(lydctx && key_item && key_str && key_len); + assert(snode); - switch (lydctx->cborctx->format) + if (!(snode->nodetype & (LYD_NODE_TERM | LYS_LIST))) { - case LYD_CBOR_NAMED: - /* Keys must be strings for named format */ - if (!cbor_isa_string(key_item)) + return LY_SUCCESS; + } + + if (lydctx->parse_opts & LYD_PARSE_OPAQ) + { + switch (snode->nodetype) { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "CBOR map key must be string for named identifier format"); - return LY_EVALID; + case LYS_LEAFLIST: + case LYS_LEAF: + if ((ret = lydcbor_value_type_hint(lydctx, cbor_value, type_hint_p))) + { + break; + } + + prev_lo = ly_temp_log_options(&temp_lo); + ret = lydcbor_item_to_string(cbor_value, &str_val, &str_len); + if (ret == LY_SUCCESS) + { + if (ly_value_validate(NULL, snode, str_val, str_len, LY_VALUE_CBOR, NULL, *type_hint_p)) + { + ret = LY_ENOT; + } + } + ly_temp_log_options(prev_lo); + free(str_val); + break; + case LYS_LIST: + /* Lists may not have all keys - handled elsewhere */ + break; } - ret = lydcbor_item_to_string(key_item, key_str, key_len); - break; - case LYD_CBOR_SID: - /* Future: Handle SID integer keys */ - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "CBOR SID format not yet implemented"); - ret = LY_ENOT; - break; - default: - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Unknown CBOR format"); - ret = LY_EINVAL; - break; + } + else if (snode->nodetype & LYD_NODE_TERM) + { + ret = lydcbor_value_type_hint(lydctx, cbor_value, type_hint_p); } return ret; } /** - * @brief Parse a single CBOR value according to schema node. + * @brief Join the forward-referencing metadata with their target data nodes. * - * @param[in] lydctx CBOR parser context. - * @param[in] snode Schema node for the value. - * @param[in] cbor_item CBOR item to parse. - * @param[out] node Created data node. - * @return LY_ERR value. + * @param[in] lydctx CBOR data parser context. + * @param[in,out] first_p Pointer to the first sibling node. + * @return LY_SUCCESS on success. + * @return LY_EVALID if there are unresolved metadata. */ static LY_ERR -lydcbor_parse_value(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, - const void *cbor_item, struct lyd_node **node) +lydcbor_metadata_finish(struct lyd_cbor_ctx *lydctx, struct lyd_node **first_p) { LY_ERR ret = LY_SUCCESS; - const cbor_item_t *item = (const cbor_item_t *)cbor_item; - char *str_val = NULL; - size_t str_len = 0; - - assert(lydctx && snode && item && node); + struct lyd_node *node, *attr, *next, *meta_iter; + struct lysc_ext_instance *ext; + uint64_t instance = 0; + const char *prev = NULL; + uint32_t log_location_items = 0; + + /* finish linking metadata */ + LY_LIST_FOR_SAFE(*first_p, next, attr) + { + struct lyd_node_opaq *meta_container = (struct lyd_node_opaq *)attr; + uint64_t match = 0; + ly_bool is_attr; + const char *name, *prefix; + size_t name_len, prefix_len; + const struct lysc_node *snode; + + if (attr->schema || (meta_container->name.name[0] != '@')) + { + continue; + } - /* Convert CBOR value to string */ - LY_CHECK_GOTO(ret = lydcbor_item_to_string(item, &str_val, &str_len), cleanup); + LOG_LOCSET(NULL, attr); + log_location_items++; - /* Create data node based on schema node type */ - switch (snode->nodetype) - { - case LYS_LEAF: - case LYS_LEAFLIST: - ret = lyd_create_term(snode, str_val, str_len, 0, 0, NULL, LY_VALUE_JSON, NULL, LYD_HINT_DATA, NULL, node); - break; - case LYS_ANYDATA: - case LYS_ANYXML: - /* For anydata/anyxml, we store the CBOR directly */ - ret = lyd_create_any(snode, cbor_item, LYD_ANYDATA_CBOR, 0, node); - break; - default: - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Invalid schema node type for CBOR value"); - ret = LY_EVALID; - break; + if (prev != meta_container->name.name) + { + lydict_remove(lydctx->cborctx->ctx, prev); + LY_CHECK_GOTO(ret = lydict_insert(lydctx->cborctx->ctx, meta_container->name.name, 0, &prev), cleanup); + instance = 1; + } + else + { + instance++; + } + + /* find the corresponding data node */ + LY_LIST_FOR(*first_p, node) + { + if (!node->schema) + { + /* opaq node */ + if (strcmp(&meta_container->name.name[1], ((struct lyd_node_opaq *)node)->name.name)) + { + continue; + } + + if (((struct lyd_node_opaq *)node)->hints & LYD_NODEHINT_LIST) + { + LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Metadata container references a sibling list node %s.", + ((struct lyd_node_opaq *)node)->name.name); + ret = LY_EVALID; + goto cleanup; + } + + match++; + if (match != instance) + { + continue; + } + + LY_LIST_FOR(meta_container->child, meta_iter) + { + struct lyd_node_opaq *meta = (struct lyd_node_opaq *)meta_iter; + + ret = lyd_create_attr(node, NULL, lydctx->cborctx->ctx, meta->name.name, strlen(meta->name.name), + meta->name.prefix, ly_strlen(meta->name.prefix), meta->name.module_name, + ly_strlen(meta->name.module_name), meta->value, ly_strlen(meta->value), NULL, LY_VALUE_CBOR, + NULL, meta->hints); + LY_CHECK_GOTO(ret, cleanup); + } + break; + } + else + { + lydcbor_parse_name(meta_container->name.name, strlen(meta_container->name.name), &name, &name_len, + &prefix, &prefix_len, &is_attr); + assert(is_attr); + lydcbor_get_snode(lydctx, is_attr, prefix, prefix_len, name, name_len, lyd_parent(*first_p), &snode, &ext); + + if (snode != node->schema) + { + continue; + } + + match++; + if (match != instance) + { + continue; + } + + LY_LIST_FOR(meta_container->child, meta_iter) + { + struct lyd_node_opaq *meta = (struct lyd_node_opaq *)meta_iter; + struct lys_module *mod = NULL; + + mod = ly_ctx_get_module_implemented(lydctx->cborctx->ctx, meta->name.prefix); + if (mod) + { + ret = lyd_parser_create_meta((struct lyd_ctx *)lydctx, node, NULL, mod, + meta->name.name, strlen(meta->name.name), meta->value, ly_strlen(meta->value), + NULL, LY_VALUE_CBOR, NULL, meta->hints, node->schema); + LY_CHECK_GOTO(ret, cleanup); + } + else if (lydctx->parse_opts & LYD_PARSE_STRICT) + { + if (meta->name.prefix) + { + LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, + "Unknown (or not implemented) YANG module \"%s\" of metadata \"%s%s%s\".", + meta->name.prefix, meta->name.prefix, ly_strlen(meta->name.prefix) ? ":" : "", + meta->name.name); + } + else + { + LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, "Missing YANG module of metadata \"%s\".", + meta->name.name); + } + ret = LY_EVALID; + goto cleanup; + } + } + + ret = lyd_parser_set_data_flags(node, &node->meta, (struct lyd_ctx *)lydctx, ext); + LY_CHECK_GOTO(ret, cleanup); + break; + } + } + + if (match != instance) + { + if (instance > 1) + { + LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, + "Missing CBOR data instance #%" PRIu64 " to be coupled with %s metadata.", + instance, meta_container->name.name); + } + else + { + LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, "Missing CBOR data instance to be coupled with %s metadata.", + meta_container->name.name); + } + ret = LY_EVALID; + } + else + { + if (attr == (*first_p)) + { + *first_p = attr->next; + } + lyd_free_tree(attr); + } + + LOG_LOCBACK(0, log_location_items); + log_location_items = 0; } cleanup: - free(str_val); + lydict_remove(lydctx->cborctx->ctx, prev); + LOG_LOCBACK(0, log_location_items); return ret; } +/** + * @brief Parse a metadata member/attribute from CBOR. + * + * @param[in] lydctx CBOR data parser context. + * @param[in] snode Schema node of the metadata parent. + * @param[in] node Parent node. + * @param[in] cbor_meta CBOR metadata item. + * @return LY_ERR value. + */ static LY_ERR -lydcbor_parse_node_value(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, - struct lyd_node **node, const cbor_item_t *cbor_value) +lydcbor_meta_attr(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, struct lyd_node *node, + const cbor_item_t *cbor_meta) { - LY_ERR ret = LY_SUCCESS; + LY_ERR rc = LY_SUCCESS, r; + enum cbor_type type; + const char *expected; + ly_bool in_parent = 0; + const char *name, *prefix = NULL; + size_t name_len, prefix_len = 0; + struct lys_module *mod; + const struct ly_ctx *ctx = lydctx->cborctx->ctx; + ly_bool is_attr = 0; + struct lyd_node *prev = node; + uint32_t instance = 0, val_hints; + uint16_t nodetype; + struct cbor_pair *pairs; + size_t map_size; + + assert(snode || node); + + nodetype = snode ? snode->nodetype : LYS_CONTAINER; + if (snode) + { + LOG_LOCSET(snode, NULL); + } - assert(lydctx && snode && node && cbor_value); - *node = NULL; + type = cbor_typeof(cbor_meta); - switch (snode->nodetype) + /* check attribute encoding */ + switch (nodetype) { - case LYS_CONTAINER: - ret = lyd_create_inner(snode, node); - break; - case LYS_LIST: - ret = lyd_create_inner(snode, node); - break; - case LYS_LEAF: case LYS_LEAFLIST: - { - char *str_val = NULL; - size_t str_len = 0; + expected = "@name/array of objects/nulls"; + LY_CHECK_GOTO(type != CBOR_TYPE_ARRAY, representation_error); + + next_entry: + instance++; + if (!node || (node->schema != prev->schema)) + { + LOGVAL(ctx, LYVE_REFERENCE, "Missing CBOR data instance #%" PRIu32 " of %s:%s to be coupled with metadata.", instance, prev->schema->module->name, prev->schema->name); + rc = LY_EVALID; + goto cleanup; + } + + /* Process array item */ + if (cbor_array_size(cbor_meta) > instance - 1) + { + cbor_item_t **handle = cbor_array_handle(cbor_meta); + const cbor_item_t *item = handle[instance - 1]; - ret = lydcbor_item_to_string(cbor_value, &str_val, &str_len); - if (ret == LY_SUCCESS) + if (lydcbor_is_null(item)) + { + prev = node; + node = node->next; + if (instance < cbor_array_size(cbor_meta)) + { + goto next_entry; + } + goto cleanup; + } + } + else { - ret = lyd_create_term(snode, str_val, str_len, 0, 0, NULL, LY_VALUE_JSON, NULL, LYD_HINT_DATA, NULL, node); + goto cleanup; } - free(str_val); break; - } - case LYS_ANYDATA: + case LYS_LEAF: case LYS_ANYXML: - ret = lyd_create_any(snode, cbor_value, LYD_ANYDATA_CBOR, 0, node); + expected = "@name/object"; + LY_CHECK_GOTO(type != CBOR_TYPE_MAP, representation_error); break; - default: - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Invalid schema node type %d", snode->nodetype); - ret = LY_EVALID; + case LYS_CONTAINER: + case LYS_LIST: + case LYS_ANYDATA: + case LYS_NOTIF: + case LYS_ACTION: + case LYS_RPC: + in_parent = 1; + expected = "@/object"; + LY_CHECK_GOTO(type != CBOR_TYPE_MAP, representation_error); break; + default: + LOGINT(ctx); + rc = LY_EINT; + goto cleanup; } - return ret; -} + /* process all members inside metadata object */ + assert(type == CBOR_TYPE_MAP); + map_size = cbor_map_size(cbor_meta); + pairs = cbor_map_handle(cbor_meta); -static LY_ERR -lydcbor_parse_terminal(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, - const cbor_item_t *cbor_value, struct lyd_node **first_p, struct ly_set *parsed) -{ - LY_ERR ret; - struct lyd_node *node = NULL; + for (size_t i = 0; i < map_size; ++i) + { + const cbor_item_t *key_item = pairs[i].key; + const cbor_item_t *value_item = pairs[i].value; + char *key_str = NULL; + size_t key_len = 0; - ret = lydcbor_parse_node_value(lydctx, snode, &node, cbor_value); - LY_CHECK_RET(ret); + if (!cbor_isa_string(key_item)) + { + LOGVAL(ctx, LYVE_SYNTAX, "Metadata key must be a string."); + rc = LY_EVALID; + goto cleanup; + } - if (!node) - { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Failed to create terminal node for \"%s\"", snode->name); - return LY_EVALID; - } + LY_CHECK_GOTO(rc = lydcbor_item_to_string(key_item, &key_str, &key_len), cleanup); - lyd_hash(node); - /* Insert into tree */ - ret = lyd_insert_sibling(*first_p, node, first_p); - if (ret) - { - lyd_free_tree(node); - return ret; - } + lydcbor_parse_name(key_str, key_len, &name, &name_len, &prefix, &prefix_len, &is_attr); - /* Add to parsed set */ - return ly_set_add(parsed, node, 1, NULL); -} + if (!name_len) + { + LOGVAL(ctx, LYVE_SYNTAX, "Metadata in CBOR found with an empty name."); + free(key_str); + rc = LY_EVALID; + goto cleanup; + } + else if (!prefix_len) + { + LOGVAL(ctx, LYVE_SYNTAX, "Metadata in CBOR must be namespace-qualified, missing prefix for \"%.*s\".", + (int)key_len, key_str); + free(key_str); + rc = LY_EVALID; + goto cleanup; + } + else if (is_attr) + { + LOGVAL(ctx, LYVE_SYNTAX, "Invalid format of Metadata identifier in CBOR, unexpected '@' in \"%.*s\"", + (int)key_len, key_str); + free(key_str); + rc = LY_EVALID; + goto cleanup; + } -static LY_ERR -lydcbor_parse_container(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, - const cbor_item_t *cbor_value, struct lyd_node **first_p, struct ly_set *parsed) -{ - LY_ERR ret; - struct lyd_node *node = NULL; + /* get the element module */ + mod = ly_ctx_get_module_implemented2(ctx, prefix, prefix_len); + if (!mod) + { + if (lydctx->parse_opts & LYD_PARSE_STRICT) + { + LOGVAL(ctx, LYVE_REFERENCE, "Prefix \"%.*s\" of the metadata \"%.*s\" does not match any module in the context.", + (int)prefix_len, prefix, (int)name_len, name); + free(key_str); + rc = LY_EVALID; + goto cleanup; + } + if (node->schema) + { + free(key_str); + continue; + } + assert(lydctx->parse_opts & LYD_PARSE_OPAQ); + } - ret = lyd_create_inner(snode, &node); - LY_CHECK_RET(ret); + /* get value hints */ + LY_CHECK_ERR_GOTO(rc = lydcbor_value_type_hint(lydctx, value_item, &val_hints), free(key_str), cleanup); - if (!node) - { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Failed to create container node for \"%s\"", snode->name); - return LY_EVALID; + if (node->schema) + { + char *str_val = NULL; + size_t str_len = 0; + + LY_CHECK_ERR_GOTO(rc = lydcbor_item_to_string(value_item, &str_val, &str_len), free(key_str), cleanup); + + /* create metadata */ + rc = lyd_parser_create_meta((struct lyd_ctx *)lydctx, node, NULL, mod, name, name_len, str_val, + str_len, NULL, LY_VALUE_CBOR, NULL, val_hints, node->schema); + free(str_val); + LY_CHECK_ERR_GOTO(rc, free(key_str), cleanup); + + /* add/correct flags */ + rc = lyd_parser_set_data_flags(node, &node->meta, (struct lyd_ctx *)lydctx, NULL); + LY_CHECK_ERR_GOTO(rc, free(key_str), cleanup); + } + else + { + /* create attribute */ + const char *module_name; + size_t module_name_len; + char *str_val = NULL; + size_t str_len = 0; + + lydcbor_get_node_prefix(node, prefix, prefix_len, &module_name, &module_name_len); + + LY_CHECK_ERR_GOTO(rc = lydcbor_item_to_string(value_item, &str_val, &str_len), free(key_str), cleanup); + + rc = lyd_create_attr(node, NULL, ctx, name, name_len, prefix, prefix_len, module_name, + module_name_len, str_val, str_len, NULL, LY_VALUE_CBOR, NULL, val_hints); + free(str_val); + LY_CHECK_ERR_GOTO(rc, free(key_str), cleanup); + } + + free(key_str); } - lyd_hash(node); - /* Insert into tree first */ - ret = lyd_insert_sibling(*first_p, node, first_p); - if (ret) + if (nodetype == LYS_LEAFLIST && instance < cbor_array_size(cbor_meta)) { - lyd_free_tree(node); - return ret; + prev = node; + node = node->next; + goto next_entry; } - /* Add to parsed set */ - ret = ly_set_add(parsed, node, 1, NULL); - LY_CHECK_RET(ret); + goto cleanup; - /* Parse container children */ - if (cbor_isa_map(cbor_value) && cbor_map_size(cbor_value) > 0) - { - struct lyd_node *child_first = NULL; - ret = lydcbor_subtree_r(lydctx, node, &child_first, parsed, cbor_value); - if (ret) - { - return ret; - } +representation_error: + LOGVAL(ctx, LYVE_SYNTAX, + "The attribute(s) of %s \"%s\" is expected to be represented as CBOR %s, but input data contains different type.", + lys_nodetype2str(nodetype), node ? LYD_NAME(node) : LYD_NAME(prev), expected); + rc = LY_EVALID; - /* Link children to container */ - if (child_first) +cleanup: + if ((rc == LY_EVALID) && (lydctx->val_opts & LYD_VALIDATE_MULTI_ERROR)) + { + if ((r = lydcbor_data_skip(lydctx->cborctx))) { - lyd_insert_child(node, child_first); + rc = r; } } - - return LY_SUCCESS; + LOG_LOCBACK(snode ? 1 : 0, 0); + return rc; } -/* Helper function to check if CBOR item is null/undefined */ -static ly_bool -lydcbor_is_null(const cbor_item_t *item) +/** + * @brief Maintain children - insert node and update first pointer. + * + * @param[in] parent Parent node to insert to. + * @param[in,out] first_p Pointer to the first sibling. + * @param[in,out] node_p Pointer to the node to insert. + * @param[in] last If set, insert at the end. + * @param[in] ext Extension instance. + */ +static void +lydcbor_maintain_children(struct lyd_node *parent, struct lyd_node **first_p, struct lyd_node **node_p, ly_bool last, + struct lysc_ext_instance *ext) { - if (!item) { - return 1; + if (!*node_p) + { + return; } - - /* Check for CBOR null primitive */ - if (cbor_isa_float_ctrl(item)) { - if (cbor_float_get_width(item) == CBOR_FLOAT_0) { - uint8_t ctrl = cbor_ctrl_value(item); - if (ctrl == 22 || ctrl == 23) { /* null or undefined */ - return 1; + + if (ext) + { + lyplg_ext_insert(parent, *node_p); + } + else + { + lyd_insert_node(parent, first_p, *node_p, last); + } + if (first_p) + { + if (parent) + { + *first_p = lyd_child(parent); + } + else + { + while ((*first_p)->prev->next) + { + *first_p = (*first_p)->prev; } } } - - return 0; + *node_p = NULL; } +/** + * @brief Create an opaq node from CBOR. + * + * @param[in] lydctx CBOR data parser context. + * @param[in] name Name of the opaq node. + * @param[in] name_len Length of @p name. + * @param[in] prefix Prefix of the opaq node. + * @param[in] prefix_len Length of @p prefix. + * @param[in] parent Data parent. + * @param[in] cbor_value CBOR value item. + * @param[out] node_p Pointer to the created opaq node. + * @return LY_ERR value. + */ static LY_ERR -lydcbor_parse_list_array(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, - const cbor_item_t *array_item, struct lyd_node **first_p, struct ly_set *parsed) +lydcbor_create_opaq(struct lyd_cbor_ctx *lydctx, const char *name, size_t name_len, const char *prefix, size_t prefix_len, + struct lyd_node *parent, const cbor_item_t *cbor_value, struct lyd_node **node_p) { LY_ERR ret = LY_SUCCESS; - struct lyd_node *node = NULL; - size_t array_size; - cbor_item_t **array_handle; - - assert(lydctx && snode && array_item && parsed); + const char *value = NULL, *module_name; + size_t value_len = 0, module_name_len = 0; + ly_bool dynamic = 0; + uint32_t type_hint = 0; + char *str_val = NULL; - if (!cbor_isa_array(array_item)) + if (cbor_typeof(cbor_value) != CBOR_TYPE_MAP) { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Expected CBOR array for list"); - return LY_EVALID; + /* prepare for creating opaq node with a value */ + LY_CHECK_RET(lydcbor_item_to_string(cbor_value, &str_val, &value_len)); + value = str_val; + dynamic = 1; + + LY_CHECK_GOTO(ret = lydcbor_value_type_hint(lydctx, cbor_value, &type_hint), cleanup); } - if (snode->nodetype != LYS_LIST) + /* get the module name */ + lydcbor_get_node_prefix(parent, prefix, prefix_len, &module_name, &module_name_len); + if (!module_name && !parent && lydctx->any_schema) { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Schema node must be list"); - return LY_EVALID; + module_name = lydctx->any_schema->module->name; + module_name_len = strlen(module_name); } - array_size = cbor_array_size(array_item); - array_handle = cbor_array_handle(array_item); + /* create node */ + ret = lyd_create_opaq(lydctx->cborctx->ctx, name, name_len, prefix, prefix_len, module_name, module_name_len, value, + value_len, &dynamic, LY_VALUE_CBOR, NULL, type_hint, node_p); - if (!array_handle && array_size > 0) +cleanup: + if (dynamic) { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Invalid CBOR array structure"); - return LY_EVALID; + free((char *)value); } + return ret; +} - for (size_t i = 0; i < array_size; ++i) - { - const cbor_item_t *item = array_handle[i]; +static LY_ERR lydcbor_subtree_r(struct lyd_cbor_ctx *lydctx, struct lyd_node *parent, + struct lyd_node **first_p, struct ly_set *parsed, const cbor_item_t *cbor_obj); - if (!item) - { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Null array element at index %zu", i); - ret = LY_EVALID; - goto cleanup; - } +/** + * @brief Parse opaq node from CBOR. + * + * @param[in] lydctx CBOR data parser context. + * @param[in] name Name of the opaq node. + * @param[in] name_len Length of @p name. + * @param[in] prefix Prefix of the opaq node. + * @param[in] prefix_len Length of @p prefix. + * @param[in] parent Data parent. + * @param[in] cbor_value CBOR value item. + * @param[in,out] first_p First top-level/parent sibling. + * @param[out] node_p Pointer to the created opaq node. + * @return LY_ERR value. + */ +static LY_ERR +lydcbor_parse_opaq(struct lyd_cbor_ctx *lydctx, const char *name, size_t name_len, const char *prefix, size_t prefix_len, + struct lyd_node *parent, const cbor_item_t *cbor_value, struct lyd_node **first_p, struct lyd_node **node_p) +{ + LY_ERR ret = LY_SUCCESS; + enum cbor_type type = cbor_typeof(cbor_value); + + LY_CHECK_GOTO(ret = lydcbor_create_opaq(lydctx, name, name_len, prefix, prefix_len, parent, cbor_value, node_p), cleanup); + + assert(*node_p); + LOG_LOCSET(NULL, *node_p); - if (!cbor_isa_map(item)) + if ((type == CBOR_TYPE_ARRAY) && (cbor_array_size(cbor_value) == 1)) + { + cbor_item_t **handle = cbor_array_handle(cbor_value); + if (lydcbor_is_null(handle[0])) { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "List entry must be a CBOR map"); - ret = LY_EVALID; - goto cleanup; + /* special array null value */ + ((struct lyd_node_opaq *)*node_p)->hints |= LYD_VALHINT_EMPTY; + goto finish; } + } - ret = lyd_create_inner(snode, &node); - LY_CHECK_GOTO(ret, cleanup); + if (type == CBOR_TYPE_ARRAY) + { + /* process array */ + size_t array_size = cbor_array_size(cbor_value); + cbor_item_t **array_handle = cbor_array_handle(cbor_value); - if (!node) + for (size_t i = 0; i < array_size; ++i) { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Failed to create list node"); - ret = LY_EVALID; - goto cleanup; - } - lyd_hash(node); - /* Insert the list node */ - ret = lyd_insert_sibling(*first_p, node, first_p); - LY_CHECK_GOTO(ret, cleanup); + const cbor_item_t *item = array_handle[i]; - /* Add to parsed set */ - LY_CHECK_GOTO(ret = ly_set_add(parsed, node, 1, NULL), cleanup); + if (cbor_typeof(item) == CBOR_TYPE_MAP) + { + /* array with objects, list */ + ((struct lyd_node_opaq *)*node_p)->hints |= LYD_NODEHINT_LIST; - /* Parse list entry content */ - struct lyd_node *child_first = NULL; - ret = lydcbor_subtree_r(lydctx, node, &child_first, parsed, item); - LY_CHECK_GOTO(ret, cleanup); + /* process children */ + LY_CHECK_GOTO(ret = lydcbor_subtree_r(lydctx, *node_p, lyd_node_child_p(*node_p), NULL, item), cleanup); + } + else + { + /* array with values, leaf-list */ + ((struct lyd_node_opaq *)*node_p)->hints |= LYD_NODEHINT_LEAFLIST; + } - /* Link children to list entry */ - if (child_first) - { - lyd_insert_child(node, child_first); - } + if (i < array_size - 1) + { + /* continue with next instance */ + assert(*node_p); + lydcbor_maintain_children(parent, first_p, node_p, + lydctx->parse_opts & LYD_PARSE_ORDERED ? LYD_INSERT_NODE_LAST : LYD_INSERT_NODE_DEFAULT, NULL); + + LOG_LOCBACK(0, 1); + + LY_CHECK_GOTO(ret = lydcbor_create_opaq(lydctx, name, name_len, prefix, prefix_len, parent, item, node_p), cleanup); - node = NULL; /* Reset pointer after successful processing */ + assert(*node_p); + LOG_LOCSET(NULL, *node_p); + } + } + } + else if (type == CBOR_TYPE_MAP) + { + ((struct lyd_node_opaq *)*node_p)->hints |= LYD_NODEHINT_CONTAINER; + /* process children */ + LY_CHECK_GOTO(ret = lydcbor_subtree_r(lydctx, *node_p, lyd_node_child_p(*node_p), NULL, cbor_value), cleanup); } +finish: + /* finish linking metadata */ + ret = lydcbor_metadata_finish(lydctx, lyd_node_child_p(*node_p)); + cleanup: - if (ret && node) + if (*node_p) { - lyd_free_tree(node); + LOG_LOCBACK(0, 1); } return ret; } +/** + * @brief Process the attribute container (starting by @). + * + * @param[in] lydctx CBOR data parser context. + * @param[in] attr_node The data node referenced by the attribute container. + * @param[in] snode The schema node of the data node. + * @param[in] name Name of the node. + * @param[in] name_len Length of @p name. + * @param[in] prefix Prefix of the node. + * @param[in] prefix_len Length of @p prefix. + * @param[in] parent Data parent. + * @param[in] cbor_value CBOR value item. + * @param[in,out] first_p First top-level/parent sibling. + * @param[out] node_p Pointer to the created node. + * @return LY_ERR value. + */ static LY_ERR -lydcbor_parse_list(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, - const cbor_item_t *cbor_value, struct lyd_node **first_p, struct ly_set *parsed) +lydcbor_parse_attribute(struct lyd_cbor_ctx *lydctx, struct lyd_node *attr_node, const struct lysc_node *snode, + const char *name, size_t name_len, const char *prefix, size_t prefix_len, struct lyd_node *parent, + const cbor_item_t *cbor_value, struct lyd_node **first_p, struct lyd_node **node_p) { - LY_ERR ret = LY_SUCCESS; + LY_ERR r; + const char *opaq_name, *mod_name, *attr_mod; + size_t opaq_name_len, attr_mod_len; - if (cbor_isa_array(cbor_value)) + if (!attr_node) { - ret = lydcbor_parse_list_array(lydctx, snode, cbor_value, first_p, parsed); - } - else if (cbor_isa_map(cbor_value)) - { - /* Single list entry */ - struct lyd_node *node = NULL; - - ret = lyd_create_inner(snode, &node); - LY_CHECK_RET(ret); - - lyd_hash(node); - /* Insert into tree */ - ret = lyd_insert_sibling(*first_p, node, first_p); - if (ret) + /* learn the attribute module name */ + if (!snode) { - lyd_free_tree(node); - return ret; + if (!prefix) + { + lydcbor_get_node_prefix(parent, NULL, 0, &attr_mod, &attr_mod_len); + } + else + { + attr_mod = prefix; + attr_mod_len = prefix_len; + } } - /* Add to parsed set */ - ret = ly_set_add(parsed, node, 1, NULL); - LY_CHECK_RET(ret); - - /* Parse list entry content */ - struct lyd_node *child_first = NULL; - ret = lydcbor_subtree_r(lydctx, node, &child_first, parsed, cbor_value); - if (ret) + /* try to find the instance */ + LY_LIST_FOR(parent ? lyd_child(parent) : *first_p, attr_node) { - return ret; - } + if (snode) + { + if (attr_node->schema) + { + if (attr_node->schema == snode) + { + break; + } + } + else + { + mod_name = ((struct lyd_node_opaq *)attr_node)->name.module_name; + if (!strcmp(LYD_NAME(attr_node), snode->name) && mod_name && !strcmp(mod_name, snode->module->name)) + { + break; + } + } + } + else + { + if (attr_node->schema) + { + mod_name = attr_node->schema->module->name; + } + else + { + mod_name = ((struct lyd_node_opaq *)attr_node)->name.module_name; + } - /* Link children to list entry */ - if (child_first) - { - lyd_insert_child(node, child_first); + if (!ly_strncmp(LYD_NAME(attr_node), name, name_len) && mod_name && attr_mod && + !ly_strncmp(mod_name, attr_mod, attr_mod_len)) + { + break; + } + } } } - else - { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "List \"%s\" value must be a CBOR map or array", snode->name); - ret = LY_EVALID; - } - return ret; -} + if (!attr_node) + { + /* parse as an opaq node with @ prefix */ + uint32_t prev_opts; -static LY_ERR -lydcbor_parse_any(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, - const cbor_item_t *cbor_value, struct lyd_node **first_p, struct ly_set *parsed) -{ - LY_ERR ret; - struct lyd_node *node = NULL; + prev_opts = lydctx->parse_opts; + lydctx->parse_opts &= ~LYD_PARSE_STRICT; + lydctx->parse_opts |= LYD_PARSE_OPAQ; - ret = lyd_create_any(snode, cbor_value, LYD_ANYDATA_CBOR, 0, &node); - LY_CHECK_RET(ret); + opaq_name = prefix ? prefix - 1 : name - 1; + opaq_name_len = prefix ? prefix_len + name_len + 2 : name_len + 1; + r = lydcbor_parse_opaq(lydctx, opaq_name, opaq_name_len, NULL, 0, parent, cbor_value, first_p, node_p); - if (!node) - { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Failed to create any node for \"%s\"", snode->name); - return LY_EVALID; + lydctx->parse_opts = prev_opts; + LY_CHECK_RET(r); } - - lyd_hash(node); - /* Insert into tree */ - ret = lyd_insert_sibling(*first_p, node, first_p); - if (ret) + else { - lyd_free_tree(node); - return ret; + LY_CHECK_RET(lydcbor_meta_attr(lydctx, snode, attr_node, cbor_value)); } - /* Add to parsed set */ - return ly_set_add(parsed, node, 1, NULL); -} - - -/** - * @brief Parse CBOR metadata/attributes. - * - * @param[in] lydctx CBOR parser context. - * @param[in] cbor_item CBOR item containing metadata. - * @param[in,out] node Data node to attach metadata to. - * @return LY_ERR value. - */ -static LY_ERR -lydcbor_parse_metadata(struct lyd_cbor_ctx *lydctx, const void *cbor_item, struct lyd_node *node) -{ - /* Future implementation for CBOR metadata parsing */ - (void)lydctx; - (void)cbor_item; - (void)node; return LY_SUCCESS; } - /** - * @brief Get module prefix from a qualified name. + * @brief Parse a single anydata/anyxml node from CBOR. * - * @param[in] qname Qualified name (prefix:name or just name). - * @param[in] qname_len Length of the qualified name. - * @param[out] prefix Extracted prefix (points into qname, not allocated). - * @param[out] prefix_len Length of the prefix. - * @param[out] name Local name (points into qname, not allocated). - * @param[out] name_len Length of the local name. - * @return LY_SUCCESS on success. + * @param[in] lydctx CBOR data parser context. + * @param[in] snode Schema node corresponding to the member. + * @param[in] ext Extension instance of @p snode, if any. + * @param[in] cbor_value CBOR value item. + * @param[out] node Parsed data (or opaque) node. + * @return LY_SUCCESS if a node was successfully parsed. + * @return LY_ENOT in case of invalid CBOR encoding. + * @return LY_ERR on other errors. */ static LY_ERR -lydcbor_parse_qname(const char *qname, size_t qname_len, const char **prefix, size_t *prefix_len, - const char **name, size_t *name_len) +lydcbor_parse_any(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, struct lysc_ext_instance *ext, + const cbor_item_t *cbor_value, struct lyd_node **node) { - const char *colon; + LY_ERR r, rc = LY_SUCCESS; + uint32_t prev_parse_opts = lydctx->parse_opts, prev_int_opts = lydctx->int_opts; + struct lyd_node *child = NULL; + ly_bool log_node = 0; + enum cbor_type type = cbor_typeof(cbor_value); - assert(qname && name && name_len); + assert(snode->nodetype & LYD_NODE_ANY); - *name = qname; - *name_len = qname_len; + *node = NULL; - if (prefix) + /* status check according to allowed CBOR types */ + if (snode->nodetype == LYS_ANYXML) { - *prefix = NULL; + LY_CHECK_RET((type != CBOR_TYPE_MAP) && (type != CBOR_TYPE_ARRAY) && (type != CBOR_TYPE_UINT) && + (type != CBOR_TYPE_NEGINT) && (type != CBOR_TYPE_STRING) && (type != CBOR_TYPE_FLOAT_CTRL), + LY_ENOT); } - if (prefix_len) + else { - *prefix_len = 0; + LY_CHECK_RET(type != CBOR_TYPE_MAP, LY_ENOT); } - /* Look for module prefix separator */ - colon = ly_strnchr(qname, ':', qname_len); - if (colon) + /* create any node */ + if (type == CBOR_TYPE_MAP) { - /* We have a module prefix */ - if (prefix) - { - *prefix = qname; - *prefix_len = colon - qname; - } + /* create node */ + r = lyd_create_any(snode, NULL, LYD_ANYDATA_DATATREE, 1, node); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + + assert(*node); + LOG_LOCSET(NULL, *node); + log_node = 1; + + /* parse data tree with correct options */ + lydctx->parse_opts &= ~LYD_PARSE_STRICT; + lydctx->parse_opts |= LYD_PARSE_OPAQ | (ext ? LYD_PARSE_ONLY : 0); + lydctx->int_opts |= LYD_INTOPT_ANY | LYD_INTOPT_WITH_SIBLINGS; + lydctx->any_schema = snode; + + /* process the anydata content */ + r = lydcbor_subtree_r(lydctx, NULL, &child, NULL, cbor_value); + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); - /* Local name starts after the colon */ - *name = colon + 1; - *name_len = qname_len - (colon - qname) - 1; + /* finish linking metadata */ + r = lydcbor_metadata_finish(lydctx, &child); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); - /* Validate we have both prefix and name */ - if ((colon == qname) || (*name_len == 0)) - { - return LY_EVALID; - } + /* assign the data tree */ + ((struct lyd_node_any *)*node)->value.tree = child; + child = NULL; + } + else + { + /* store CBOR value directly */ + r = lyd_create_any(snode, cbor_value, LYD_ANYDATA_CBOR, 0, node); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); } - return LY_SUCCESS; +cleanup: + if (log_node) + { + LOG_LOCBACK(0, 1); + } + lydctx->parse_opts = prev_parse_opts; + lydctx->int_opts = prev_int_opts; + lydctx->any_schema = NULL; + lyd_free_tree(child); + return rc; } /** - * @brief Get schema node from CBOR node name, following lydjson_get_snode logic. + * @brief Parse a single instance of an inner node from CBOR. * - * @param[in] lydctx CBOR parser context. - * @param[in] name Node name. - * @param[in] name_len Length of node name. - * @param[in] parent Data parent node. - * @param[out] snode Schema node found. - * @param[out] ext Extension instance if found. - * @return LY_ERR value. + * @param[in] lydctx CBOR data parser context. + * @param[in] snode Schema node corresponding to the member. + * @param[in] ext Extension instance of @p snode, if any. + * @param[in] cbor_value CBOR value item. + * @param[out] node Parsed data (or opaque) node. + * @return LY_SUCCESS if a node was successfully parsed. + * @return LY_ENOT in case of invalid CBOR encoding. + * @return LY_ERR on other errors. */ static LY_ERR -lydcbor_get_snode(struct lyd_cbor_ctx *lydctx, const char *name, size_t name_len, - struct lyd_node *parent, const struct lysc_node **snode, - const struct lysc_ext_instance **ext) +lydcbor_parse_instance_inner(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, struct lysc_ext_instance *ext, + const cbor_item_t *cbor_value, struct lyd_node **node) { - LY_ERR ret = LY_SUCCESS, r; - const char *prefix = NULL, *local_name = NULL; - size_t prefix_len = 0, local_name_len = 0; - const struct lys_module *mod = NULL; - const struct lysc_node *sparent = NULL; - uint32_t getnext_opts; + LY_ERR r, rc = LY_SUCCESS; + uint32_t prev_parse_opts = lydctx->parse_opts; + + LY_CHECK_RET(cbor_typeof(cbor_value) != CBOR_TYPE_MAP, LY_ENOT); + + /* create inner node */ + LY_CHECK_RET(lyd_create_inner(snode, node)); + + /* use it for logging */ + LOG_LOCSET(NULL, *node); - assert(lydctx && name && snode); - *snode = NULL; if (ext) { - *ext = NULL; + /* only parse these extension data and validate afterwards */ + lydctx->parse_opts |= LYD_PARSE_ONLY; } - /* Parse qualified name */ - LY_CHECK_RET(lydcbor_parse_qname(name, name_len, &prefix, &prefix_len, &local_name, &local_name_len)); + /* process children */ + r = lydcbor_subtree_r(lydctx, *node, lyd_node_child_p(*node), NULL, cbor_value); + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); + + /* finish linking metadata */ + r = lydcbor_metadata_finish(lydctx, lyd_node_child_p(*node)); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); - /* Get parent schema node */ - if (parent) + if (snode->nodetype == LYS_LIST) { - sparent = parent->schema; - if (!sparent) - { - /* Opaque parent */ - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Cannot parse \"%.*s\" node with opaque parent.", - (int)local_name_len, local_name); - ret = LY_EVALID; - goto cleanup; - } + /* check all keys exist */ + r = lyd_parser_check_keys(*node); + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); } - else + + if (!(lydctx->parse_opts & LYD_PARSE_ONLY) && !rc) { - sparent = NULL; + /* new node validation */ + r = lyd_parser_validate_new_implicit((struct lyd_ctx *)lydctx, *node); + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); } - /* Resolve module if prefix is present */ - if (prefix) +cleanup: + lydctx->parse_opts = prev_parse_opts; + LOG_LOCBACK(0, 1); + if (!(*node)->hash) { - mod = ly_ctx_get_module_implemented2(lydctx->cborctx->ctx, prefix, prefix_len); - if (!mod) - { - if (lydctx->parse_opts & LYD_PARSE_STRICT) - { - LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, "Unknown module \"%.*s\".", (int)prefix_len, prefix); - ret = LY_EVALID; - goto cleanup; - } - if (!(lydctx->parse_opts & LYD_PARSE_OPAQ)) - { - LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, "Unknown module \"%.*s\".", (int)prefix_len, prefix); - ret = LY_EVALID; - goto cleanup; - } - } + /* list without keys is unusable */ + lyd_free_tree(*node); + *node = NULL; } - else if (!sparent) - { - /* Top-level node without prefix - need to find module */ - /* Try to find the node in all implemented modules */ - const struct lys_module *iter_mod; - uint32_t idx = 0; - ly_bool found = 0; - - while ((iter_mod = ly_ctx_get_module_iter(lydctx->cborctx->ctx, &idx))) - { - if (!iter_mod->implemented) - { - continue; - } + return rc; +} - /* Check if node exists in this module */ - if (lys_find_child(NULL, iter_mod, local_name, local_name_len, 0, 0)) - { - if (found) - { - /* Ambiguous name */ - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Ambiguous node name \"%.*s\", use module prefix.", - (int)local_name_len, local_name); - ret = LY_EVALID; - goto cleanup; - } - mod = iter_mod; - found = 1; - } - } +/** + * @brief Parse a single instance of a node from CBOR. + * + * @param[in] lydctx CBOR data parser context. + * @param[in] parent Data parent of the subtree. + * @param[in,out] first_p Pointer to the variable holding the first top-level sibling. + * @param[in] snode Schema node corresponding to the member. + * @param[in] ext Extension instance of @p snode, if any. + * @param[in] name Parsed CBOR node name. + * @param[in] name_len Length of @p name. + * @param[in] prefix Parsed CBOR node prefix. + * @param[in] prefix_len Length of @p prefix. + * @param[in] cbor_value CBOR value item. + * @param[out] node Parsed data (or opaque) node. + * @return LY_SUCCESS if a node was successfully parsed. + * @return LY_ENOT in case of invalid CBOR encoding. + * @return LY_ERR on other errors. + */ +static LY_ERR +lydcbor_parse_instance(struct lyd_cbor_ctx *lydctx, struct lyd_node *parent, struct lyd_node **first_p, + const struct lysc_node *snode, struct lysc_ext_instance *ext, const char *name, size_t name_len, + const char *prefix, size_t prefix_len, const cbor_item_t *cbor_value, struct lyd_node **node) +{ + LY_ERR r, rc = LY_SUCCESS; + uint32_t type_hints = 0; + char *str_val = NULL; + size_t str_len = 0; - if (!found && !(lydctx->parse_opts & LYD_PARSE_OPAQ)) - { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Unknown node \"%.*s\".", (int)local_name_len, local_name); - ret = LY_EVALID; - goto cleanup; - } - } + LOG_LOCSET(snode, NULL); - /* Set getnext options */ - getnext_opts = lydctx->int_opts & LYD_INTOPT_REPLY ? LYS_GETNEXT_OUTPUT : 0; - if (parent && (parent->schema->nodetype & (LYS_RPC | LYS_ACTION))) + r = lydcbor_data_check_opaq(lydctx, snode, cbor_value, &type_hints); + if (r == LY_SUCCESS) { - if (lydctx->int_opts & LYD_INTOPT_RPC) + assert(snode->nodetype & (LYD_NODE_TERM | LYD_NODE_INNER | LYD_NODE_ANY)); + if (lydcbor_is_null(cbor_value)) { - getnext_opts = 0; + /* do not do anything if value is CBOR null */ + goto cleanup; } - else if (lydctx->int_opts & LYD_INTOPT_REPLY) + else if (snode->nodetype & LYD_NODE_TERM) { - getnext_opts = LYS_GETNEXT_OUTPUT; - } - } - - /* Find schema node */ - if (sparent) - { - /* Search in parent's children */ - *snode = lys_find_child(sparent, sparent->module, local_name, local_name_len, 0, getnext_opts); + enum cbor_type type = cbor_typeof(cbor_value); - /* Try to find extension data if regular node not found */ - if (!*snode && ext) - { - r = ly_nested_ext_schema(parent, sparent, prefix, prefix_len, LY_VALUE_JSON, NULL, - local_name, local_name_len, snode, ext); - if (r != LY_ENOT) + if ((type == CBOR_TYPE_ARRAY) && (cbor_array_size(cbor_value) == 1)) { - if (r) + cbor_item_t **handle = cbor_array_handle(cbor_value); + if (lydcbor_is_null(handle[0])) { - ret = r; + /* [null] case */ goto cleanup; } } - } - } - else - { - /* Top-level node */ - if (mod) - { - /* Search in specific module */ - *snode = lys_find_child(NULL, mod, local_name, local_name_len, 0, getnext_opts); - } - /* Extension data for top-level not typically handled */ - } - /* Handle missing schema node */ - if (!*snode) - { - if (lydctx->parse_opts & LYD_PARSE_STRICT) - { - if (prefix) + if ((type != CBOR_TYPE_ARRAY) && (type != CBOR_TYPE_UINT) && (type != CBOR_TYPE_NEGINT) && + (type != CBOR_TYPE_STRING) && (type != CBOR_TYPE_FLOAT_CTRL)) { - LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, "Unknown element \"%.*s\" in module \"%.*s\".", - (int)local_name_len, local_name, (int)prefix_len, prefix); - } - else - { - LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, "Unknown element \"%.*s\".", - (int)local_name_len, local_name); + rc = LY_ENOT; + goto cleanup; } - ret = LY_EVALID; - goto cleanup; + + /* create terminal node */ + r = lydcbor_item_to_string(cbor_value, &str_val, &str_len); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + + r = lyd_parser_create_term((struct lyd_ctx *)lydctx, snode, str_val, str_len, NULL, LY_VALUE_CBOR, + NULL, type_hints, node); + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); } - else if (!(lydctx->parse_opts & LYD_PARSE_OPAQ)) + else if (snode->nodetype & LYD_NODE_INNER) { - /* Log error but continue if not in strict mode and opaque allowed */ - if (prefix) - { - LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, "Unknown element \"%.*s\" in module \"%.*s\".", - (int)local_name_len, local_name, (int)prefix_len, prefix); - } - else - { - LOGVAL(lydctx->cborctx->ctx, LYVE_REFERENCE, "Unknown element \"%.*s\".", - (int)local_name_len, local_name); - } - ret = LY_EVALID; - goto cleanup; + /* create inner node */ + r = lydcbor_parse_instance_inner(lydctx, snode, ext, cbor_value, node); + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); } - /* If opaque parsing allowed, *snode remains NULL and caller handles it */ - } - -cleanup: - return ret; -} - -static LY_ERR -lydcbor_parse_leaflist_array(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, - const cbor_item_t *array_item, struct lyd_node **first_p, struct ly_set *parsed) -{ - LY_ERR ret = LY_SUCCESS; - struct lyd_node *node = NULL; - size_t array_size; - cbor_item_t **array_handle; - - assert(lydctx && snode && array_item && parsed); - - if (!cbor_isa_array(array_item)) - { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Expected CBOR array for leaf-list"); - return LY_EVALID; - } - - if (snode->nodetype != LYS_LEAFLIST) - { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Schema node must be leaf-list"); - return LY_EVALID; - } + else + { + /* create any node */ + r = lydcbor_parse_any(lydctx, snode, ext, cbor_value, node); + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); + } + LY_CHECK_GOTO(!*node, cleanup); - array_size = cbor_array_size(array_item); - array_handle = cbor_array_handle(array_item); + /* add/correct flags */ + r = lyd_parser_set_data_flags(*node, &(*node)->meta, (struct lyd_ctx *)lydctx, ext); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); - if (!array_handle && array_size > 0) - { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Invalid CBOR array structure"); - return LY_EVALID; + if (!(lydctx->parse_opts & LYD_PARSE_ONLY)) + { + /* store for ext instance node validation, if needed */ + r = lyd_validate_node_ext(*node, &lydctx->ext_node); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + } } - - for (size_t i = 0; i < array_size; ++i) + else if (r == LY_ENOT) { - const cbor_item_t *item = array_handle[i]; + /* parse it again as an opaq node */ + r = lydcbor_parse_opaq(lydctx, name, name_len, prefix, prefix_len, parent, cbor_value, first_p, node); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); - if (!item) + if (snode->nodetype == LYS_LIST) { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Null array element at index %zu", i); - ret = LY_EVALID; - goto cleanup; + ((struct lyd_node_opaq *)*node)->hints |= LYD_NODEHINT_LIST; } - - LY_CHECK_GOTO(ret = lydcbor_parse_node_value(lydctx, snode, &node, item), cleanup); - - if (!node) + else if (snode->nodetype == LYS_LEAFLIST) { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Failed to create node for array element %zu", i); - ret = LY_EVALID; - goto cleanup; + ((struct lyd_node_opaq *)*node)->hints |= LYD_NODEHINT_LEAFLIST; } - - lyd_hash(node); - /* Insert the node */ - ret = lyd_insert_sibling(*first_p, node, first_p); - LY_CHECK_GOTO(ret, cleanup); - - /* Add to parsed set */ - LY_CHECK_GOTO(ret = ly_set_add(parsed, node, 1, NULL), cleanup); - node = NULL; /* Reset pointer after successful insertion */ } - -cleanup: - if (ret && node) + else { - lyd_free_tree(node); + /* error */ + rc = r; + goto cleanup; } - return ret; + +cleanup: + free(str_val); + LOG_LOCBACK(1, 0); + return rc; } +/** + * @brief Parse CBOR subtree. All leaf-list and list instances of a node are considered one subtree. + * + * @param[in] lydctx CBOR data parser context. + * @param[in] parent Data parent of the subtree, must be set if @p first_p is not. + * @param[in,out] first_p Pointer to the variable holding the first top-level sibling. + * @param[in,out] parsed Optional set to add all the parsed siblings into. + * @return LY_ERR value. + */ static LY_ERR -lydcbor_subtree_r(struct lyd_cbor_ctx *lydctx, struct lyd_node *parent, - struct lyd_node **first_p, struct ly_set *parsed, const cbor_item_t *cbor_obj) +lydcbor_subtree_r(struct lyd_cbor_ctx *lydctx, struct lyd_node *parent, struct lyd_node **first_p, + struct ly_set *parsed, const cbor_item_t *cbor_obj) { - LY_ERR ret = LY_SUCCESS; - - printf("Entering lydcbor_subtree_r\n"); - printf("CBOR object:\n"); - print_json(cbor_obj); - printf("\n"); - + LY_ERR r, rc = LY_SUCCESS; + const char *name, *prefix = NULL, *expected = NULL; + size_t name_len, prefix_len = 0; + ly_bool is_meta = 0; const struct lysc_node *snode = NULL; - char *key_str = NULL; - size_t key_len = 0; + struct lysc_ext_instance *ext = NULL; + struct lyd_node *node = NULL, *attr_node = NULL; + const struct ly_ctx *ctx = lydctx->cborctx->ctx; + struct cbor_pair *pairs; + size_t map_size; - assert(lydctx && first_p && parsed && cbor_obj); + assert(parent || first_p); + assert(cbor_typeof(cbor_obj) == CBOR_TYPE_MAP); - size_t map_size = cbor_map_size(cbor_obj); - struct cbor_pair *pairs = cbor_map_handle(cbor_obj); + map_size = cbor_map_size(cbor_obj); + pairs = cbor_map_handle(cbor_obj); if (!pairs && map_size > 0) { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Invalid CBOR map structure"); + LOGVAL(ctx, LYVE_SYNTAX, "Invalid CBOR map structure"); return LY_EVALID; } + /* process all members */ for (size_t i = 0; i < map_size; ++i) { const cbor_item_t *key_item = pairs[i].key; const cbor_item_t *value_item = pairs[i].value; + char *key_str = NULL; + size_t key_len = 0; if (!key_item || !value_item) { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Null key or value at map index %zu", i); - ret = LY_EVALID; + LOGVAL(ctx, LYVE_SYNTAX, "Null key or value in CBOR map"); + rc = LY_EVALID; goto cleanup; } - if (lydcbor_is_null(value_item)) { - // Skip null values - don't create any nodes - continue; // or return LY_SUCCESS depending on your loop structure + /* Skip null values */ + if (lydcbor_is_null(value_item)) + { + continue; } /* Get key string */ - LY_CHECK_GOTO(ret = lydcbor_get_key_string(lydctx, key_item, &key_str, &key_len), cleanup); + if (!cbor_isa_string(key_item)) + { + LOGVAL(ctx, LYVE_SYNTAX, "CBOR map key must be string for named identifier format"); + rc = LY_EVALID; + goto cleanup; + } + + LY_CHECK_ERR_GOTO(rc = lydcbor_item_to_string(key_item, &key_str, &key_len), free(key_str), cleanup); - /* Find schema node */ - LY_CHECK_GOTO(ret = lydcbor_get_snode(lydctx, key_str, key_len, - parent, &snode, NULL), - cleanup); + /* process the node name */ + lydcbor_parse_name(key_str, key_len, &name, &name_len, &prefix, &prefix_len, &is_meta); - /* Handle different node types */ - if (snode->nodetype & (LYS_LEAF | LYS_LEAFLIST)) + if (!is_meta || name_len || prefix_len) { - if (snode->nodetype == LYS_LEAFLIST && cbor_isa_array(value_item)) + /* get the schema node */ + r = lydcbor_get_snode(lydctx, is_meta, prefix, prefix_len, name, name_len, parent, &snode, &ext); + if (r == LY_ENOT) { - ret = lydcbor_parse_leaflist_array(lydctx, snode, value_item, first_p, parsed); + free(key_str); + continue; } - else + else if ((r == LY_EVALID) && (lydctx->val_opts & LYD_VALIDATE_MULTI_ERROR)) + { + rc = r; + free(key_str); + continue; + } + else if (r) { - ret = lydcbor_parse_terminal(lydctx, snode, value_item, first_p, parsed); + rc = r; + free(key_str); + goto cleanup; } } - else if (snode->nodetype == LYS_CONTAINER) + + if (is_meta) { - ret = lydcbor_parse_container(lydctx, snode, value_item, first_p, parsed); + /* parse as metadata */ + if (!name_len && !prefix_len && !parent) + { + LOGVAL(ctx, LYVE_SYNTAX, + "Invalid metadata format - \"@\" can be used only inside anydata, container or list entries."); + r = LY_EVALID; + free(key_str); + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); + } + else if (!name_len && !prefix_len) + { + /* parent's metadata without a name */ + attr_node = parent; + snode = attr_node->schema; + } + r = lydcbor_parse_attribute(lydctx, attr_node, snode, name, name_len, prefix, prefix_len, parent, + value_item, first_p, &node); + free(key_str); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); } - else if (snode->nodetype == LYS_LIST) + else if (!snode) { - ret = lydcbor_parse_list(lydctx, snode, value_item, first_p, parsed); + if (!(lydctx->parse_opts & LYD_PARSE_OPAQ)) + { + /* skip element */ + free(key_str); + continue; + } + else + { + /* parse as an opaq node */ + if (name_len == 0) + { + LOGVAL(ctx, LYVE_SYNTAX, "CBOR object member name cannot be a zero-length string."); + r = LY_EVALID; + free(key_str); + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); + } + + /* parse opaq */ + r = lydcbor_parse_opaq(lydctx, name, name_len, prefix, prefix_len, parent, value_item, first_p, &node); + free(key_str); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + } } - else if (snode->nodetype & (LYS_ANYDATA | LYS_ANYXML)) + else { - ret = lydcbor_parse_any(lydctx, snode, value_item, first_p, parsed); + /* parse as a standard lyd_node but it can still turn out to be an opaque node */ + + /* set expected representation */ + switch (snode->nodetype) + { + case LYS_LEAFLIST: + expected = "name/array of values"; + break; + case LYS_LIST: + expected = "name/array of objects"; + break; + case LYS_LEAF: + expected = "name/value"; + break; + case LYS_CONTAINER: + case LYS_NOTIF: + case LYS_ACTION: + case LYS_RPC: + case LYS_ANYDATA: + expected = "name/object"; + break; + case LYS_ANYXML: + expected = "name/value"; + break; + } + + /* check the representation and process */ + enum cbor_type value_type = cbor_typeof(value_item); + + switch (snode->nodetype) + { + case LYS_LEAFLIST: + case LYS_LIST: + if (value_type != CBOR_TYPE_ARRAY) + { + goto representation_error; + } + + /* process all values/objects in array */ + size_t array_size = cbor_array_size(value_item); + cbor_item_t **array_handle = cbor_array_handle(value_item); + + for (size_t j = 0; j < array_size; ++j) + { + const cbor_item_t *item = array_handle[j]; + + r = lydcbor_parse_instance(lydctx, parent, first_p, snode, ext, name, name_len, prefix, prefix_len, + item, &node); + if (r == LY_ENOT) + { + free(key_str); + goto representation_error; + } + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); + + lydcbor_maintain_children(parent, first_p, &node, + lydctx->parse_opts & LYD_PARSE_ORDERED ? LYD_INSERT_NODE_LAST : LYD_INSERT_NODE_DEFAULT, ext); + } + break; + case LYS_LEAF: + case LYS_CONTAINER: + case LYS_NOTIF: + case LYS_ACTION: + case LYS_RPC: + case LYS_ANYDATA: + case LYS_ANYXML: + /* process the value/object */ + r = lydcbor_parse_instance(lydctx, parent, first_p, snode, ext, name, name_len, prefix, prefix_len, + value_item, &node); + if (r == LY_ENOT) + { + free(key_str); + goto representation_error; + } + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); + + if (snode->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF)) + { + /* remember the RPC/action/notification */ + lydctx->op_node = node; + } + break; + } } - else + + free(key_str); + + /* remember a successfully parsed node */ + if (parsed && node) { - LOGVAL(lydctx->cborctx->ctx, LYVE_SYNTAX, "Invalid schema node type %d for \"%s\"", - snode->nodetype, snode->name); - ret = LY_EVALID; + ly_set_add(parsed, node, 1, NULL); } - LY_CHECK_GOTO(ret, cleanup); + /* finally connect the parsed node */ + lydcbor_maintain_children(parent, first_p, &node, + lydctx->parse_opts & LYD_PARSE_ORDERED ? LYD_INSERT_NODE_LAST : LYD_INSERT_NODE_DEFAULT, ext); + } - free(key_str); - key_str = NULL; + /* success */ + goto cleanup; + +representation_error: + LOGVAL(ctx, LYVE_SYNTAX, "Expecting CBOR %s but %s \"%s\" is represented in input data differently.", + expected, lys_nodetype2str(snode->nodetype), snode->name); + rc = LY_EVALID; + if (lydctx->val_opts & LYD_VALIDATE_MULTI_ERROR) + { + /* try to skip the invalid data */ + if ((r = lydcbor_data_skip(lydctx->cborctx))) + { + rc = r; + } } cleanup: - free(key_str); - return ret; + lyd_free_tree(node); + return rc; } /** - * @brief Create a new CBOR parser context. + * @brief Common start of CBOR parser processing different types of input data. * * @param[in] ctx libyang context. - * @param[in] ext Extension instance providing context for the top level element, NULL if none. - * @param[in] parse_opts Parse options, see @ref dataparseroptions. - * @param[in] val_opts Validation options, see @ref datavalidationoptions. - * @param[in] format CBOR format variant (named or SID). - * @param[out] lydctx_p Pointer to the created CBOR parser context. + * @param[in] in Input structure. + * @param[in] parse_opts Options for parser. + * @param[in] val_opts Options for validation phase. + * @param[out] lydctx_p Data parser context to finish validation. * @return LY_ERR value. */ static LY_ERR -lydcbor_ctx_init(const struct ly_ctx *ctx, struct ly_in *in, uint32_t parse_opts, - uint32_t val_opts, struct lyd_cbor_ctx **lydctx_p) +lyd_parse_cbor_init(const struct ly_ctx *ctx, struct ly_in *in, uint32_t parse_opts, uint32_t val_opts, + struct lyd_cbor_ctx **lydctx_p) { LY_ERR ret = LY_SUCCESS; struct lyd_cbor_ctx *lydctx; @@ -1162,19 +1998,14 @@ lydcbor_ctx_init(const struct ly_ctx *ctx, struct ly_in *in, uint32_t parse_opts lydctx->val_opts = val_opts; lydctx->free = lyd_cbor_ctx_free; - /* Create low-level CBOR context (includes CBOR parsing) */ - LY_CHECK_ERR_RET(ret = lycbor_ctx_new(ctx, in, &lydctx->cborctx), free(lydctx), ret); + /* Create low-level CBOR context */ + LY_CHECK_ERR_RET(ret = lycbor_ctx_new(ctx, in, &lydctx->cborctx), free(lydctx), ret); cbortype = cbor_typeof(lydctx->cborctx->cbor_data); - /* assuming that the top level structure is always a map - - though this is not mentioned explicitly in RFC9254 - it is implied - and it is almost always the case - This is a similar assumption made - to the RFC 7951 where JSON Encoding of data modeled by YANG is always assumed - to a have a top-level structure as an object */ if (!cbor_isa_map(lydctx->cborctx->cbor_data)) { /* expecting top-level map */ - LOGVAL(ctx, LYVE_SYNTAX_CBOR, "Expected top-level CBOR map, but %s found.", lycbor_token2str(cbortype)); + LOGVAL(ctx, LYVE_SYNTAX, "Expected top-level CBOR map, but %d found.", cbortype); *lydctx_p = NULL; lyd_cbor_ctx_free((struct lyd_ctx *)lydctx); return LY_EVALID; @@ -1191,40 +2022,67 @@ lyd_parse_cbor(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, st { LY_ERR r, rc = LY_SUCCESS; struct lyd_cbor_ctx *lydctx = NULL; - printf("Entering lyd_parse_cbor\n AHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH\n"); - /* Initialize context (CBOR parsing happens in lycbor_ctx_new) */ - rc = lydcbor_ctx_init(ctx, in, parse_opts, val_opts, &lydctx); + rc = lyd_parse_cbor_init(ctx, in, parse_opts, val_opts, &lydctx); LY_CHECK_GOTO(rc, cleanup); lydctx->int_opts = int_opts; lydctx->ext = ext; + // DEBUG: print the parsed CBOR data + printf("DEBUG: Parsed CBOR data:\n"); + print_json(lydctx->cborctx->cbor_data); + printf("\n"); + // END DEBUG + /* find the operation node if it exists already */ LY_CHECK_GOTO(rc = lyd_parser_find_operation(parent, int_opts, &lydctx->op_node), cleanup); - /* Parse the CBOR structure - read subtrees */ + /* read subtree(s) */ r = lydcbor_subtree_r(lydctx, parent, first_p, parsed, lydctx->cborctx->cbor_data); LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); - /* Unexpected sibling node error handling */ - - /* Validate operation node presence */ - if ((int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_ACTION | LYD_INTOPT_NOTIF | LYD_INTOPT_REPLY)) && - !lydctx->op_node) { + if ((int_opts & LYD_INTOPT_NO_SIBLINGS)) + { + LOGVAL(ctx, LYVE_SYNTAX, "Unexpected sibling node."); + r = LY_EVALID; + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); + } + if ((int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_ACTION | LYD_INTOPT_NOTIF | LYD_INTOPT_REPLY)) && !lydctx->op_node) + { LOGVAL(ctx, LYVE_DATA, "Missing the operation node."); r = LY_EVALID; LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); } - /* also need to deal with metadata linking etc*/ + + /* finish linking metadata */ + r = lydcbor_metadata_finish(lydctx, parent ? lyd_node_child_p(parent) : first_p); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + + if (parse_opts & LYD_PARSE_SUBTREE) + { + /* subtree parsing not fully implemented for CBOR */ + if (subtree_sibling) + { + *subtree_sibling = 0; + } + } cleanup: - if (rc && (!lydctx || !(lydctx->val_opts & LYD_VALIDATE_MULTI_ERROR) || (rc != LY_EVALID))) { + /* there should be no unresolved types stored */ + assert(!(parse_opts & LYD_PARSE_ONLY) || !lydctx || (!lydctx->node_types.count && !lydctx->meta_types.count && !lydctx->node_when.count)); + + if (rc && (!lydctx || !(lydctx->val_opts & LYD_VALIDATE_MULTI_ERROR) || (rc != LY_EVALID))) + { lyd_cbor_ctx_free((struct lyd_ctx *)lydctx); lydctx = NULL; } - - *lydctx_p = (struct lyd_ctx *)lydctx; + else + { + *lydctx_p = (struct lyd_ctx *)lydctx; + lycbor_ctx_free(lydctx->cborctx); + lydctx->cborctx = NULL; + } return rc; } diff --git a/src/parser_json.c b/src/parser_json.c index af0ec64b6..c0dc01568 100644 --- a/src/parser_json.c +++ b/src/parser_json.c @@ -307,6 +307,7 @@ lydjson_get_snode(struct lyd_json_ctx *lydctx, ly_bool is_attr, const char *pref } /* unknown data node */ + printf("checkpoint1-json\n"); if (lydctx->parse_opts & LYD_PARSE_STRICT) { if (parent) { LOGVAL(lydctx->jsonctx->ctx, LYVE_REFERENCE, "Node \"%.*s\" not found as a child of \"%s\" node.", diff --git a/src/path.c b/src/path.c index d3cea0d7a..0c6ea0662 100644 --- a/src/path.c +++ b/src/path.c @@ -597,6 +597,7 @@ ly_path_compile_snode(const struct ly_ctx *ctx, const struct lysc_node *cur_node /* use current module */ mod = cur_mod; break; + case LY_VALUE_CBOR: case LY_VALUE_JSON: case LY_VALUE_LYB: if (!prev_ctx_node) { diff --git a/src/plugins_types.c b/src/plugins_types.c index fb6f75a7e..90aee291f 100644 --- a/src/plugins_types.c +++ b/src/plugins_types.c @@ -137,6 +137,7 @@ ly_resolve_prefix(const struct ly_ctx *ctx, const void *prefix, size_t prefix_le mod = ly_xml_resolve_prefix(ctx, prefix, prefix_len, prefix_data); break; case LY_VALUE_CANON: + case LY_VALUE_CBOR: case LY_VALUE_JSON: case LY_VALUE_LYB: mod = ly_json_resolve_prefix(ctx, prefix, prefix_len, prefix_data); @@ -161,6 +162,7 @@ lyplg_type_identity_module(const struct ly_ctx *ctx, const struct lysc_node *ctx /* use local module */ return ly_schema_resolved_resolve_prefix(ctx, prefix, prefix_len, prefix_data); case LY_VALUE_CANON: + case LY_VALUE_CBOR: case LY_VALUE_JSON: case LY_VALUE_LYB: case LY_VALUE_STR_NS: @@ -286,6 +288,7 @@ ly_get_prefix(const struct lys_module *mod, LY_VALUE_FORMAT format, void *prefix prefix = ly_xml_get_prefix(mod, prefix_data); break; case LY_VALUE_CANON: + case LY_VALUE_CBOR: case LY_VALUE_JSON: case LY_VALUE_LYB: prefix = ly_json_get_prefix(mod, prefix_data); @@ -837,6 +840,7 @@ lyplg_type_lypath_new(const struct ly_ctx *ctx, const char *value, size_t value_ break; case LY_VALUE_CANON: case LY_VALUE_LYB: + case LY_VALUE_CBOR: case LY_VALUE_JSON: case LY_VALUE_STR_NS: prefix_opt = LY_PATH_PREFIX_STRICT_INHERIT; diff --git a/src/plugins_types/instanceid.c b/src/plugins_types/instanceid.c index 46bb4baef..3c7bad561 100644 --- a/src/plugins_types/instanceid.c +++ b/src/plugins_types/instanceid.c @@ -69,6 +69,7 @@ instanceid_path2str(const struct ly_path *path, LY_VALUE_FORMAT format, void *pr inherit_prefix = 0; break; case LY_VALUE_CANON: + case LY_VALUE_CBOR: case LY_VALUE_JSON: case LY_VALUE_LYB: case LY_VALUE_STR_NS: diff --git a/src/plugins_types/instanceid_keys.c b/src/plugins_types/instanceid_keys.c index 12d28e5d4..5e4758ef3 100644 --- a/src/plugins_types/instanceid_keys.c +++ b/src/plugins_types/instanceid_keys.c @@ -190,6 +190,7 @@ lyplg_type_store_instanceid_keys(const struct ly_ctx *ctx, const struct lysc_typ switch (format) { case LY_VALUE_CANON: + case LY_VALUE_CBOR: case LY_VALUE_JSON: case LY_VALUE_LYB: case LY_VALUE_STR_NS: diff --git a/src/plugins_types/node_instanceid.c b/src/plugins_types/node_instanceid.c index ab141d5f3..86fd484a2 100644 --- a/src/plugins_types/node_instanceid.c +++ b/src/plugins_types/node_instanceid.c @@ -75,6 +75,7 @@ node_instanceid_path2str(const struct ly_path *path, LY_VALUE_FORMAT format, voi inherit_prefix = 0; break; case LY_VALUE_CANON: + case LY_VALUE_CBOR: case LY_VALUE_JSON: case LY_VALUE_LYB: case LY_VALUE_STR_NS: @@ -202,6 +203,7 @@ lyplg_type_store_node_instanceid(const struct ly_ctx *ctx, const struct lysc_typ break; case LY_VALUE_CANON: case LY_VALUE_LYB: + case LY_VALUE_CBOR: case LY_VALUE_JSON: case LY_VALUE_STR_NS: prefix_opt = LY_PATH_PREFIX_STRICT_INHERIT; diff --git a/src/plugins_types/xpath1.0.c b/src/plugins_types/xpath1.0.c index 9376351ef..01d055ab1 100644 --- a/src/plugins_types/xpath1.0.c +++ b/src/plugins_types/xpath1.0.c @@ -291,6 +291,7 @@ lyplg_type_store_xpath10(const struct ly_ctx *ctx, const struct lysc_type *type, switch (format) { case LY_VALUE_CANON: + case LY_VALUE_CBOR: case LY_VALUE_JSON: case LY_VALUE_LYB: case LY_VALUE_STR_NS: diff --git a/src/printer_json.c b/src/printer_json.c index 233bc0254..59d67288a 100644 --- a/src/printer_json.c +++ b/src/printer_json.c @@ -156,6 +156,7 @@ node_prefix(const struct lyd_node *node) const struct lys_module *mod; switch (onode->format) { + case LY_VALUE_CBOR: case LY_VALUE_JSON: return onode->name.module_name; case LY_VALUE_XML: @@ -303,6 +304,7 @@ json_print_member2(struct jsonpr_ctx *pctx, const struct lyd_node *parent, LY_VA /* determine prefix string */ if (name) { switch (format) { + case LY_VALUE_CBOR: case LY_VALUE_JSON: module_name = name->module_name; break; diff --git a/src/printer_lyb.c b/src/printer_lyb.c index f13ef1593..3434ec68d 100644 --- a/src/printer_lyb.c +++ b/src/printer_lyb.c @@ -593,6 +593,7 @@ lyb_print_prefix_data(struct ly_out *out, LY_VALUE_FORMAT format, const void *pr LY_CHECK_RET(lyb_write_string(ns->uri, 0, sizeof(uint16_t), out, lybctx)); } break; + case LY_VALUE_CBOR: case LY_VALUE_JSON: case LY_VALUE_LYB: /* nothing to print */ diff --git a/src/printer_xml.c b/src/printer_xml.c index 3e11fe14b..834ba45cd 100644 --- a/src/printer_xml.c +++ b/src/printer_xml.c @@ -128,6 +128,7 @@ xml_print_ns_opaq(struct xmlpr_ctx *pctx, LY_VALUE_FORMAT format, const struct l return xml_print_ns(pctx, name->module_ns, (prefix_opts & LYXML_PREFIX_DEFAULT) ? NULL : name->prefix, prefix_opts); } break; + case LY_VALUE_CBOR: case LY_VALUE_JSON: if (name->module_name) { mod = ly_ctx_get_module_latest(pctx->ctx, name->module_name); diff --git a/src/schema_compile_node.c b/src/schema_compile_node.c index 5498b208e..0fff33305 100644 --- a/src/schema_compile_node.c +++ b/src/schema_compile_node.c @@ -3216,6 +3216,7 @@ lysc_resolve_schema_nodeid(struct lysc_ctx *ctx, const char *nodeid, size_t node /* use the current module */ mod = ctx->cur_mod; break; + case LY_VALUE_CBOR: case LY_VALUE_JSON: case LY_VALUE_LYB: if (!ctx_node) { diff --git a/src/tree.h b/src/tree.h index 9e2b7fa8d..309c6cb1e 100644 --- a/src/tree.h +++ b/src/tree.h @@ -237,7 +237,7 @@ typedef enum { LY_VALUE_SCHEMA_RESOLVED, /**< resolved YANG schema value, prefixes map to module structures directly */ LY_VALUE_XML, /**< XML data value, prefixes map to XML namespace prefixes */ LY_VALUE_JSON, /**< JSON data value, prefixes map to module names */ - LY_VALUE_CBOR, /**< CBOR data value, prefixes map to module names */ + LY_VALUE_CBOR, /**< CBOR data value, prefixes map to module names (same as JSON) */ LY_VALUE_LYB, /**< LYB data binary value, prefix mapping is type-specific (but usually like JSON) */ LY_VALUE_STR_NS /**< any data format value, prefixes map to XML namespace prefixes */ } LY_VALUE_FORMAT; diff --git a/src/tree_data_common.c b/src/tree_data_common.c index 96a6e68ad..2cf945e11 100644 --- a/src/tree_data_common.c +++ b/src/tree_data_common.c @@ -315,6 +315,7 @@ lyd_owner_module(const struct lyd_node *node) return ly_ctx_get_module_implemented_ns(LYD_CTX(node), opaq->name.module_ns); } break; + case LY_VALUE_CBOR: case LY_VALUE_JSON: if (opaq->name.module_name) { return ly_ctx_get_module_implemented(LYD_CTX(node), opaq->name.module_name); @@ -349,6 +350,7 @@ lyd_node_module(const struct lyd_node *node) return ly_ctx_get_module_implemented_ns(LYD_CTX(node), opaq->name.module_ns); } break; + case LY_VALUE_CBOR: case LY_VALUE_JSON: if (opaq->name.module_name) { return ly_ctx_get_module_implemented(LYD_CTX(node), opaq->name.module_name); @@ -900,6 +902,7 @@ lyd_parse_opaq_error(const struct lyd_node *node) mod = sparent->module; } break; + case LY_VALUE_CBOR: case LY_VALUE_JSON: case LY_VALUE_LYB: if (!sparent || strcmp(opaq->name.module_name, sparent->module->name)) { @@ -1390,6 +1393,7 @@ ly_free_prefix_data(LY_VALUE_FORMAT format, void *prefix_data) break; case LY_VALUE_CANON: case LY_VALUE_SCHEMA: + case LY_VALUE_CBOR: case LY_VALUE_JSON: case LY_VALUE_LYB: break; @@ -1449,6 +1453,7 @@ ly_dup_prefix_data(const struct ly_ctx *ctx, LY_VALUE_FORMAT format, const void } break; case LY_VALUE_CANON: + case LY_VALUE_CBOR: case LY_VALUE_JSON: case LY_VALUE_LYB: assert(!prefix_data); @@ -1574,6 +1579,7 @@ ly_store_prefix_data(const struct ly_ctx *ctx, const void *value, size_t value_l break; case LY_VALUE_CANON: case LY_VALUE_SCHEMA_RESOLVED: + case LY_VALUE_CBOR: case LY_VALUE_JSON: case LY_VALUE_LYB: if (!*prefix_data_p) { @@ -1604,6 +1610,8 @@ ly_format2str(LY_VALUE_FORMAT format) return "schema stored mapping"; case LY_VALUE_XML: return "XML prefixes"; + case LY_VALUE_CBOR: + return "CBOR module names"; case LY_VALUE_JSON: return "JSON module names"; case LY_VALUE_LYB: diff --git a/src/tree_data_new.c b/src/tree_data_new.c index d8341175a..c117b7730 100644 --- a/src/tree_data_new.c +++ b/src/tree_data_new.c @@ -1069,6 +1069,7 @@ lyd_new_meta2(const struct ly_ctx *ctx, struct lyd_node *parent, uint32_t option return LY_ENOTFOUND; } break; + case LY_VALUE_CBOR: case LY_VALUE_JSON: mod = ly_ctx_get_module_implemented(ctx, attr->name.module_name); if (!mod) { diff --git a/src/tree_schema_internal.h b/src/tree_schema_internal.h index 974c99664..890a5fef0 100644 --- a/src/tree_schema_internal.h +++ b/src/tree_schema_internal.h @@ -623,6 +623,7 @@ char *lysc_path_until(const struct lysc_node *node, const struct lysc_node *pare * LY_VALUE_SCHEMA - const struct ::lysp_module* (module used for resolving imports to prefixes) * LY_VALUE_SCHEMA_RESOLVED - struct ::lysc_prefix* (sized array of pairs: prefix - module) * LY_VALUE_XML - struct ::ly_set* (set of all returned modules as struct ::lys_module) + * LY_VALUE_CBOR - NULL * LY_VALUE_JSON - NULL * LY_VALUE_LYB - NULL * @return Module prefix to print. @@ -642,6 +643,7 @@ const char *ly_get_prefix(const struct lys_module *mod, LY_VALUE_FORMAT format, * LY_VALUE_SCHEMA - const struct lysp_module * (module used for resolving prefixes from imports) * LY_VALUE_SCHEMA_RESOLVED - struct lyd_value_prefix * (sized array of pairs: prefix - module) * LY_VALUE_XML - const struct ly_set * (set with defined namespaces stored as ::lyxml_ns) + * LY_VALUE_CBOR - NULL * LY_VALUE_JSON - NULL * LY_VALUE_LYB - NULL * @return Resolved prefix module, diff --git a/src/xpath.c b/src/xpath.c index 99a4a2278..dc27cbefc 100644 --- a/src/xpath.c +++ b/src/xpath.c @@ -5706,6 +5706,7 @@ moveto_resolve_module(const char **qname, uint32_t *qname_len, const struct lyxp mod = set->cur_mod; break; case LY_VALUE_CANON: + case LY_VALUE_CBOR: case LY_VALUE_JSON: case LY_VALUE_LYB: case LY_VALUE_STR_NS: @@ -6323,6 +6324,7 @@ moveto_scnode_check(const struct lysc_node *node, const struct lysc_node *ctx_sc /* use current module */ moveto_mod = set->cur_mod; break; + case LY_VALUE_CBOR: case LY_VALUE_JSON: case LY_VALUE_LYB: case LY_VALUE_STR_NS: From 1f36cab8647caaace248e95593790806c7ab5b49 Mon Sep 17 00:00:00 2001 From: MeherRushi Date: Mon, 3 Nov 2025 06:12:44 +0000 Subject: [PATCH 9/9] feat and restructure: Adding the printing functionality for cbor. - 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) --- src/printer_cbor.c | 1765 +++++++++++++++++++++++++++++--------------- 1 file changed, 1167 insertions(+), 598 deletions(-) diff --git a/src/printer_cbor.c b/src/printer_cbor.c index d0cc0997a..dc4fc2a65 100644 --- a/src/printer_cbor.c +++ b/src/printer_cbor.c @@ -1,9 +1,9 @@ /** * @file printer_cbor.c - * @author Meher Rushi - * @brief CBOR printer for libyang data tree using libcbor + * @author Meher Rushi + * @brief CBOR printer for libyang data structure * - * Copyright (c) 2024 CESNET, z.s.p.o. + * Copyright (c) 2015 - 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -12,771 +12,1340 @@ * https://opensource.org/licenses/BSD-3-Clause */ -#include "printer_data.h" +#define _GNU_SOURCE + +#ifdef ENABLE_CBOR_SUPPORT #include -#include -#include #include #include #include -#include - #include "context.h" #include "log.h" #include "ly_common.h" #include "out.h" -#include "plugins_exts.h" +#include "out_internal.h" +#include "parser_data.h" +#include "plugins_exts/metadata.h" +#include "plugins_types.h" +#include "printer_data.h" #include "printer_internal.h" #include "set.h" +#include "tree.h" #include "tree_data.h" #include "tree_schema.h" +#include "cbor.h" /** - * @brief CBOR printer context + * @brief CBOR printer context. */ struct cborpr_ctx { - struct ly_out *out; /**< output structure */ - const struct lyd_node *root; /**< root node of the subtree being printed */ - const struct lyd_node *print_sibling_metadata; /**< node with metadata supposed to be printed */ - ly_bool simple_status; /**< flag for simple status */ + struct ly_out *out; /**< output specification */ + const struct lyd_node *root; /**< root node of the subtree being printed */ + const struct lyd_node *parent; /**< parent of the node being printed */ + uint32_t options; /**< [Data printer flags](@ref dataprinterflags) */ + const struct ly_ctx *ctx; /**< libyang context */ - uint16_t level; /**< current nesting level */ - uint32_t options; /**< [printer flags](@ref dataprinterflags) */ - const struct ly_ctx *ctx; /**< libyang context */ - - struct ly_set prefix; /**< printed module prefixes */ - uint32_t array_index; /**< index in array if we are printing an array element */ + struct ly_set open; /**< currently open array(s) */ + const struct lyd_node *first_leaflist; /**< first printed leaf-list instance, used when printing its metadata/attributes */ - cbor_item_t *root_item; /**< root CBOR item */ + cbor_item_t *root_map; /**< root CBOR map */ }; +static LY_ERR cbor_print_node(struct cborpr_ctx *pctx, const struct lyd_node *node, cbor_item_t *parent_map); + /** - * @brief Check if module needs prefix - ROBUST VERSION + * @brief Compare 2 nodes, despite it is regular data node or an opaq node, and + * decide if they corresponds to the same schema node. + * + * @return 1 - matching nodes, 0 - non-matching nodes */ -static ly_bool -cbor_module_needs_prefix(struct cborpr_ctx *ctx, const struct lys_module *module) +static int +matching_node(const struct lyd_node *node1, const struct lyd_node *node2) { - /* CRITICAL: Add comprehensive null checks */ - if (!ctx) { - fprintf(stderr, "DEBUG: cbor_module_needs_prefix called with NULL ctx\n"); + assert(node1 || node2); + + if (!node1 || !node2) { return 0; - } - - if (!module) { - fprintf(stderr, "DEBUG: cbor_module_needs_prefix called with NULL module\n"); + } else if (node1->schema != node2->schema) { return 0; } - - if (!module->name) { - fprintf(stderr, "DEBUG: Module has NULL name\n"); - return 0; + if (!node1->schema) { + /* compare node names */ + struct lyd_node_opaq *onode1 = (struct lyd_node_opaq *)node1; + struct lyd_node_opaq *onode2 = (struct lyd_node_opaq *)node2; + + if ((onode1->name.name != onode2->name.name) || (onode1->name.prefix != onode2->name.prefix)) { + return 0; + } } - - fprintf(stderr, "DEBUG: Checking prefix for module: '%s'\n", module->name); - - /* Always add prefix if explicitly requested */ - if (ctx->options & LYD_PRINT_WD_ALL_TAG) { - fprintf(stderr, "DEBUG: Prefix requested via options\n"); + + return 1; +} + +/** + * @brief Open a CBOR array for the specified @p node + * + * @param[in] pctx CBOR printer context. + * @param[in] node First node of the array. + * @return LY_ERR value. + */ +static LY_ERR +cbor_print_array_open(struct cborpr_ctx *pctx, const struct lyd_node *node) +{ + LY_CHECK_RET(ly_set_add(&pctx->open, (void *)node, 0, NULL)); + return LY_SUCCESS; +} + +/** + * @brief Get know if the array for the provided @p node is currently open. + * + * @param[in] pctx CBOR printer context. + * @param[in] node Data node to check. + * @return 1 in case the printer is currently in the array belonging to the provided @p node. + * @return 0 in case the provided @p node is not connected with the currently open array (or there is no open array). + */ +static int +is_open_array(struct cborpr_ctx *pctx, const struct lyd_node *node) +{ + if (pctx->open.count && matching_node(node, pctx->open.dnodes[pctx->open.count - 1])) { return 1; - } - - /* Check if it's an internal libyang module */ - if (!strcmp(module->name, "ietf-yang-metadata") || - !strcmp(module->name, "yang") || - !strcmp(module->name, "ietf-inet-types") || - !strcmp(module->name, "ietf-yang-types") || - !strcmp(module->name, "ietf-yang-structure-ext")) { - fprintf(stderr, "DEBUG: Internal module, no prefix needed\n"); + } else { return 0; } - - /* For now, don't add prefixes unless explicitly requested */ - fprintf(stderr, "DEBUG: No prefix needed for module: '%s'\n", module->name); - return 0; } +/** + * @brief Close the most inner CBOR array. + * + * @param[in] pctx CBOR printer context. + */ +static void +cbor_print_array_close(struct cborpr_ctx *pctx) +{ + ly_set_rm_index(&pctx->open, pctx->open.count - 1, NULL); +} /** - * @brief Safe wrapper for cbor_build_string that handles NULL inputs - ENHANCED + * @brief Get the node's module name to use as the @p node prefix in CBOR. + * + * @param[in] node Node to process. + * @return The name of the module where the @p node belongs, it can be NULL in case the module name + * cannot be determined (source format is XML and the refered namespace is unknown/not implemented in the current context). */ -static cbor_item_t * -safe_cbor_build_string(const char *str) +static const char * +node_prefix(const struct lyd_node *node) { - if (!str) { - fprintf(stderr, "DEBUG: NULL string passed to safe_cbor_build_string, using empty string\n"); - return cbor_build_string(""); - } - - fprintf(stderr, "DEBUG: Building CBOR string: '%s' (len=%zu)\n", str, strlen(str)); - cbor_item_t *item = cbor_build_string(str); - if (!item) { - fprintf(stderr, "DEBUG: cbor_build_string failed for '%s'\n", str); + if (node->schema) { + return node->schema->module->name; + } else { + struct lyd_node_opaq *onode = (struct lyd_node_opaq *)node; + const struct lys_module *mod; + + switch (onode->format) { + case LY_VALUE_CBOR: + case LY_VALUE_JSON: + return onode->name.module_name; + case LY_VALUE_XML: + mod = ly_ctx_get_module_implemented_ns(onode->ctx, onode->name.module_ns); + if (!mod) { + return NULL; + } + return mod->name; + default: + /* cannot be created */ + LOGINT(LYD_CTX(node)); + } } - return item; + + return NULL; } /** - * @brief Convert YANG value to CBOR item + * @brief Compare 2 nodes if the belongs to the same module (if they come from the same namespace) + * + * Accepts both regulard a well as opaq nodes. + * + * @param[in] node1 The first node to compare. + * @param[in] node2 The second node to compare. + * @return 0 in case the nodes' modules are the same + * @return 1 in case the nodes belongs to different modules */ -static cbor_item_t * -cbor_value_to_item(const struct lyd_node *node) +int +cbor_nscmp(const struct lyd_node *node1, const struct lyd_node *node2) { - const char *str; - cbor_item_t *item = NULL; - - if (!node) { - fprintf(stderr, "DEBUG: cbor_value_to_item called with NULL node\n"); - return safe_cbor_build_string(""); - } - - fprintf(stderr, "DEBUG: cbor_value_to_item - node type: %d\n", node->schema->nodetype); - - if (!(node->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { - fprintf(stderr, "DEBUG: Non-leaf node, returning empty string\n"); - return safe_cbor_build_string(""); /* Empty string for non-leaf nodes */ + assert(node1 || node2); + + if (!node1 || !node2) { + return 1; + } else if (node1->schema && node2->schema) { + if (node1->schema->module == node2->schema->module) { + /* belongs to the same module */ + return 0; + } else { + /* different modules */ + return 1; + } + } else { + const char *pref1 = node_prefix(node1); + const char *pref2 = node_prefix(node2); + + if ((pref1 && pref2) && (pref1 == pref2)) { + return 0; + } else { + return 1; + } } +} + +/** + * @brief Create CBOR member name as [prefix:]name + * + * @param[in] pctx CBOR printer context. + * @param[in] node The data node being printed. + * @param[in] is_attr Flag if the metadata sign (@) is supposed to be added before the identifier. + * @return Newly allocated string with the member name, NULL on error. + */ +static char * +cbor_print_member_name(struct cborpr_ctx *pctx, const struct lyd_node *node, ly_bool is_attr) +{ + char *name = NULL; + const char *prefix_str = node_prefix(node); + const char *node_name = node->schema->name; - str = lyd_get_value(node); - fprintf(stderr, "DEBUG: Node value: '%s'\n", str ? str : "NULL"); - - if (!str || strlen(str) == 0) { - fprintf(stderr, "DEBUG: Empty or NULL value, returning empty string\n"); - return safe_cbor_build_string(""); + if (cbor_nscmp(node, pctx->parent)) { + /* print "namespace" */ + if (is_attr) { + if (asprintf(&name, "@%s:%s", prefix_str, node_name) == -1) { + return NULL; + } + } else { + if (asprintf(&name, "%s:%s", prefix_str, node_name) == -1) { + return NULL; + } + } + } else { + if (is_attr) { + if (asprintf(&name, "@%s", node_name) == -1) { + return NULL; + } + } else { + name = strdup(node_name); + } } - /* FIXED: Add null check for schema before casting */ - if (!node->schema) { - fprintf(stderr, "DEBUG: Node has NULL schema, using string value\n"); - return cbor_build_string(str); + return name; +} + +/** + * @brief More generic alternative to cbor_print_member_name() to print some special cases of the member names. + * + * @param[in] pctx CBOR printer context. + * @param[in] parent Parent node to compare modules deciding if the prefix is printed. + * @param[in] format Format to decide how to process the @p prefix. + * @param[in] name Name structure to provide name and prefix to print. If NULL, only "" name is printed. + * @param[in] is_attr Flag if the metadata sign (@) is supposed to be added before the identifier. + * @return Newly allocated string with the member name, NULL on error. + */ +static char * +cbor_print_member_name2(struct cborpr_ctx *pctx, const struct lyd_node *parent, LY_VALUE_FORMAT format, + const struct ly_opaq_name *name, ly_bool is_attr) +{ + const char *module_name = NULL, *name_str; + char *result = NULL; + + /* determine prefix string */ + if (name) { + switch (format) { + case LY_VALUE_CBOR: + case LY_VALUE_JSON: + module_name = name->module_name; + break; + case LY_VALUE_XML: { + const struct lys_module *mod = NULL; + + if (name->module_ns) { + mod = ly_ctx_get_module_implemented_ns(pctx->ctx, name->module_ns); + } + if (mod) { + module_name = mod->name; + } + break; + } + default: + /* cannot be created */ + LOGINT_RET(pctx->ctx); + } + + name_str = name->name; + } else { + name_str = ""; } - - /* Handle different data types based on YANG type */ - switch (((struct lysc_node_leaf *)node->schema)->type->basetype) { - case LY_TYPE_BOOL: - if (strcmp(str, "true") == 0) { - item = cbor_build_bool(true); + + /* create the member name */ + if (module_name && (!parent || (node_prefix(parent) != module_name))) { + if (is_attr) { + if (asprintf(&result, "@%s:%s", module_name, name_str) == -1) { + return NULL; + } } else { - item = cbor_build_bool(false); + if (asprintf(&result, "%s:%s", module_name, name_str) == -1) { + return NULL; + } + } + } else { + if (is_attr) { + if (asprintf(&result, "@%s", name_str) == -1) { + return NULL; + } + } else { + result = strdup(name_str); } + } + + return result; +} + +/** + * @brief Print data value to CBOR item. + * + * @param[in] pctx CBOR printer context. + * @param[in] ctx Context used to print the value. + * @param[in] val Data value to be printed. + * @param[in] local_mod Module of the current node. + * @param[out] item_p Pointer to store the created CBOR item. + * @return LY_ERR value. + */ +static LY_ERR +cbor_print_value(struct cborpr_ctx *pctx, const struct ly_ctx *ctx, const struct lyd_value *val, + const struct lys_module *local_mod, cbor_item_t **item_p) +{ + ly_bool dynamic; + LY_DATA_TYPE basetype; + const char *value; + cbor_item_t *item = NULL; + + value = val->realtype->plugin->print(ctx, val, LY_VALUE_JSON, (void *)local_mod, &dynamic, NULL); + LY_CHECK_RET(!value, LY_EINVAL); + basetype = val->realtype->basetype; + +print_val: + /* leafref is not supported */ + switch (basetype) { + case LY_TYPE_UNION: + /* use the resolved type */ + val = &val->subvalue->value; + basetype = val->realtype->basetype; + goto print_val; + + case LY_TYPE_BINARY: + case LY_TYPE_STRING: + case LY_TYPE_BITS: + case LY_TYPE_ENUM: + case LY_TYPE_INST: + case LY_TYPE_IDENT: + /* string types */ + item = cbor_build_string(value); break; - + + case LY_TYPE_INT64: + case LY_TYPE_UINT64: + case LY_TYPE_DEC64: { + /* numeric types stored as strings in CBOR */ + item = cbor_build_string(value); + break; + } + case LY_TYPE_INT8: case LY_TYPE_INT16: - case LY_TYPE_INT32: - case LY_TYPE_INT64: { - char *endptr; - long long num = strtoll(str, &endptr, 10); - if (*endptr == '\0') { - if (num >= 0) { - item = cbor_build_uint64((uint64_t)num); - } else { - item = cbor_build_negint64((uint64_t)(-num - 1)); - } + case LY_TYPE_INT32: { + /* signed integer types */ + int64_t num = strtoll(value, NULL, 10); + if (num >= 0) { + item = cbor_build_uint64(num); } else { - item = cbor_build_string(str); + item = cbor_build_negint64(-num - 1); } break; } - + case LY_TYPE_UINT8: case LY_TYPE_UINT16: - case LY_TYPE_UINT32: - case LY_TYPE_UINT64: { - char *endptr; - unsigned long long num = strtoull(str, &endptr, 10); - if (*endptr == '\0') { - item = cbor_build_uint64(num); - } else { - item = cbor_build_string(str); - } + case LY_TYPE_UINT32: { + /* unsigned integer types */ + uint64_t num = strtoull(value, NULL, 10); + item = cbor_build_uint64(num); break; } - - case LY_TYPE_DEC64: { - char *endptr; - double num = strtod(str, &endptr); - if (*endptr == '\0') { - item = cbor_build_float8(num); + + case LY_TYPE_BOOL: + /* boolean */ + if (strcmp(value, "true") == 0) { + item = cbor_build_bool(true); } else { - item = cbor_build_string(str); + item = cbor_build_bool(false); } break; - } - + case LY_TYPE_EMPTY: - item = cbor_build_string(""); + /* empty type is represented as [null] */ + item = cbor_new_definite_array(1); + if (item) { + cbor_item_t *null_item = cbor_build_ctrl(CBOR_CTRL_NULL); + if (null_item) { + cbor_array_push(item, null_item); + cbor_decref(&null_item); + } + } break; - + default: - /* String types and others */ - item = cbor_build_string(str); - break; + /* error */ + LOGINT_RET(pctx->ctx); } - - return item; + + if (dynamic) { + free((char *)value); + } + + *item_p = item; + return item ? LY_SUCCESS : LY_EMEM; } /** - * @brief Count direct children of a node + * @brief Print all the attributes of the opaq node to CBOR map. + * + * @param[in] pctx CBOR printer context. + * @param[in] node Opaq node where the attributes are placed. + * @param[in] attr_map CBOR map to add attributes to. + * @return LY_ERR value. */ -static size_t -cbor_count_children(const struct lyd_node *node) +static LY_ERR +cbor_print_attribute(struct cborpr_ctx *pctx, const struct lyd_node_opaq *node, cbor_item_t *attr_map) { - size_t count = 0; - const struct lyd_node *child; - - LY_LIST_FOR(lyd_child(node), child) { - count++; + struct lyd_attr *attr; + cbor_item_t *value_item = NULL; + + for (attr = node->attr; attr; attr = attr->next) { + char *key = cbor_print_member_name2(pctx, &node->node, attr->format, &attr->name, 0); + LY_CHECK_RET(!key, LY_EMEM); + + if (attr->hints & (LYD_VALHINT_STRING | LYD_VALHINT_OCTNUM | LYD_VALHINT_HEXNUM | LYD_VALHINT_NUM64)) { + value_item = cbor_build_string(attr->value); + } else if (attr->hints & (LYD_VALHINT_BOOLEAN | LYD_VALHINT_DECNUM)) { + if (strcmp(attr->value, "true") == 0) { + value_item = cbor_build_bool(true); + } else if (strcmp(attr->value, "false") == 0) { + value_item = cbor_build_bool(false); + } else { + /* numeric value as string */ + value_item = cbor_build_string(attr->value); + } + } else if (attr->hints & LYD_VALHINT_EMPTY) { + value_item = cbor_new_definite_array(1); + if (value_item) { + cbor_item_t *null_item = cbor_build_ctrl(CBOR_CTRL_NULL); + if (null_item) { + cbor_array_push(value_item, null_item); + cbor_decref(&null_item); + } + } + } else { + /* unknown value format with no hints, use universal string */ + value_item = cbor_build_string(attr->value); + } + + if (!value_item) { + free(key); + return LY_EMEM; + } + + struct cbor_pair pair = { + .key = cbor_move(cbor_build_string(key)), + .value = cbor_move(value_item) + }; + free(key); + + if (!cbor_map_add(attr_map, pair)) { + cbor_decref(&pair.key); + cbor_decref(&pair.value); + return LY_EMEM; + } } - return count; + + return LY_SUCCESS; } /** - * @brief Count sibling nodes with the same name (for leaf-lists) + * @brief Print all the metadata of the node to CBOR map. + * + * @param[in] pctx CBOR printer context. + * @param[in] node Node where the metadata are placed. + * @param[in] wdmod With-defaults module to mark that default attribute is supposed to be printed. + * @param[in] meta_map CBOR map to add metadata to. + * @return LY_ERR value. */ -static size_t -cbor_count_siblings_same_name(const struct lyd_node *node) +static LY_ERR +cbor_print_metadata(struct cborpr_ctx *pctx, const struct lyd_node *node, const struct lys_module *wdmod, + cbor_item_t *meta_map) { - size_t count = 0; - const struct lyd_node *sibling; - const struct lysc_node *schema = node->schema; - - /* Check if this is a leaf-list */ - if (!(schema->nodetype & LYS_LEAFLIST)) { - return 1; + struct lyd_meta *meta; + cbor_item_t *value_item = NULL; + char *key = NULL; + + if (wdmod) { + if (asprintf(&key, "%s:default", wdmod->name) == -1) { + return LY_EMEM; + } + value_item = cbor_build_bool(true); + if (!value_item) { + free(key); + return LY_EMEM; + } + + struct cbor_pair pair = { + .key = cbor_move(cbor_build_string(key)), + .value = cbor_move(value_item) + }; + free(key); + + if (!cbor_map_add(meta_map, pair)) { + cbor_decref(&pair.key); + cbor_decref(&pair.value); + return LY_EMEM; + } } - - /* Count siblings with same schema */ - LY_LIST_FOR(node, sibling) { - if (sibling->schema == schema) { - count++; - } else { - break; /* leaf-list instances are consecutive */ + + for (meta = node->meta; meta; meta = meta->next) { + if (!lyd_metadata_should_print(meta)) { + continue; + } + + if (asprintf(&key, "%s:%s", meta->annotation->module->name, meta->name) == -1) { + return LY_EMEM; + } + + LY_CHECK_RET(cbor_print_value(pctx, LYD_CTX(node), &meta->value, NULL, &value_item)); + + struct cbor_pair pair = { + .key = cbor_move(cbor_build_string(key)), + .value = cbor_move(value_item) + }; + free(key); + + if (!cbor_map_add(meta_map, pair)) { + cbor_decref(&pair.key); + cbor_decref(&pair.value); + return LY_EMEM; } } - - return count; + + return LY_SUCCESS; } /** - * @brief Print a single node recursively + * @brief Check if a value can be printed for at least one metadata. + * + * @param[in] node Node to check. + * @return 1 if node has printable meta otherwise 0. */ -static LY_ERR cbor_print_node(struct cborpr_ctx *ctx, const struct lyd_node *node, cbor_item_t *parent_map); +static ly_bool +node_has_printable_meta(const struct lyd_node *node) +{ + struct lyd_meta *iter; + + if (!node->meta) { + return 0; + } + + LY_LIST_FOR(node->meta, iter) { + if (lyd_metadata_should_print(iter)) { + return 1; + } + } + + return 0; +} /** - * @brief Print container or list node - EXTRA SAFE VERSION + * @brief Print attributes/metadata of the given @p node to CBOR map. + * + * @param[in] pctx CBOR printer context. + * @param[in] node Data node where the attributes/metadata are placed. + * @param[in] parent_map CBOR map to add the metadata to. + * @param[in] inner Flag if the @p node is an inner node in the tree. + * @return LY_ERR value. */ static LY_ERR -cbor_print_container(struct cborpr_ctx *ctx, const struct lyd_node *node, cbor_item_t *parent_map) +cbor_print_attributes(struct cborpr_ctx *pctx, const struct lyd_node *node, cbor_item_t *parent_map, ly_bool inner) { - cbor_item_t *node_map = NULL; - cbor_item_t *key_item = NULL; - char *node_name = NULL; - const struct lyd_node *child; - LY_ERR ret = LY_SUCCESS; - size_t child_count; - - fprintf(stderr, "DEBUG: cbor_print_container called for node: %s\n", - node && node->schema && node->schema->name ? node->schema->name : "NULL"); - - /* COMPREHENSIVE NULL CHECKS */ - if (!ctx) { - fprintf(stderr, "DEBUG: Container called with NULL ctx\n"); - return LY_EINVAL; - } - - if (!node) { - fprintf(stderr, "DEBUG: Container called with NULL node\n"); - return LY_EINVAL; - } - - if (!node->schema) { - fprintf(stderr, "DEBUG: Container node has NULL schema\n"); - return LY_EINVAL; - } - - if (!parent_map) { - fprintf(stderr, "DEBUG: Container called with NULL parent_map\n"); - return LY_EINVAL; - } - - /* Get node name - This should now be safe */ - // node_name = cbor_get_node_name(ctx, node); - node_name = ""; if (!node_name) { - fprintf(stderr, "DEBUG: Failed to get container node name\n"); - ret = LY_EMEM; - goto cleanup; - } - - fprintf(stderr, "DEBUG: Container name: '%s'\n", node_name); - - /* Count children */ - child_count = cbor_count_children(node); - fprintf(stderr, "DEBUG: Container has %zu children\n", child_count); - - /* Create map for this container/list */ - node_map = cbor_new_definite_map(child_count); - if (!node_map) { - fprintf(stderr, "DEBUG: Failed to create CBOR map for container\n"); - ret = LY_EMEM; - goto cleanup; + const struct lys_module *wdmod = NULL; + cbor_item_t *meta_map = NULL; + char *key = NULL; + + if (node->schema && (node->schema->nodetype != LYS_CONTAINER) && (((node->flags & LYD_DEFAULT) && + (pctx->options & (LYD_PRINT_WD_ALL_TAG | LYD_PRINT_WD_IMPL_TAG))) || + ((pctx->options & LYD_PRINT_WD_ALL_TAG) && lyd_is_default(node)))) { + /* we have implicit OR explicit default node */ + /* get with-defaults module */ + wdmod = ly_ctx_get_module_implemented(LYD_CTX(node), "ietf-netconf-with-defaults"); } - - /* Add all children to the map */ - LY_LIST_FOR(lyd_child(node), child) { - if (!child || !child->schema || !child->schema->name) { - fprintf(stderr, "DEBUG: Skipping invalid child\n"); - continue; + + if (node->schema && (wdmod || node_has_printable_meta(node))) { + meta_map = cbor_new_indefinite_map(); + LY_CHECK_RET(!meta_map, LY_EMEM); + + LY_CHECK_RET(cbor_print_metadata(pctx, node, wdmod, meta_map)); + + if (inner) { + key = cbor_print_member_name2(pctx, lyd_parent(node), LY_VALUE_JSON, NULL, 1); + } else { + key = cbor_print_member_name(pctx, node, 1); } - - fprintf(stderr, "DEBUG: Processing child: %s\n", child->schema->name); - ret = cbor_print_node(ctx, child, node_map); - if (ret != LY_SUCCESS) { - fprintf(stderr, "DEBUG: Failed to process child node\n"); - goto cleanup; + LY_CHECK_RET(!key, LY_EMEM); + + struct cbor_pair pair = { + .key = cbor_move(cbor_build_string(key)), + .value = cbor_move(meta_map) + }; + free(key); + + if (!cbor_map_add(parent_map, pair)) { + cbor_decref(&pair.key); + cbor_decref(&pair.value); + return LY_EMEM; } - } - - /* Add this container/list to parent map */ - key_item = safe_cbor_build_string(node_name); - if (!key_item) { - fprintf(stderr, "DEBUG: Failed to create key item for container\n"); - ret = LY_EMEM; - goto cleanup; - } - - if (!cbor_map_add(parent_map, (struct cbor_pair) { - .key = key_item, - .value = node_map - })) { - fprintf(stderr, "DEBUG: Failed to add container to parent map\n"); - ret = LY_EMEM; - goto cleanup; - } - - fprintf(stderr, "DEBUG: Container added successfully\n"); - - /* Items are now owned by the map, don't decref them */ - key_item = NULL; - node_map = NULL; + } else if (!node->schema && ((struct lyd_node_opaq *)node)->attr) { + meta_map = cbor_new_indefinite_map(); + LY_CHECK_RET(!meta_map, LY_EMEM); -cleanup: - if (key_item) { - cbor_decref(&key_item); - } - if (node_map) { - cbor_decref(&node_map); - } - if (node_name) { - free(node_name); + LY_CHECK_RET(cbor_print_attribute(pctx, (struct lyd_node_opaq *)node, meta_map)); + + if (inner) { + key = cbor_print_member_name2(pctx, lyd_parent(node), LY_VALUE_JSON, NULL, 1); + } else { + key = cbor_print_member_name2(pctx, lyd_parent(node), ((struct lyd_node_opaq *)node)->format, + &((struct lyd_node_opaq *)node)->name, 1); + } + LY_CHECK_RET(!key, LY_EMEM); + + struct cbor_pair pair = { + .key = cbor_move(cbor_build_string(key)), + .value = cbor_move(meta_map) + }; + free(key); + + if (!cbor_map_add(parent_map, pair)) { + cbor_decref(&pair.key); + cbor_decref(&pair.value); + return LY_EMEM; + } } - return ret; + + return LY_SUCCESS; } /** - * @brief Print leaf or leaf-list node + * @brief Print leaf data node including its metadata. + * + * @param[in] pctx CBOR printer context. + * @param[in] node Data node to print. + * @param[in] parent_map CBOR map to add the leaf to. + * @return LY_ERR value. */ static LY_ERR -cbor_print_leaf(struct cborpr_ctx *ctx, const struct lyd_node *node, cbor_item_t *parent_map) +cbor_print_leaf(struct cborpr_ctx *pctx, const struct lyd_node *node, cbor_item_t *parent_map) { - cbor_item_t *key_item = NULL; + char *key = NULL; cbor_item_t *value_item = NULL; - cbor_item_t *array_item = NULL; - char *node_name = NULL; - const struct lyd_node *sibling; - LY_ERR ret = LY_SUCCESS; - size_t sibling_count; - - fprintf(stderr, "DEBUG: cbor_print_leaf called for node: %s\n", - node && node->schema && node->schema->name ? node->schema->name : "NULL"); + + key = cbor_print_member_name(pctx, node, 0); + LY_CHECK_RET(!key, LY_EMEM); + + LY_CHECK_ERR_RET(cbor_print_value(pctx, LYD_CTX(node), &((const struct lyd_node_term *)node)->value, + node->schema->module, &value_item), free(key), LY_EINVAL); + + struct cbor_pair pair = { + .key = cbor_move(cbor_build_string(key)), + .value = cbor_move(value_item) + }; - /* FIXED: Add null checks */ - if (!node || !node->schema) { - fprintf(stderr, "DEBUG: Leaf node or schema is NULL\n"); - return LY_EINVAL; + if (!cbor_map_add(parent_map, pair)) { + free(key); + cbor_decref(&pair.key); + cbor_decref(&pair.value); + return LY_EMEM; } - - /* Get node name */ - // node_name = cbor_get_node_name(ctx, node); - node_name = ""; - if (!node_name) { - fprintf(stderr, "DEBUG: Failed to get node name\n"); - ret = LY_EMEM; - goto cleanup; - } - - fprintf(stderr, "DEBUG: Got node name: '%s'\n", node_name); - - /* Check if this is a leaf-list with multiple values */ - sibling_count = cbor_count_siblings_same_name(node); - fprintf(stderr, "DEBUG: Sibling count: %zu\n", sibling_count); - - if (sibling_count > 1 && (node->schema->nodetype & LYS_LEAFLIST)) { - fprintf(stderr, "DEBUG: Processing leaf-list with %zu values\n", sibling_count); - - /* Create array for leaf-list */ - array_item = cbor_new_definite_array(sibling_count); - if (!array_item) { - fprintf(stderr, "DEBUG: Failed to create CBOR array\n"); - ret = LY_EMEM; - goto cleanup; - } - - /* Add all values to array */ - LY_LIST_FOR(node, sibling) { - if (sibling->schema != node->schema) { - break; /* Different schema, stop */ - } - - fprintf(stderr, "DEBUG: Adding leaf-list value to array\n"); - value_item = cbor_value_to_item(sibling); - if (!value_item) { - fprintf(stderr, "DEBUG: Failed to create CBOR value item\n"); - ret = LY_EMEM; - goto cleanup; - } - - if (!cbor_array_push(array_item, value_item)) { - fprintf(stderr, "DEBUG: Failed to add item to CBOR array\n"); - cbor_decref(&value_item); - ret = LY_EMEM; - goto cleanup; - } - - value_item = NULL; /* Array owns it now */ - } - - /* Add array to parent map */ - key_item = safe_cbor_build_string(node_name); - if (!key_item) { - fprintf(stderr, "DEBUG: Failed to create CBOR key item for leaf-list\n"); - ret = LY_EMEM; - goto cleanup; - } - - if (!cbor_map_add(parent_map, (struct cbor_pair) { - .key = key_item, - .value = array_item - })) { - fprintf(stderr, "DEBUG: Failed to add leaf-list to parent map\n"); - ret = LY_EMEM; - goto cleanup; - } - - /* Items are now owned by the map */ - key_item = NULL; - array_item = NULL; - - } else { - fprintf(stderr, "DEBUG: Processing single leaf value\n"); - - /* Single leaf value */ - value_item = cbor_value_to_item(node); - if (!value_item) { - fprintf(stderr, "DEBUG: Failed to create CBOR value item for leaf\n"); - ret = LY_EMEM; - goto cleanup; - } - - key_item = safe_cbor_build_string(node_name); - if (!key_item) { - fprintf(stderr, "DEBUG: Failed to create CBOR key item for leaf\n"); - ret = LY_EMEM; - goto cleanup; - } - - if (!cbor_map_add(parent_map, (struct cbor_pair) { - .key = key_item, - .value = value_item - })) { - fprintf(stderr, "DEBUG: Failed to add leaf to parent map\n"); - ret = LY_EMEM; - goto cleanup; - } - - /* Items are now owned by the map */ - key_item = NULL; - value_item = NULL; - } - - fprintf(stderr, "DEBUG: cbor_print_leaf completed successfully\n"); + free(key); -cleanup: - if (key_item) { - cbor_decref(&key_item); - } - if (value_item) { - cbor_decref(&value_item); - } - if (array_item) { - cbor_decref(&array_item); - } - free(node_name); - return ret; + /* print attributes as sibling */ + cbor_print_attributes(pctx, node, parent_map, 0); + + return LY_SUCCESS; } /** - * @brief Print anydata/anyxml node + * @brief Print anydata/anyxml content to CBOR item. + * + * @param[in] pctx CBOR printer context. + * @param[in] any Anydata node to print. + * @param[out] item_p Pointer to store the created CBOR item. + * @return LY_ERR value. */ static LY_ERR -cbor_print_any(struct cborpr_ctx *ctx, const struct lyd_node *node, cbor_item_t *parent_map) +cbor_print_any_content(struct cborpr_ctx *pctx, struct lyd_node_any *any, cbor_item_t **item_p) { - cbor_item_t *key_item = NULL; - cbor_item_t *value_item = NULL; - char *node_name = NULL; LY_ERR ret = LY_SUCCESS; - struct lyd_node_any *any = (struct lyd_node_any *)node; - const char *value_str = ""; - - /* FIXED: Add null checks */ - if (!node || !node->schema) { - fprintf(stderr, "DEBUG: Any node or schema is NULL\n"); - return LY_EINVAL; + struct lyd_node *iter; + const struct lyd_node *prev_parent; + uint32_t prev_opts, *prev_lo, temp_lo = 0; + cbor_item_t *content_map = NULL; + + assert(any->schema->nodetype & LYD_NODE_ANY); + + if ((any->schema->nodetype == LYS_ANYDATA) && (any->value_type != LYD_ANYDATA_DATATREE)) { + LOGINT_RET(pctx->ctx); } - - /* Get node name */ - // node_name = cbor_get_node_name(ctx, node); - node_name = ""; if (!node_name) { - ret = LY_EMEM; - goto cleanup; + if (any->value_type == LYD_ANYDATA_LYB) { + uint32_t parser_options = LYD_PARSE_ONLY | LYD_PARSE_OPAQ | LYD_PARSE_STRICT; + + /* turn logging off */ + prev_lo = ly_temp_log_options(&temp_lo); + + /* try to parse it into a data tree */ + if (lyd_parse_data_mem(pctx->ctx, any->value.mem, LYD_LYB, parser_options, 0, &iter) == LY_SUCCESS) { + /* successfully parsed */ + free(any->value.mem); + any->value.tree = iter; + any->value_type = LYD_ANYDATA_DATATREE; + } + + /* turn logging on again */ + ly_temp_log_options(prev_lo); } - - /* Convert anydata to string representation for now */ - /* TODO: Could be enhanced to preserve the actual data format */ + switch (any->value_type) { - case LYD_ANYDATA_STRING: - value_str = any->value.str ? (char *)any->value.str : ""; - break; case LYD_ANYDATA_DATATREE: - /* For now, just indicate it's a data tree */ - value_str = "[DATA TREE]"; - break; - case LYD_ANYDATA_XML: - value_str = any->value.str ? (char *)any->value.str : ""; + /* create a map for the content */ + content_map = cbor_new_indefinite_map(); + LY_CHECK_RET(!content_map, LY_EMEM); + + /* print data tree */ + prev_parent = pctx->parent; + prev_opts = pctx->options; + pctx->parent = &any->node; + pctx->options &= ~LYD_PRINT_WITHSIBLINGS; + LY_LIST_FOR(any->value.tree, iter) { + ret = cbor_print_node(pctx, iter, content_map); + LY_CHECK_ERR_RET(ret, cbor_decref(&content_map), ret); + } + pctx->parent = prev_parent; + pctx->options = prev_opts; + + *item_p = content_map; break; case LYD_ANYDATA_JSON: - value_str = any->value.str ? (char *)any->value.str : ""; + if (!any->value.json) { + /* no content */ + if (any->schema->nodetype == LYS_ANYXML) { + *item_p = cbor_build_ctrl(CBOR_CTRL_NULL); + } else { + *item_p = cbor_new_indefinite_map(); + } + } else { + /* JSON content - store as string */ + *item_p = cbor_build_string(any->value.json); + } break; - default: - value_str = ""; + case LYD_ANYDATA_STRING: + case LYD_ANYDATA_XML: + if (!any->value.str) { + /* no content */ + if (any->schema->nodetype == LYS_ANYXML) { + *item_p = cbor_build_ctrl(CBOR_CTRL_NULL); + } else { + *item_p = cbor_new_indefinite_map(); + } + } else { + /* print as a string */ + *item_p = cbor_build_string(any->value.str); + } + break; + case LYD_ANYDATA_LYB: + /* LYB format is not supported */ + LOGWRN(pctx->ctx, "Unable to print anydata content (type %d) as CBOR.", any->value_type); + *item_p = cbor_build_ctrl(CBOR_CTRL_NULL); break; } - - value_item = cbor_build_string(value_str); - if (!value_item) { - ret = LY_EMEM; - goto cleanup; + + return LY_SUCCESS; +} + +/** + * @brief Print content of a single container/list data node including its metadata. + * + * @param[in] pctx CBOR printer context. + * @param[in] node Data node to print. + * @param[out] item_p Pointer to store the created CBOR map. + * @return LY_ERR value. + */ +static LY_ERR +cbor_print_inner(struct cborpr_ctx *pctx, const struct lyd_node *node, cbor_item_t **item_p) +{ + struct lyd_node *child; + const struct lyd_node *prev_parent; + cbor_item_t *inner_map = NULL; + + /* create map for inner node */ + inner_map = cbor_new_indefinite_map(); + LY_CHECK_RET(!inner_map, LY_EMEM); + + /* print attributes first */ + cbor_print_attributes(pctx, node, inner_map, 1); + + /* print children */ + prev_parent = pctx->parent; + pctx->parent = node; + LY_LIST_FOR(lyd_child(node), child) { + LY_CHECK_ERR_RET(cbor_print_node(pctx, child, inner_map), cbor_decref(&inner_map), LY_EINVAL); } + pctx->parent = prev_parent; + + *item_p = inner_map; + return LY_SUCCESS; +} + +/** + * @brief Print container data node including its metadata. + * + * @param[in] pctx CBOR printer context. + * @param[in] node Data node to print. + * @param[in] parent_map CBOR map to add the container to. + * @return LY_ERR value. + */ +static LY_ERR +cbor_print_container(struct cborpr_ctx *pctx, const struct lyd_node *node, cbor_item_t *parent_map) +{ + char *key = NULL; + cbor_item_t *inner_map = NULL; + + key = cbor_print_member_name(pctx, node, 0); + LY_CHECK_RET(!key, LY_EMEM); + + LY_CHECK_ERR_RET(cbor_print_inner(pctx, node, &inner_map), free(key), LY_EINVAL); + + struct cbor_pair pair = { + .key = cbor_move(cbor_build_string(key)), + .value = cbor_move(inner_map) + }; - key_item = cbor_build_string(node_name); - if (!key_item) { - ret = LY_EMEM; - goto cleanup; + if (!cbor_map_add(parent_map, pair)) { + free(key); + cbor_decref(&pair.key); + cbor_decref(&pair.value); + return LY_EMEM; } + free(key); + + return LY_SUCCESS; +} + +/** + * @brief Print anydata/anyxml data node including its metadata. + * + * @param[in] pctx CBOR printer context. + * @param[in] node Data node to print. + * @param[in] parent_map CBOR map to add the anydata to. + * @return LY_ERR value. + */ +static LY_ERR +cbor_print_any(struct cborpr_ctx *pctx, const struct lyd_node *node, cbor_item_t *parent_map) +{ + char *key = NULL; + cbor_item_t *any_item = NULL; + + key = cbor_print_member_name(pctx, node, 0); + LY_CHECK_RET(!key, LY_EMEM); + + LY_CHECK_ERR_RET(cbor_print_any_content(pctx, (struct lyd_node_any *)node, &any_item), free(key), LY_EINVAL); + + struct cbor_pair pair = { + .key = cbor_move(cbor_build_string(key)), + .value = cbor_move(any_item) + }; - if (!cbor_map_add(parent_map, (struct cbor_pair) { - .key = key_item, - .value = value_item - })) { - ret = LY_EMEM; - goto cleanup; + if (!cbor_map_add(parent_map, pair)) { + free(key); + cbor_decref(&pair.key); + cbor_decref(&pair.value); + return LY_EMEM; + } + free(key); + + /* print attributes as sibling */ + cbor_print_attributes(pctx, node, parent_map, 0); + + return LY_SUCCESS; +} + +/** + * @brief Check whether a node is the last printed instance of a (leaf-)list. + * + * @param[in] pctx CBOR printer context. + * @param[in] node Last printed node. + * @return Whether it is the last printed instance or not. + */ +static ly_bool +cbor_print_array_is_last_inst(struct cborpr_ctx *pctx, const struct lyd_node *node) +{ + if (!is_open_array(pctx, node)) { + /* no array open */ + return 0; } - - /* Items are now owned by the map */ - key_item = NULL; - value_item = NULL; -cleanup: - if (key_item) { - cbor_decref(&key_item); + if ((pctx->root == node) && !(pctx->options & LYD_PRINT_WITHSIBLINGS)) { + /* the only printed instance */ + return 1; } - if (value_item) { - cbor_decref(&value_item); + + if (!node->next || (node->next->schema != node->schema)) { + /* last instance */ + return 1; } - free(node_name); - return ret; + + return 0; } /** - * @brief Print a single node recursively + * @brief Print single leaf-list or list instance. + * + * @param[in] pctx CBOR printer context. + * @param[in] node Data node to print. + * @param[in] parent_map CBOR map to add the node to. + * @param[in,out] array_p Pointer to the array being built. + * @return LY_ERR value. */ static LY_ERR -cbor_print_node(struct cborpr_ctx *ctx, const struct lyd_node *node, cbor_item_t *parent_map) +cbor_print_leaf_list(struct cborpr_ctx *pctx, const struct lyd_node *node, cbor_item_t *parent_map, cbor_item_t **array_p) { - /* FIXED: Add null checks at the beginning */ - if (!node || !node->schema) { - fprintf(stderr, "DEBUG: cbor_print_node called with NULL node or schema\n"); - return LY_EINVAL; + const struct lys_module *wdmod = NULL; + cbor_item_t *value_item = NULL; + cbor_item_t *inner_map = NULL; + char *key = NULL; + + if (!is_open_array(pctx, node)) { + /* start new array */ + *array_p = cbor_new_indefinite_array(); + LY_CHECK_RET(!*array_p, LY_EMEM); + + key = cbor_print_member_name(pctx, node, 0); + LY_CHECK_ERR_RET(!key, cbor_decref(array_p), LY_EMEM); + + LY_CHECK_RET(cbor_print_array_open(pctx, node)); } - - switch (node->schema->nodetype) { - case LYS_CONTAINER: - case LYS_LIST: - return cbor_print_container(ctx, node, parent_map); - case LYS_LEAF: - case LYS_LEAFLIST: - return cbor_print_leaf(ctx, node, parent_map); - case LYS_ANYXML: - case LYS_ANYDATA: - return cbor_print_any(ctx, node, parent_map); - default: - /* Skip unknown node types */ - fprintf(stderr, "DEBUG: Skipping unknown node type: %d\n", node->schema->nodetype); - return LY_SUCCESS; + + if (node->schema->nodetype == LYS_LIST) { + /* print list's content */ + LY_CHECK_RET(cbor_print_inner(pctx, node, &inner_map)); + cbor_array_push(*array_p, inner_map); + cbor_decref(&inner_map); + } else { + assert(node->schema->nodetype == LYS_LEAFLIST); + + LY_CHECK_RET(cbor_print_value(pctx, LYD_CTX(node), &((const struct lyd_node_term *)node)->value, + node->schema->module, &value_item)); + cbor_array_push(*array_p, value_item); + cbor_decref(&value_item); + + if (!pctx->first_leaflist) { + if (((node->flags & LYD_DEFAULT) && (pctx->options & (LYD_PRINT_WD_ALL_TAG | LYD_PRINT_WD_IMPL_TAG))) || + ((pctx->options & LYD_PRINT_WD_ALL_TAG) && lyd_is_default(node))) { + /* we have implicit OR explicit default node, get with-defaults module */ + wdmod = ly_ctx_get_module_implemented(LYD_CTX(node), "ietf-netconf-with-defaults"); + } + if (wdmod || node_has_printable_meta(node)) { + /* we will be printing metadata for these siblings */ + pctx->first_leaflist = node; + } + } + } + + if (cbor_print_array_is_last_inst(pctx, node)) { + /* add completed array to parent map */ + if (key) { + struct cbor_pair pair = { + .key = cbor_move(cbor_build_string(key)), + .value = cbor_move(*array_p) + }; + free(key); + + if (!cbor_map_add(parent_map, pair)) { + cbor_decref(&pair.key); + cbor_decref(&pair.value); + return LY_EMEM; + } + } + cbor_print_array_close(pctx); + *array_p = NULL; } + + return LY_SUCCESS; } /** - * @brief Count root level nodes, handling leaf-lists correctly + * @brief Print leaf-list's metadata or opaque nodes attributes. + * + * @param[in] pctx CBOR printer context. + * @param[in] parent_map CBOR map to add metadata to. + * @return LY_ERR value. */ -static size_t -cbor_count_root_nodes(const struct lyd_node *root) +static LY_ERR +cbor_print_meta_attr_leaflist(struct cborpr_ctx *pctx, cbor_item_t *parent_map) { - size_t count = 0; - const struct lyd_node *node; - const struct lysc_node *last_schema = NULL; - - LY_LIST_FOR(root, node) { - /* FIXED: Add null check for schema */ - if (!node->schema) { - continue; + const struct lyd_node *prev, *node, *iter; + const struct lys_module *wdmod = NULL, *iter_wdmod; + const struct lyd_node_opaq *opaq = NULL; + cbor_item_t *meta_array = NULL; + cbor_item_t *meta_map = NULL; + char *key = NULL; + + assert(pctx->first_leaflist); + + if (pctx->options & (LYD_PRINT_WD_ALL_TAG | LYD_PRINT_WD_IMPL_TAG)) { + /* get with-defaults module */ + wdmod = ly_ctx_get_module_implemented(pctx->ctx, "ietf-netconf-with-defaults"); + } + + /* node is the first instance of the leaf-list */ + for (node = pctx->first_leaflist, prev = pctx->first_leaflist->prev; + prev->next && matching_node(node, prev); + node = prev, prev = node->prev) {} + + /* create metadata array */ + meta_array = cbor_new_indefinite_array(); + LY_CHECK_RET(!meta_array, LY_EMEM); + + if (node->schema) { + key = cbor_print_member_name(pctx, node, 1); + } else { + opaq = (struct lyd_node_opaq *)node; + key = cbor_print_member_name2(pctx, lyd_parent(node), opaq->format, &opaq->name, 1); + } + LY_CHECK_ERR_RET(!key, cbor_decref(&meta_array), LY_EMEM); + + LY_LIST_FOR(node, iter) { + if (iter->schema && ((iter->flags & LYD_DEFAULT) || ((pctx->options & LYD_PRINT_WD_ALL_TAG) && lyd_is_default(iter)))) { + iter_wdmod = wdmod; + } else { + iter_wdmod = NULL; } - - /* For leaf-lists, only count the first occurrence */ - if (node->schema != last_schema) { - count++; - last_schema = node->schema; - } else if (!(node->schema->nodetype & LYS_LEAFLIST)) { - count++; + + if ((iter->schema && (node_has_printable_meta(iter) || iter_wdmod)) || (opaq && opaq->attr)) { + meta_map = cbor_new_indefinite_map(); + if (!meta_map) { + free(key); + cbor_decref(&meta_array); + return LY_EMEM; + } + + if (iter->schema) { + LY_CHECK_ERR_RET(cbor_print_metadata(pctx, iter, iter_wdmod, meta_map), + free(key); cbor_decref(&meta_array); cbor_decref(&meta_map), LY_EINVAL); + } else { + LY_CHECK_ERR_RET(cbor_print_attribute(pctx, (struct lyd_node_opaq *)iter, meta_map), + free(key); cbor_decref(&meta_array); cbor_decref(&meta_map), LY_EINVAL); + } + + cbor_array_push(meta_array, meta_map); + cbor_decref(&meta_map); + } else { + cbor_item_t *null_item = cbor_build_ctrl(CBOR_CTRL_NULL); + cbor_array_push(meta_array, null_item); + cbor_decref(&null_item); + } + + if (!matching_node(iter, iter->next)) { + break; } } - - return count; + + /* add metadata array to parent map */ + struct cbor_pair pair = { + .key = cbor_move(cbor_build_string(key)), + .value = cbor_move(meta_array) + }; + free(key); + + if (!cbor_map_add(parent_map, pair)) { + cbor_decref(&pair.key); + cbor_decref(&pair.value); + return LY_EMEM; + } + + return LY_SUCCESS; } /** - * @brief Main function to print data tree in CBOR format + * @brief Print opaq data node including its attributes. + * + * @param[in] pctx CBOR printer context. + * @param[in] node Opaq node to print. + * @param[in] parent_map CBOR map to add the node to. + * @param[in,out] array_p Pointer to the array being built (for leaf-lists/lists). + * @return LY_ERR value. */ -LY_ERR -cbor_print_data(struct ly_out *out, const struct lyd_node *root, uint32_t options) +static LY_ERR +cbor_print_opaq(struct cborpr_ctx *pctx, const struct lyd_node_opaq *node, cbor_item_t *parent_map, cbor_item_t **array_p) { - LY_ERR ret = LY_SUCCESS; - struct cborpr_ctx ctx = {0}; - const struct lyd_node *node; - const struct lysc_node *last_schema = NULL; - size_t root_count; - unsigned char *cbor_data = NULL; - size_t cbor_data_len = 0; - - if (!out) { - return LY_EINVAL; + ly_bool first = 1, last = 1; + uint32_t hints; + char *key = NULL; + cbor_item_t *value_item = NULL; + + if (node->hints == LYD_HINT_DATA) { + /* useless and confusing hints */ + hints = 0; + } else { + hints = node->hints; } - - /* Initialize context */ - ctx.out = out; - ctx.root = root; - ctx.options = options; - ctx.level = 0; - - if (root) { - ctx.ctx = LYD_CTX(root); - - /* Count root level nodes */ - root_count = cbor_count_root_nodes(root); - - /* Debug: Print what we're processing */ - fprintf(stderr, "DEBUG: Processing %zu root nodes\n", root_count); - - /* Create root map */ - ctx.root_item = cbor_new_definite_map(root_count); - if (!ctx.root_item) { - fprintf(stderr, "DEBUG: Failed to create root map\n"); - ret = LY_EMEM; - goto cleanup; + + if (hints & (LYD_NODEHINT_LIST | LYD_NODEHINT_LEAFLIST)) { + if (node->prev->next && matching_node(node->prev, &node->node)) { + first = 0; } - - /* Process all root nodes */ - LY_LIST_FOR(root, node) { - /* FIXED: Add null check for schema */ - if (!node->schema) { - fprintf(stderr, "DEBUG: Skipping node with NULL schema\n"); - continue; + if (node->next && matching_node(&node->node, node->next)) { + last = 0; + } + } + + if (first) { + key = cbor_print_member_name2(pctx, pctx->parent, node->format, &node->name, 0); + LY_CHECK_RET(!key, LY_EMEM); + + if (hints & (LYD_NODEHINT_LIST | LYD_NODEHINT_LEAFLIST)) { + *array_p = cbor_new_indefinite_array(); + LY_CHECK_ERR_RET(!*array_p, free(key), LY_EMEM); + LY_CHECK_ERR_RET(cbor_print_array_open(pctx, &node->node), free(key), LY_EINVAL); + } + } + + if (node->child || (hints & LYD_NODEHINT_LIST) || (hints & LYD_NODEHINT_CONTAINER)) { + cbor_item_t *inner_map = NULL; + LY_CHECK_ERR_RET(cbor_print_inner(pctx, &node->node, &inner_map), free(key), LY_EINVAL); + + if (hints & (LYD_NODEHINT_LIST | LYD_NODEHINT_LEAFLIST)) { + cbor_array_push(*array_p, inner_map); + cbor_decref(&inner_map); + } else { + struct cbor_pair pair = { + .key = cbor_move(cbor_build_string(key)), + .value = cbor_move(inner_map) + }; + free(key); + key = NULL; + + if (!cbor_map_add(parent_map, pair)) { + cbor_decref(&pair.key); + cbor_decref(&pair.value); + return LY_EMEM; } - - /* Skip duplicate leaf-list entries (they are handled together) */ - if ((node->schema->nodetype & LYS_LEAFLIST) && (node->schema == last_schema)) { - continue; + } + } else { + if (hints & LYD_VALHINT_EMPTY) { + value_item = cbor_new_definite_array(1); + if (value_item) { + cbor_item_t *null_item = cbor_build_ctrl(CBOR_CTRL_NULL); + if (null_item) { + cbor_array_push(value_item, null_item); + cbor_decref(&null_item); + } } - - fprintf(stderr, "DEBUG: Processing node: %s\n", node->schema->name); - - ctx.root = node; - ret = cbor_print_node(&ctx, node, ctx.root_item); - if (ret != LY_SUCCESS) { - fprintf(stderr, "DEBUG: Failed to print node: %s\n", node->schema->name); - goto cleanup; + } else if ((hints & (LYD_VALHINT_BOOLEAN | LYD_VALHINT_DECNUM)) && !(hints & LYD_VALHINT_NUM64)) { + if (strcmp(node->value, "true") == 0) { + value_item = cbor_build_bool(true); + } else if (strcmp(node->value, "false") == 0) { + value_item = cbor_build_bool(false); + } else { + value_item = cbor_build_string(node->value); } - - last_schema = node->schema; - - /* Break if not printing siblings */ - if (!(options & LYD_PRINT_WITHSIBLINGS)) { - break; + } else { + /* string or a large number */ + value_item = cbor_build_string(node->value); + } + + LY_CHECK_ERR_RET(!value_item, free(key), LY_EMEM); + + if (hints & (LYD_NODEHINT_LIST | LYD_NODEHINT_LEAFLIST)) { + cbor_array_push(*array_p, value_item); + cbor_decref(&value_item); + } else { + struct cbor_pair pair = { + .key = cbor_move(cbor_build_string(key)), + .value = cbor_move(value_item) + }; + free(key); + key = NULL; + + if (!cbor_map_add(parent_map, pair)) { + cbor_decref(&pair.key); + cbor_decref(&pair.value); + return LY_EMEM; } } - } else { - fprintf(stderr, "DEBUG: Empty data tree\n"); - /* Empty data tree - create empty map */ - ctx.root_item = cbor_new_definite_map(0); - if (!ctx.root_item) { - ret = LY_EMEM; - goto cleanup; + + if (!(hints & LYD_NODEHINT_LEAFLIST)) { + /* attributes */ + cbor_print_attributes(pctx, (const struct lyd_node *)node, parent_map, 0); + } else if (!pctx->first_leaflist && node->attr) { + /* attributes printed later */ + pctx->first_leaflist = &node->node; } } - - /* Serialize CBOR to bytes */ - cbor_data_len = cbor_serialize_alloc(ctx.root_item, &cbor_data, &cbor_data_len); - if (cbor_data_len == 0 || !cbor_data) { - fprintf(stderr, "DEBUG: Failed to serialize CBOR or got 0 bytes\n"); - ret = LY_EMEM; - goto cleanup; - } - - fprintf(stderr, "DEBUG: Generated %zu bytes of CBOR data\n", cbor_data_len); - - /* Write to output using ly_print_ macro */ - ly_print_(out, "%.*s", (int)cbor_data_len, cbor_data); -cleanup: - if (ctx.root_item) { - cbor_decref(&ctx.root_item); + if (last && (hints & (LYD_NODEHINT_LIST | LYD_NODEHINT_LEAFLIST))) { + if (key) { + struct cbor_pair pair = { + .key = cbor_move(cbor_build_string(key)), + .value = cbor_move(*array_p) + }; + free(key); + + if (!cbor_map_add(parent_map, pair)) { + cbor_decref(&pair.key); + cbor_decref(&pair.value); + return LY_EMEM; + } + } + cbor_print_array_close(pctx); + *array_p = NULL; } - if (cbor_data) { - free(cbor_data); + + if (key) { + free(key); } - - return ret; + + return LY_SUCCESS; } /** - * @brief Print data subtree in CBOR format + * @brief Print all the types of data node including its metadata. + * + * @param[in] pctx CBOR printer context. + * @param[in] node Data node to print. + * @param[in] parent_map CBOR map to add the node to. + * @return LY_ERR value. */ -LY_ERR -cbor_print_tree(struct ly_out *out, const struct lyd_node *root, uint32_t options, size_t max_depth) +static LY_ERR +cbor_print_node(struct cborpr_ctx *pctx, const struct lyd_node *node, cbor_item_t *parent_map) { - /* For now, ignore max_depth and use the regular print function */ - /* TODO: Implement depth limiting if needed */ - (void)max_depth; - return cbor_print_data(out, root, options); + static cbor_item_t *array = NULL; + + if (!lyd_node_should_print(node, pctx->options)) { + if (cbor_print_array_is_last_inst(pctx, node)) { + cbor_print_array_close(pctx); + } + return LY_SUCCESS; + } + + if (!node->schema) { + LY_CHECK_RET(cbor_print_opaq(pctx, (const struct lyd_node_opaq *)node, parent_map, &array)); + } else { + switch (node->schema->nodetype) { + case LYS_RPC: + case LYS_ACTION: + case LYS_NOTIF: + case LYS_CONTAINER: + LY_CHECK_RET(cbor_print_container(pctx, node, parent_map)); + break; + case LYS_LEAF: + LY_CHECK_RET(cbor_print_leaf(pctx, node, parent_map)); + break; + case LYS_LEAFLIST: + case LYS_LIST: + LY_CHECK_RET(cbor_print_leaf_list(pctx, node, parent_map, &array)); + break; + case LYS_ANYDATA: + case LYS_ANYXML: + LY_CHECK_RET(cbor_print_any(pctx, node, parent_map)); + break; + default: + LOGINT(pctx->ctx); + return EXIT_FAILURE; + } + } + + if (pctx->first_leaflist && !matching_node(node->next, pctx->first_leaflist)) { + cbor_print_meta_attr_leaflist(pctx, parent_map); + pctx->first_leaflist = NULL; + } + + return LY_SUCCESS; } -/** - * @brief Print all data trees in CBOR format - */ LY_ERR -cbor_print_all(struct ly_out *out, const struct lyd_node *root, uint32_t options) +cbor_print_data(struct ly_out *out, const struct lyd_node *root, uint32_t options) { - return cbor_print_data(out, root, options); -} \ No newline at end of file + const struct lyd_node *node; + struct cborpr_ctx pctx = {0}; + unsigned char *buffer = NULL; + size_t buffer_size = 0; + + if (!root) { + /* empty data - print empty map */ + cbor_item_t *empty_map = cbor_new_indefinite_map(); + LY_CHECK_RET(!empty_map, LY_EMEM); + + buffer_size = cbor_serialize_alloc(empty_map, &buffer, &buffer_size); + cbor_decref(&empty_map); + + if (buffer_size == 0) { + return LY_EMEM; + } + + ly_write_(out, (const char *)buffer, buffer_size); + free(buffer); + ly_print_flush(out); + return LY_SUCCESS; + } + + pctx.out = out; + pctx.parent = NULL; + pctx.options = options; + pctx.ctx = LYD_CTX(root); + + /* create root map */ + pctx.root_map = cbor_new_indefinite_map(); + LY_CHECK_RET(!pctx.root_map, LY_EMEM); + + /* print content */ + LY_LIST_FOR(root, node) { + pctx.root = node; + LY_CHECK_ERR_RET(cbor_print_node(&pctx, node, pctx.root_map), + cbor_decref(&pctx.root_map); ly_set_erase(&pctx.open, NULL), LY_EINVAL); + if (!(options & LYD_PRINT_WITHSIBLINGS)) { + break; + } + } + + /* serialize CBOR to buffer */ + buffer_size = cbor_serialize_alloc(pctx.root_map, &buffer, &buffer_size); + cbor_decref(&pctx.root_map); + + if (buffer_size == 0) { + ly_set_erase(&pctx.open, NULL); + return LY_EMEM; + } + + /* write to output */ + ly_write_(out, (const char *)buffer, buffer_size); + free(buffer); + + assert(!pctx.open.count); + ly_set_erase(&pctx.open, NULL); + + ly_print_flush(out); + return LY_SUCCESS; +} + +#endif /* ENABLE_CBOR_SUPPORT */ \ No newline at end of file