diff --git a/CMakeLists.txt b/CMakeLists.txt index 7414e1f70..8792c7a80 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,13 +60,13 @@ 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 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}) @@ -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 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 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() + 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 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 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() + 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/lcbor.c b/src/lcbor.c new file mode 100644 index 000000000..097ea7eec --- /dev/null +++ b/src/lcbor.c @@ -0,0 +1,131 @@ +/** + * @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" + +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 ""; +} + +/** + * @brief Free CBOR context. + * + * @param[in] cborctx CBOR context to free. + */ +void lycbor_ctx_free(struct lycbor_ctx *cborctx) +{ + if (cborctx) + { + free(cborctx); + } +} + +/** + * @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] 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 **cborctx_p) +{ + /* TODO : Need to restructure error handling here */ + LY_ERR ret = LY_SUCCESS; + struct lycbor_ctx *cborctx; + struct cbor_load_result result = {0}; + enum lyd_cbor_format format; + + 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 */ + 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; + } + + /* input line logging */ + ly_log_location(NULL, NULL, NULL, in); + + *cborctx_p = cborctx; + 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..838077ca8 --- /dev/null +++ b/src/lcbor.h @@ -0,0 +1,77 @@ +/** + * @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 */ + + struct { + cbor_item_t *cbor_data; /**< parsed CBOR data */ + enum lyd_cbor_format format; /**< CBOR format variant */ + const char *input; + } backup; +}; + +/** + * @brief Create new CBOR context for parsing. + * + * @param[in] ctx libyang context. + * @param[in] in Input handler. + * @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 **cborctx_p); + +/** + * @brief Free CBOR context. + * + * @param[in] cborctx CBOR context to free. + */ +void +lycbor_ctx_free(struct lycbor_ctx *cborctx); + +#endif /* ENABLE_CBOR_SUPPORT */ + +#endif /* LY_LCBOR_H_ */ \ No newline at end of file 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 new file mode 100644 index 000000000..064dbe62d --- /dev/null +++ b/src/parser_cbor.c @@ -0,0 +1,2089 @@ +/** + * @file parser_cbor.c + * @author Meher Rushi + * @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 +#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" +#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 +#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_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("{"); + 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_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 truly unsupported types + } +} + +/** + * @brief Free the CBOR data parser context. + * + * CBOR implementation of lyd_ctx_free_clb(). + */ +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 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. + * + * @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). + * @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 { + 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 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. + * + * @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_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(snode); + + if (!(snode->nodetype & (LYD_NODE_TERM | LYS_LIST))) + { + return LY_SUCCESS; + } + + if (lydctx->parse_opts & LYD_PARSE_OPAQ) + { + switch (snode->nodetype) + { + 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; + } + } + else if (snode->nodetype & LYD_NODE_TERM) + { + ret = lydcbor_value_type_hint(lydctx, cbor_value, type_hint_p); + } + + return ret; +} + +/** + * @brief Join the forward-referencing metadata with their target data nodes. + * + * @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_metadata_finish(struct lyd_cbor_ctx *lydctx, struct lyd_node **first_p) +{ + LY_ERR ret = LY_SUCCESS; + 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; + } + + LOG_LOCSET(NULL, attr); + log_location_items++; + + 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: + 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_meta_attr(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, struct lyd_node *node, + const cbor_item_t *cbor_meta) +{ + 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); + } + + type = cbor_typeof(cbor_meta); + + /* check attribute encoding */ + switch (nodetype) + { + case LYS_LEAFLIST: + 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]; + + if (lydcbor_is_null(item)) + { + prev = node; + node = node->next; + if (instance < cbor_array_size(cbor_meta)) + { + goto next_entry; + } + goto cleanup; + } + } + else + { + goto cleanup; + } + break; + case LYS_LEAF: + case LYS_ANYXML: + expected = "@name/object"; + LY_CHECK_GOTO(type != CBOR_TYPE_MAP, representation_error); + break; + 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; + } + + /* process all members inside metadata object */ + assert(type == CBOR_TYPE_MAP); + map_size = cbor_map_size(cbor_meta); + pairs = cbor_map_handle(cbor_meta); + + 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 (!cbor_isa_string(key_item)) + { + LOGVAL(ctx, LYVE_SYNTAX, "Metadata key must be a string."); + rc = LY_EVALID; + goto cleanup; + } + + LY_CHECK_GOTO(rc = lydcbor_item_to_string(key_item, &key_str, &key_len), cleanup); + + lydcbor_parse_name(key_str, key_len, &name, &name_len, &prefix, &prefix_len, &is_attr); + + 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; + } + + /* 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); + } + + /* get value hints */ + LY_CHECK_ERR_GOTO(rc = lydcbor_value_type_hint(lydctx, value_item, &val_hints), free(key_str), cleanup); + + 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); + } + + if (nodetype == LYS_LEAFLIST && instance < cbor_array_size(cbor_meta)) + { + prev = node; + node = node->next; + goto next_entry; + } + + goto cleanup; + +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; + +cleanup: + if ((rc == LY_EVALID) && (lydctx->val_opts & LYD_VALIDATE_MULTI_ERROR)) + { + if ((r = lydcbor_data_skip(lydctx->cborctx))) + { + rc = r; + } + } + LOG_LOCBACK(snode ? 1 : 0, 0); + return rc; +} + +/** + * @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 (!*node_p) + { + return; + } + + 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; + } + } + } + *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_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; + 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_typeof(cbor_value) != CBOR_TYPE_MAP) + { + /* 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); + } + + /* get the module name */ + lydcbor_get_node_prefix(parent, prefix, prefix_len, &module_name, &module_name_len); + if (!module_name && !parent && lydctx->any_schema) + { + module_name = lydctx->any_schema->module->name; + module_name_len = strlen(module_name); + } + + /* 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); + +cleanup: + if (dynamic) + { + free((char *)value); + } + return ret; +} + +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 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 ((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])) + { + /* special array null value */ + ((struct lyd_node_opaq *)*node_p)->hints |= LYD_VALHINT_EMPTY; + goto finish; + } + } + + 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); + + for (size_t i = 0; i < array_size; ++i) + { + const cbor_item_t *item = array_handle[i]; + + if (cbor_typeof(item) == CBOR_TYPE_MAP) + { + /* array with objects, list */ + ((struct lyd_node_opaq *)*node_p)->hints |= LYD_NODEHINT_LIST; + + /* 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; + } + + 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); + + 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 (*node_p) + { + 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_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 r; + const char *opaq_name, *mod_name, *attr_mod; + size_t opaq_name_len, attr_mod_len; + + if (!attr_node) + { + /* learn the attribute module name */ + if (!snode) + { + if (!prefix) + { + lydcbor_get_node_prefix(parent, NULL, 0, &attr_mod, &attr_mod_len); + } + else + { + attr_mod = prefix; + attr_mod_len = prefix_len; + } + } + + /* try to find the instance */ + LY_LIST_FOR(parent ? lyd_child(parent) : *first_p, attr_node) + { + 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; + } + + if (!ly_strncmp(LYD_NAME(attr_node), name, name_len) && mod_name && attr_mod && + !ly_strncmp(mod_name, attr_mod, attr_mod_len)) + { + break; + } + } + } + } + + if (!attr_node) + { + /* parse as an opaq node with @ prefix */ + uint32_t prev_opts; + + prev_opts = lydctx->parse_opts; + lydctx->parse_opts &= ~LYD_PARSE_STRICT; + lydctx->parse_opts |= LYD_PARSE_OPAQ; + + 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); + + lydctx->parse_opts = prev_opts; + LY_CHECK_RET(r); + } + else + { + LY_CHECK_RET(lydcbor_meta_attr(lydctx, snode, attr_node, cbor_value)); + } + + return LY_SUCCESS; +} + +/** + * @brief Parse a single anydata/anyxml node from CBOR. + * + * @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_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) +{ + 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(snode->nodetype & LYD_NODE_ANY); + + *node = NULL; + + /* status check according to allowed CBOR types */ + if (snode->nodetype == LYS_ANYXML) + { + 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); + } + else + { + LY_CHECK_RET(type != CBOR_TYPE_MAP, LY_ENOT); + } + + /* create any node */ + if (type == CBOR_TYPE_MAP) + { + /* 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); + + /* finish linking metadata */ + r = lydcbor_metadata_finish(lydctx, &child); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + + /* 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); + } + +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 Parse a single instance of an inner node from CBOR. + * + * @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_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 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); + + if (ext) + { + /* only parse these extension data and validate afterwards */ + lydctx->parse_opts |= LYD_PARSE_ONLY; + } + + /* 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); + + if (snode->nodetype == LYS_LIST) + { + /* check all keys exist */ + r = lyd_parser_check_keys(*node); + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); + } + + if (!(lydctx->parse_opts & LYD_PARSE_ONLY) && !rc) + { + /* new node validation */ + r = lyd_parser_validate_new_implicit((struct lyd_ctx *)lydctx, *node); + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); + } + +cleanup: + lydctx->parse_opts = prev_parse_opts; + LOG_LOCBACK(0, 1); + if (!(*node)->hash) + { + /* list without keys is unusable */ + lyd_free_tree(*node); + *node = NULL; + } + return rc; +} + +/** + * @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; + + LOG_LOCSET(snode, NULL); + + r = lydcbor_data_check_opaq(lydctx, snode, cbor_value, &type_hints); + if (r == LY_SUCCESS) + { + assert(snode->nodetype & (LYD_NODE_TERM | LYD_NODE_INNER | LYD_NODE_ANY)); + if (lydcbor_is_null(cbor_value)) + { + /* do not do anything if value is CBOR null */ + goto cleanup; + } + else if (snode->nodetype & LYD_NODE_TERM) + { + enum cbor_type type = cbor_typeof(cbor_value); + + 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])) + { + /* [null] case */ + goto cleanup; + } + } + + if ((type != CBOR_TYPE_ARRAY) && (type != CBOR_TYPE_UINT) && (type != CBOR_TYPE_NEGINT) && + (type != CBOR_TYPE_STRING) && (type != CBOR_TYPE_FLOAT_CTRL)) + { + rc = LY_ENOT; + 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 (snode->nodetype & LYD_NODE_INNER) + { + /* create inner node */ + r = lydcbor_parse_instance_inner(lydctx, snode, ext, cbor_value, node); + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); + } + 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); + + /* 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 (!(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); + } + } + else if (r == LY_ENOT) + { + /* 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 (snode->nodetype == LYS_LIST) + { + ((struct lyd_node_opaq *)*node)->hints |= LYD_NODEHINT_LIST; + } + else if (snode->nodetype == LYS_LEAFLIST) + { + ((struct lyd_node_opaq *)*node)->hints |= LYD_NODEHINT_LEAFLIST; + } + } + else + { + /* error */ + rc = r; + goto cleanup; + } + +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) +{ + 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; + 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(parent || first_p); + assert(cbor_typeof(cbor_obj) == CBOR_TYPE_MAP); + + map_size = cbor_map_size(cbor_obj); + pairs = cbor_map_handle(cbor_obj); + + if (!pairs && map_size > 0) + { + 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(ctx, LYVE_SYNTAX, "Null key or value in CBOR map"); + rc = LY_EVALID; + goto cleanup; + } + + /* Skip null values */ + if (lydcbor_is_null(value_item)) + { + continue; + } + + /* Get key string */ + 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); + + /* process the node name */ + lydcbor_parse_name(key_str, key_len, &name, &name_len, &prefix, &prefix_len, &is_meta); + + if (!is_meta || name_len || prefix_len) + { + /* get the schema node */ + r = lydcbor_get_snode(lydctx, is_meta, prefix, prefix_len, name, name_len, parent, &snode, &ext); + if (r == LY_ENOT) + { + free(key_str); + continue; + } + else if ((r == LY_EVALID) && (lydctx->val_opts & LYD_VALIDATE_MULTI_ERROR)) + { + rc = r; + free(key_str); + continue; + } + else if (r) + { + rc = r; + free(key_str); + goto cleanup; + } + } + + if (is_meta) + { + /* 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) + { + 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 + { + /* 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; + } + } + + free(key_str); + + /* remember a successfully parsed node */ + if (parsed && node) + { + ly_set_add(parsed, node, 1, NULL); + } + + /* 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); + } + + /* 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: + lyd_free_tree(node); + return rc; +} + +/** + * @brief Common start of CBOR parser processing different types of input data. + * + * @param[in] ctx libyang 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 +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; + enum cbor_type cbortype; + + assert(lydctx_p); + + /* 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_ERR_RET(ret = lycbor_ctx_new(ctx, in, &lydctx->cborctx), free(lydctx), ret); + cbortype = cbor_typeof(lydctx->cborctx->cbor_data); + + if (!cbor_isa_map(lydctx->cborctx->cbor_data)) + { + /* expecting top-level map */ + 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; + } + + *lydctx_p = 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 r, rc = LY_SUCCESS; + struct lyd_cbor_ctx *lydctx = NULL; + + 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); + + /* 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); + + 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); + } + + /* 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: + /* 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; + } + else + { + *lydctx_p = (struct lyd_ctx *)lydctx; + lycbor_ctx_free(lydctx->cborctx); + lydctx->cborctx = NULL; + } + 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 4af6102f7..4a20ce6a4 100644 --- a/src/parser_data.h +++ b/src/parser_data.h @@ -40,6 +40,12 @@ 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). < $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 @@ -265,6 +271,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_internal.h b/src/parser_internal.h index 4068d0ad3..a3501bfb4 100644 --- a/src/parser_internal.h +++ b/src/parser_internal.h @@ -179,6 +179,35 @@ 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 */ + const struct lysc_node *any_schema; /**< parent anyxml/anydata schema node if parsing nested data tree */ +}; +#endif /* ENABLE_CBOR_SUPPORT */ + /** * @brief Parsed extension instance data to validate. */ @@ -348,6 +377,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/parser_json.c b/src/parser_json.c index bda43a50b..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.", @@ -1067,7 +1068,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/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; } 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_cbor.c b/src/printer_cbor.c new file mode 100644 index 000000000..dc4fc2a65 --- /dev/null +++ b/src/printer_cbor.c @@ -0,0 +1,1351 @@ +/** + * @file printer_cbor.c + * @author Meher Rushi + * @brief CBOR printer for libyang data structure + * + * 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. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#define _GNU_SOURCE + +#ifdef ENABLE_CBOR_SUPPORT + +#include +#include +#include +#include + +#include "context.h" +#include "log.h" +#include "ly_common.h" +#include "out.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. + */ +struct cborpr_ctx { + 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 */ + + 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_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 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 int +matching_node(const struct lyd_node *node1, const struct lyd_node *node2) +{ + assert(node1 || node2); + + if (!node1 || !node2) { + return 0; + } else if (node1->schema != node2->schema) { + 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; + } + } + + 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; + } else { + 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 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 const char * +node_prefix(const struct lyd_node *node) +{ + 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 NULL; +} + +/** + * @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 + */ +int +cbor_nscmp(const struct lyd_node *node1, const struct lyd_node *node2) +{ + 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; + + 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); + } + } + + 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 = ""; + } + + /* 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 { + 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: { + /* signed integer types */ + int64_t num = strtoll(value, NULL, 10); + if (num >= 0) { + item = cbor_build_uint64(num); + } else { + item = cbor_build_negint64(-num - 1); + } + break; + } + + case LY_TYPE_UINT8: + case LY_TYPE_UINT16: + case LY_TYPE_UINT32: { + /* unsigned integer types */ + uint64_t num = strtoull(value, NULL, 10); + item = cbor_build_uint64(num); + break; + } + + case LY_TYPE_BOOL: + /* boolean */ + if (strcmp(value, "true") == 0) { + item = cbor_build_bool(true); + } else { + item = cbor_build_bool(false); + } + break; + + case LY_TYPE_EMPTY: + /* 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: + /* error */ + LOGINT_RET(pctx->ctx); + } + + if (dynamic) { + free((char *)value); + } + + *item_p = item; + return item ? LY_SUCCESS : LY_EMEM; +} + +/** + * @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 LY_ERR +cbor_print_attribute(struct cborpr_ctx *pctx, const struct lyd_node_opaq *node, cbor_item_t *attr_map) +{ + 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 LY_SUCCESS; +} + +/** + * @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 LY_ERR +cbor_print_metadata(struct cborpr_ctx *pctx, const struct lyd_node *node, const struct lys_module *wdmod, + cbor_item_t *meta_map) +{ + 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; + } + } + + 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 LY_SUCCESS; +} + +/** + * @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_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 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_attributes(struct cborpr_ctx *pctx, const struct lyd_node *node, cbor_item_t *parent_map, ly_bool inner) +{ + 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"); + } + + 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); + } + 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; + } + } else if (!node->schema && ((struct lyd_node_opaq *)node)->attr) { + meta_map = cbor_new_indefinite_map(); + LY_CHECK_RET(!meta_map, LY_EMEM); + + 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 LY_SUCCESS; +} + +/** + * @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 *pctx, const struct lyd_node *node, cbor_item_t *parent_map) +{ + char *key = NULL; + cbor_item_t *value_item = 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) + }; + + 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 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_content(struct cborpr_ctx *pctx, struct lyd_node_any *any, cbor_item_t **item_p) +{ + LY_ERR ret = LY_SUCCESS; + 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); + } + 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); + } + + switch (any->value_type) { + case LYD_ANYDATA_DATATREE: + /* 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: + 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; + 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; + } + + 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) + }; + + 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, 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; + } + + if ((pctx->root == node) && !(pctx->options & LYD_PRINT_WITHSIBLINGS)) { + /* the only printed instance */ + return 1; + } + + if (!node->next || (node->next->schema != node->schema)) { + /* last instance */ + return 1; + } + + return 0; +} + +/** + * @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_leaf_list(struct cborpr_ctx *pctx, const struct lyd_node *node, cbor_item_t *parent_map, cbor_item_t **array_p) +{ + 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)); + } + + 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 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 LY_ERR +cbor_print_meta_attr_leaflist(struct cborpr_ctx *pctx, cbor_item_t *parent_map) +{ + 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; + } + + 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; + } + } + + /* 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 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. + */ +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_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; + } + + if (hints & (LYD_NODEHINT_LIST | LYD_NODEHINT_LEAFLIST)) { + if (node->prev->next && matching_node(node->prev, &node->node)) { + first = 0; + } + 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; + } + } + } 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); + } + } + } 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); + } + } 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; + } + } + + 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; + } + } + + 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 (key) { + free(key); + } + + return LY_SUCCESS; +} + +/** + * @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. + */ +static LY_ERR +cbor_print_node(struct cborpr_ctx *pctx, const struct lyd_node *node, cbor_item_t *parent_map) +{ + 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; +} + +LY_ERR +cbor_print_data(struct ly_out *out, const struct lyd_node *root, uint32_t options) +{ + 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 diff --git a/src/printer_data.c b/src/printer_data.c index a0eeaea40..d4c73858b 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/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_ */ 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 283cea768..309c6cb1e 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 (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.c b/src/tree_data.c index 517b1694c..7e1cf57d1 100644 --- a/src/tree_data.c +++ b/src/tree_data.c @@ -76,6 +76,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 +89,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 +139,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 +240,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 +425,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_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_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. 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: